From 75778fdfc78658164cd7f41407a4cf6a35a18e8f Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Fri, 21 Oct 2022 16:42:44 -0700 Subject: [PATCH] add event data --- components/common/EventDataButton.js | 48 ++++ components/common/EventDataButton.module.css | 3 + components/forms/EventDataForm.js | 265 ++++++++++++++++++ components/forms/EventDataForm.module.css | 38 +++ components/metrics/DataTable.js | 32 ++- components/metrics/FilterTags.js | 4 +- components/metrics/FilterTags.module.css | 5 +- components/metrics/WebsiteHeader.js | 9 +- components/pages/WebsiteDetails.js | 2 + lang/en-US.json | 7 + lib/clickhouse.js | 5 +- lib/prisma.js | 12 +- pages/api/websites/[id]/eventdata.js | 18 +- pages/login.js | 2 +- queries/analytics/event/getEventData.js | 52 ++-- queries/analytics/event/getEventMetrics.js | 4 +- .../analytics/pageview/getPageviewMetrics.js | 4 +- .../analytics/pageview/getPageviewParams.js | 4 +- .../analytics/pageview/getPageviewStats.js | 4 +- .../analytics/session/getSessionMetrics.js | 4 +- queries/analytics/stats/getActiveVisitors.js | 4 +- queries/analytics/stats/getWebsiteStats.js | 4 +- 22 files changed, 446 insertions(+), 84 deletions(-) create mode 100644 components/common/EventDataButton.js create mode 100644 components/common/EventDataButton.module.css create mode 100644 components/forms/EventDataForm.js create mode 100644 components/forms/EventDataForm.module.css diff --git a/components/common/EventDataButton.js b/components/common/EventDataButton.js new file mode 100644 index 00000000..2b895840 --- /dev/null +++ b/components/common/EventDataButton.js @@ -0,0 +1,48 @@ +import List from 'assets/list-ul.svg'; +import Modal from 'components/common/Modal'; +import PropTypes from 'prop-types'; +import { useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import Button from './Button'; +import EventDataForm from 'components/forms/EventDataForm'; +import styles from './EventDataButton.module.css'; + +function EventDataButton({ websiteId }) { + const [showEventData, setShowEventData] = useState(false); + + function handleClick() { + if (!showEventData) { + setShowEventData(true); + } + } + + function handleClose() { + setShowEventData(false); + } + + return ( + <> + + {showEventData && ( + }> + + + )} + + ); +} + +EventDataButton.propTypes = { + websiteId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), +}; + +export default EventDataButton; diff --git a/components/common/EventDataButton.module.css b/components/common/EventDataButton.module.css new file mode 100644 index 00000000..cd2a2ed6 --- /dev/null +++ b/components/common/EventDataButton.module.css @@ -0,0 +1,3 @@ +.button { + width: fit-content; +} diff --git a/components/forms/EventDataForm.js b/components/forms/EventDataForm.js new file mode 100644 index 00000000..041fdf93 --- /dev/null +++ b/components/forms/EventDataForm.js @@ -0,0 +1,265 @@ +import classNames from 'classnames'; +import Button from 'components/common/Button'; +import DateFilter from 'components/common/DateFilter'; +import DropDown from 'components/common/DropDown'; +import FormLayout, { + FormButtons, + FormError, + FormMessage, + FormRow, +} from 'components/layout/FormLayout'; +import DataTable from 'components/metrics/DataTable'; +import FilterTags from 'components/metrics/FilterTags'; +import { Field, Form, Formik } from 'formik'; +import useApi from 'hooks/useApi'; +import useDateRange from 'hooks/useDateRange'; +import { useState, useEffect } from 'react'; +import { FormattedMessage } from 'react-intl'; +import styles from './EventDataForm.module.css'; +import useTimezone from 'hooks/useTimezone'; + +export const filterOptions = [ + { label: 'Count', value: 'count' }, + { label: 'Average', value: 'avg' }, + { label: 'Minimum', value: 'min' }, + { label: 'Maxmimum', value: 'max' }, + { label: 'Sum', value: 'sum' }, +]; + +export const dateOptions = [ + { label: , value: '1day' }, + { + label: ( + + ), + value: '24hour', + }, + { + label: , + value: '-1day', + }, + { + label: , + value: '1week', + divider: true, + }, + { + label: ( + + ), + value: '7day', + }, + { + label: , + value: '1month', + divider: true, + }, + { + label: ( + + ), + value: '30day', + }, + { + label: ( + + ), + value: '90day', + }, + { label: , value: '1year' }, + { + label: , + value: 'custom', + divider: true, + }, +]; + +export default function EventDataForm({ websiteId, onClose, className }) { + const { post } = useApi(); + const [message, setMessage] = useState(); + const [columns, setColumns] = useState({ url: 'count', inventory: 'sum', price: 'avg' }); + const [filters, setFilters] = useState({ url: '/about' }); + const [data, setData] = useState([]); + const [dateRange, setDateRange] = useDateRange('report'); + const { startDate, endDate, value } = dateRange; + const [timezone] = useTimezone(); + const [isValid, setIsValid] = useState(false); + + useEffect(() => { + if (Object.keys(columns).length > 0) { + setIsValid(true); + } else { + setIsValid(false); + } + }, [columns]); + + const handleAddTag = (value, list, setState, resetForm) => { + setState({ ...list, [`${value.field}`]: value.value }); + resetForm(); + }; + + const handleRemoveTag = (value, list, setState) => { + const { ...rest } = list; + + delete rest[`${value}`]; + + setState(rest); + }; + + const handleSubmit = async () => { + const params = { + website_id: websiteId, + start_at: +startDate, + end_at: +endDate, + timezone, + columns, + filters, + }; + + const { ok, data } = await post(`/websites/${websiteId}/eventdata`, params); + + if (!ok) { + setMessage(); + setData([]); + } else { + setData(data); + + if (message) { + setMessage(null); + } + } + }; + + return ( + <> + {message} +
+
+ +
+ + + + +
+
+ + handleAddTag(value, columns, setColumns, resetForm) + } + > + {({ values, setFieldValue }) => ( +
+ + +
+ + +
+
+ + +
+ setFieldValue('value', value)} + className={styles.dropdown} + name="value" + options={filterOptions} + /> + +
+
+ + + +
+ )} +
+ handleRemoveTag(value, columns, setColumns)} + /> +
+
+ + handleAddTag(value, filters, setFilters, resetForm) + } + > + {({ values }) => ( +
+ + +
+ + +
+
+ + +
+ + +
+
+ + + +
+ )} +
+ handleRemoveTag(value, filters, setFilters)} + /> +
+
+
+
+ +
+
+ + + + + + ); +} diff --git a/components/forms/EventDataForm.module.css b/components/forms/EventDataForm.module.css new file mode 100644 index 00000000..19d76f77 --- /dev/null +++ b/components/forms/EventDataForm.module.css @@ -0,0 +1,38 @@ +.container { + display: flex; +} + +.form { + border-right: 1px solid var(--gray300); + width: 420px; +} + +.filters { + padding: 10px 5px; +} + +.filters + .filters { + border-top: 1px solid var(--gray300); + min-height: 250px; +} + +.table { + padding: 10px; + min-height: 430px; + min-width: 400px; +} + +.formButtons { + justify-content: flex-start; + margin-left: 20px; +} + +.dropdown { + min-height: 39px; + min-width: 240px; +} + +.filterTag { + flex-wrap: wrap; + margin: 10px 5px 5px 5px; +} diff --git a/components/metrics/DataTable.js b/components/metrics/DataTable.js index d7e58cf7..c9596106 100644 --- a/components/metrics/DataTable.js +++ b/components/metrics/DataTable.js @@ -16,6 +16,7 @@ export default function DataTable({ height, animate = true, virtualize = false, + showPercentage = true, }) { const [format, setFormat] = useState(true); const formatFunc = format ? formatLongNumber : formatNumber; @@ -38,6 +39,7 @@ export default function DataTable({ animate={animate && !virtualize} format={formatFunc} onClick={handleSetFormat} + showPercentage={showPercentage} /> ); }; @@ -68,7 +70,15 @@ export default function DataTable({ ); } -const AnimatedRow = ({ label, value = 0, percent, animate, format, onClick }) => { +const AnimatedRow = ({ + label, + value = 0, + percent, + animate, + format, + onClick, + showPercentage = true, +}) => { const props = useSpring({ width: percent, y: value, @@ -82,15 +92,17 @@ const AnimatedRow = ({ label, value = 0, percent, animate, format, onClick }) =>
{props.y?.interpolate(format)}
-
- `${n}%`) }} - /> - - {props.width.interpolate(n => `${n.toFixed(0)}%`)} - -
+ {showPercentage && ( +
+ `${n}%`) }} + /> + + {props.width.interpolate(n => `${n.toFixed(0)}%`)} + +
+ )} ); }; diff --git a/components/metrics/FilterTags.js b/components/metrics/FilterTags.js index be65540e..bb4174b8 100644 --- a/components/metrics/FilterTags.js +++ b/components/metrics/FilterTags.js @@ -5,12 +5,12 @@ import Button from 'components/common/Button'; import Times from 'assets/times.svg'; import styles from './FilterTags.module.css'; -export default function FilterTags({ params, onClick }) { +export default function FilterTags({ className, params, onClick }) { if (Object.keys(params).filter(key => params[key]).length === 0) { return null; } return ( -
+
{Object.keys(params).map(key => { if (!params[key]) { return null; diff --git a/components/metrics/FilterTags.module.css b/components/metrics/FilterTags.module.css index bb1536e5..50ae60a0 100644 --- a/components/metrics/FilterTags.module.css +++ b/components/metrics/FilterTags.module.css @@ -7,8 +7,5 @@ .tag { text-align: center; margin-bottom: 10px; -} - -.tag + .tag { - margin-left: 20px; + margin-right: 20px; } diff --git a/components/metrics/WebsiteHeader.js b/components/metrics/WebsiteHeader.js index 1a6bdf15..517ef140 100644 --- a/components/metrics/WebsiteHeader.js +++ b/components/metrics/WebsiteHeader.js @@ -1,14 +1,13 @@ -import React from 'react'; +import Arrow from 'assets/arrow-right.svg'; import classNames from 'classnames'; -import { FormattedMessage } from 'react-intl'; +import Favicon from 'components/common/Favicon'; import Link from 'components/common/Link'; import OverflowText from 'components/common/OverflowText'; -import PageHeader from 'components/layout/PageHeader'; import RefreshButton from 'components/common/RefreshButton'; import ButtonLayout from 'components/layout/ButtonLayout'; -import Favicon from 'components/common/Favicon'; +import PageHeader from 'components/layout/PageHeader'; +import { FormattedMessage } from 'react-intl'; import ActiveUsers from './ActiveUsers'; -import Arrow from 'assets/arrow-right.svg'; import styles from './WebsiteHeader.module.css'; export default function WebsiteHeader({ websiteId, title, domain, showLink = false }) { diff --git a/components/pages/WebsiteDetails.js b/components/pages/WebsiteDetails.js index 3fc234a4..b40bc9ee 100644 --- a/components/pages/WebsiteDetails.js +++ b/components/pages/WebsiteDetails.js @@ -24,6 +24,7 @@ import useFetch from 'hooks/useFetch'; import usePageQuery from 'hooks/usePageQuery'; import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; import styles from './WebsiteDetails.module.css'; +import EventDataButton from 'components/common/EventDataButton'; const messages = defineMessages({ pages: { id: 'metrics.pages', defaultMessage: 'Pages' }, @@ -183,6 +184,7 @@ export default function WebsiteDetails({ websiteId }) { + diff --git a/lang/en-US.json b/lang/en-US.json index aa5e3ae3..f58a25cd 100644 --- a/lang/en-US.json +++ b/lang/en-US.json @@ -1,6 +1,8 @@ { "label.accounts": "Accounts", "label.add-account": "Add account", + "label.add-column": "Add column", + "label.add-filter": "Add filter", "label.add-website": "Add website", "label.administrator": "Administrator", "label.all": "All", @@ -25,6 +27,8 @@ "label.edit-account": "Edit account", "label.edit-website": "Edit website", "label.enable-share-url": "Enable share URL", + "label.event-data": "Event Data", + "label.field-name": "Field Name", "label.invalid": "Invalid", "label.invalid-domain": "Invalid domain", "label.language": "Language", @@ -48,6 +52,7 @@ "label.reset": "Reset", "label.reset-website": "Reset statistics", "label.save": "Save", + "label.search": "Search", "label.settings": "Settings", "label.share-url": "Share URL", "label.single-day": "Single day", @@ -58,8 +63,10 @@ "label.timezone": "Timezone", "label.today": "Today", "label.tracking-code": "Tracking code", + "label.type": "Type", "label.unknown": "Unknown", "label.username": "Username", + "label.value": "Value", "label.view-details": "View details", "label.websites": "Websites", "label.yesterday": "Yesterday", diff --git a/lib/clickhouse.js b/lib/clickhouse.js index bf4bc5c5..41b1df5a 100644 --- a/lib/clickhouse.js +++ b/lib/clickhouse.js @@ -65,8 +65,7 @@ function getCommaSeparatedStringFormat(data) { } function getBetweenDates(field, start_at, end_at) { - return `${field} between ${getDateFormat(start_at)} - and ${getDateFormat(end_at)}`; + return `${field} between ${getDateFormat(start_at)} and ${getDateFormat(end_at)}`; } function getJsonField(column, property) { @@ -81,7 +80,7 @@ function getEventDataColumnsQuery(column, columns) { return arr; } - arr.push(`${filter}(${getJsonField(column, key)})`); + arr.push(`${filter}(${getJsonField(column, key)}) as ${key}_${filter}`); return arr; }, []); diff --git a/lib/prisma.js b/lib/prisma.js index 1ebf8aa4..ab1e6ebf 100644 --- a/lib/prisma.js +++ b/lib/prisma.js @@ -85,13 +85,13 @@ function getTimestampInterval(field) { } } -function getJsonField(column, property, value) { +function getJsonField(column, property, isNumber) { const db = getDatabaseType(process.env.DATABASE_URL); if (db === POSTGRESQL) { let accessor = `${column} ->> '${property}'`; - if (value && typeof value === 'number') { + if (isNumber) { accessor = `CAST(${accessor} AS DECIMAL)`; } @@ -111,7 +111,9 @@ function getEventDataColumnsQuery(column, columns) { return arr; } - arr.push(`${filter}(${getJsonField(column, key)})`); + const isNumber = ['sum', 'avg', 'min', 'max'].some(a => a === filter); + + arr.push(`${filter}(${getJsonField(column, key, isNumber)}) as "${filter}(${key})"`); return arr; }, []); @@ -127,8 +129,10 @@ function getEventDataFilterQuery(column, filters) { return arr; } + const isNumber = filter && typeof filter === 'number'; + arr.push( - `${getJsonField(column, key, filter)} = ${ + `${getJsonField(column, key, isNumber)} = ${ typeof filter === 'string' ? `'${filter}'` : filter }`, ); diff --git a/pages/api/websites/[id]/eventdata.js b/pages/api/websites/[id]/eventdata.js index 61cd8671..86a17b77 100644 --- a/pages/api/websites/[id]/eventdata.js +++ b/pages/api/websites/[id]/eventdata.js @@ -4,8 +4,6 @@ import { ok, badRequest, methodNotAllowed, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; -const unitTypes = ['year', 'month', 'hour', 'day']; - export default async (req, res) => { await useCors(req, res); await useAuth(req, res); @@ -15,18 +13,11 @@ export default async (req, res) => { return unauthorized(res); } - const { - website_id: websiteId, - start_at, - end_at, - unit, - timezone, - event_name: eventName, - columns, - filters, - } = req.body; + const { id: websiteId } = req.query; - if (!moment.tz.zone(timezone) || !unitTypes.includes(unit)) { + const { start_at, end_at, timezone, event_name: eventName, columns, filters } = req.body; + + if (!moment.tz.zone(timezone)) { return badRequest(res); } @@ -37,7 +28,6 @@ export default async (req, res) => { startDate, endDate, timezone, - unit, eventName, columns, filters, diff --git a/pages/login.js b/pages/login.js index d46110c8..12f70ac9 100644 --- a/pages/login.js +++ b/pages/login.js @@ -16,6 +16,6 @@ export default function LoginPage({ loginDisabled }) { export async function getServerSideProps() { return { - props: { loginDisabled: !!process.env.DISABLE_LOGIN || process.env.isCloudMode }, + props: { loginDisabled: !!process.env.DISABLE_LOGIN || !!process.env.isCloudMode }, }; } diff --git a/queries/analytics/event/getEventData.js b/queries/analytics/event/getEventData.js index 8fa87f2f..91302d30 100644 --- a/queries/analytics/event/getEventData.js +++ b/queries/analytics/event/getEventData.js @@ -9,16 +9,12 @@ export async function getEventData(...args) { }); } -async function relationalQuery( - websiteId, - { startDate, endDate, timezone = 'utc', unit = 'day', event_name, columns, filters }, -) { - const { rawQuery, getDateQuery, getEventDataColumnsQuery, getEventDataFilterQuery } = prisma; +async function relationalQuery(websiteId, { startDate, endDate, event_name, columns, filters }) { + const { rawQuery, getEventDataColumnsQuery, getEventDataFilterQuery } = prisma; const params = [startDate, endDate]; return rawQuery( `select - ${getDateQuery('event.created_at', unit, timezone)} t, ${getEventDataColumnsQuery('event_data.event_data', columns)} from event join website @@ -28,38 +24,40 @@ async function relationalQuery( where website_uuid='${websiteId}' and event.created_at between $1 and $2 ${event_name ? `and event_name = ${event_name}` : ''} - ${filters ? `and ${getEventDataFilterQuery('event_data.event_data', filters)}` : ''} - group by 1 - order by 2`, + ${ + Object.keys(filters).length > 0 + ? `and ${getEventDataFilterQuery('event_data.event_data', filters)}` + : '' + }`, params, - ); + ).then(results => { + return Object.keys(results[0]).map(a => { + return { x: a, y: results[0][`${a}`] }; + }); + }); } -async function clickhouseQuery( - websiteId, - { startDate, endDate, timezone = 'UTC', unit = 'day', event_name, columns, filters }, -) { - const { - rawQuery, - getDateQuery, - getBetweenDates, - getEventDataColumnsQuery, - getEventDataFilterQuery, - } = clickhouse; +async function clickhouseQuery(websiteId, { startDate, endDate, event_name, columns, filters }) { + const { rawQuery, getBetweenDates, getEventDataColumnsQuery, getEventDataFilterQuery } = + clickhouse; const params = [websiteId]; return rawQuery( `select - event_name x, - ${getDateQuery('created_at', unit, timezone)} t, ${getEventDataColumnsQuery('event_data', columns)} from event where website_id= $1 ${event_name ? `and event_name = ${event_name}` : ''} and ${getBetweenDates('created_at', startDate, endDate)} - ${filters ? `and ${getEventDataFilterQuery('event_data', filters)}` : ''} - group by x, t - order by t`, + ${ + Object.keys(filters).length > 0 + ? `and ${getEventDataFilterQuery('event_data', filters)}` + : '' + }`, params, - ); + ).then(results => { + return Object.keys(results[0]).map(a => { + return { x: a, y: results[0][`${a}`] }; + }); + }); } diff --git a/queries/analytics/event/getEventMetrics.js b/queries/analytics/event/getEventMetrics.js index edf5de8c..605bb688 100644 --- a/queries/analytics/event/getEventMetrics.js +++ b/queries/analytics/event/getEventMetrics.js @@ -18,7 +18,7 @@ async function relationalQuery( filters = {}, ) { const { rawQuery, getDateQuery, getFilterQuery } = prisma; - const params = [websiteId, start_at, end_at]; + const params = [start_at, end_at]; return rawQuery( `select @@ -29,7 +29,7 @@ async function relationalQuery( join website on event.website_id = website.website_id where website_uuid='${websiteId}' - and event.created_at between $2 and $3 + and event.created_at between $1 and $2 ${getFilterQuery('event', filters, params)} group by 1, 2 order by 2`, diff --git a/queries/analytics/pageview/getPageviewMetrics.js b/queries/analytics/pageview/getPageviewMetrics.js index e1c4d43f..69607d00 100644 --- a/queries/analytics/pageview/getPageviewMetrics.js +++ b/queries/analytics/pageview/getPageviewMetrics.js @@ -11,7 +11,7 @@ export async function getPageviewMetrics(...args) { async function relationalQuery(websiteId, { startDate, endDate, column, table, filters = {} }) { const { rawQuery, parseFilters } = prisma; - const params = [websiteId, startDate, endDate]; + const params = [startDate, endDate]; const { pageviewQuery, sessionQuery, eventQuery, joinSession } = parseFilters( table, column, @@ -25,7 +25,7 @@ async function relationalQuery(websiteId, { startDate, endDate, column, table, f ${` join website on ${table}.website_id = website.website_id`} ${joinSession} where website.website_uuid='${websiteId}' - and ${table}.created_at between $2 and $3 + and ${table}.created_at between $1 and $2 ${pageviewQuery} ${joinSession && sessionQuery} ${eventQuery} diff --git a/queries/analytics/pageview/getPageviewParams.js b/queries/analytics/pageview/getPageviewParams.js index 8ec26dec..5cdabfa3 100644 --- a/queries/analytics/pageview/getPageviewParams.js +++ b/queries/analytics/pageview/getPageviewParams.js @@ -10,7 +10,7 @@ export async function getPageviewParams(...args) { async function relationalQuery(websiteId, start_at, end_at, column, table, filters = {}) { const { parseFilters, rawQuery } = prisma; - const params = [websiteId, start_at, end_at]; + const params = [start_at, end_at]; const { pageviewQuery, sessionQuery, eventQuery, joinSession } = parseFilters( table, column, @@ -25,7 +25,7 @@ async function relationalQuery(websiteId, start_at, end_at, column, table, filte ${` join website on ${table}.website_id = website.website_id`} ${joinSession} where website.website_uuid='${websiteId}' - and ${table}.created_at between $2 and $3 + and ${table}.created_at between $1 and $2 and ${table}.url like '%?%' ${pageviewQuery} ${joinSession && sessionQuery} diff --git a/queries/analytics/pageview/getPageviewStats.js b/queries/analytics/pageview/getPageviewStats.js index ceed4daf..5ec8339f 100644 --- a/queries/analytics/pageview/getPageviewStats.js +++ b/queries/analytics/pageview/getPageviewStats.js @@ -22,7 +22,7 @@ async function relationalQuery( }, ) { const { getDateQuery, parseFilters, rawQuery } = prisma; - const params = [websiteId, start_at, end_at]; + const params = [start_at, end_at]; const { pageviewQuery, sessionQuery, joinSession } = parseFilters( 'pageview', null, @@ -38,7 +38,7 @@ async function relationalQuery( on pageview.website_id = website.website_id ${joinSession} where website.website_uuid='${websiteId}' - and pageview.created_at between $2 and $3 + and pageview.created_at between $1 and $2 ${pageviewQuery} ${sessionQuery} group by 1`, diff --git a/queries/analytics/session/getSessionMetrics.js b/queries/analytics/session/getSessionMetrics.js index cbf3ed58..020bddfb 100644 --- a/queries/analytics/session/getSessionMetrics.js +++ b/queries/analytics/session/getSessionMetrics.js @@ -11,7 +11,7 @@ export async function getSessionMetrics(...args) { async function relationalQuery(websiteId, { startDate, endDate, field, filters = {} }) { const { parseFilters, rawQuery } = prisma; - const params = [websiteId, startDate, endDate]; + const params = [startDate, endDate]; const { pageviewQuery, sessionQuery, joinSession } = parseFilters(null, filters, params); return rawQuery( @@ -24,7 +24,7 @@ async function relationalQuery(websiteId, { startDate, endDate, field, filters = on pageview.website_id = website.website_id ${joinSession} where website.website_uuid='${websiteId}' - and pageview.created_at between $2 and $3 + and pageview.created_at between $1 and $2 ${pageviewQuery} ${sessionQuery} ) diff --git a/queries/analytics/stats/getActiveVisitors.js b/queries/analytics/stats/getActiveVisitors.js index 9d6b1f09..3a898d94 100644 --- a/queries/analytics/stats/getActiveVisitors.js +++ b/queries/analytics/stats/getActiveVisitors.js @@ -12,7 +12,7 @@ export async function getActiveVisitors(...args) { async function relationalQuery(websiteId) { const date = subMinutes(new Date(), 5); - const params = [websiteId, date]; + const params = [date]; return prisma.rawQuery( `select count(distinct session_id) x @@ -20,7 +20,7 @@ async function relationalQuery(websiteId) { join website on pageview.website_id = website.website_id where website.website_uuid = '${websiteId}' - and pageview.created_at >= $2`, + and pageview.created_at >= $1`, params, ); } diff --git a/queries/analytics/stats/getWebsiteStats.js b/queries/analytics/stats/getWebsiteStats.js index b6bf7b87..134e1c3e 100644 --- a/queries/analytics/stats/getWebsiteStats.js +++ b/queries/analytics/stats/getWebsiteStats.js @@ -11,7 +11,7 @@ export async function getWebsiteStats(...args) { async function relationalQuery(websiteId, { start_at, end_at, filters = {} }) { const { getDateQuery, getTimestampInterval, parseFilters, rawQuery } = prisma; - const params = [websiteId, start_at, end_at]; + const params = [start_at, end_at]; const { pageviewQuery, sessionQuery, joinSession } = parseFilters( 'pageview', null, @@ -34,7 +34,7 @@ async function relationalQuery(websiteId, { start_at, end_at, filters = {} }) { on pageview.website_id = website.website_id ${joinSession} where website.website_uuid='${websiteId}' - and pageview.created_at between $2 and $3 + and pageview.created_at between $1 and $2 ${pageviewQuery} ${sessionQuery} group by 1, 2