This commit is contained in:
Mike Cao 2024-05-21 21:15:44 -07:00
commit 6b03935fca
7 changed files with 411 additions and 143 deletions

View File

@ -6,27 +6,48 @@ import styles from './GoalsAddForm.module.css';
export function GoalsAddForm({ export function GoalsAddForm({
type: defaultType = 'url', type: defaultType = 'url',
value: defaultValue = '', value: defaultValue = '',
property: defaultProperty = '',
operator: defaultAggregae = null,
goal: defaultGoal = 10, goal: defaultGoal = 10,
onChange, onChange,
}: { }: {
type?: string; type?: string;
value?: string; value?: string;
operator?: string;
property?: string;
goal?: number; goal?: number;
onChange?: (step: { type: string; value: string; goal: number }) => void; onChange?: (step: {
type: string;
value: string;
goal: number;
operator?: string;
property?: string;
}) => void;
}) { }) {
const [type, setType] = useState(defaultType); const [type, setType] = useState(defaultType);
const [value, setValue] = useState(defaultValue); const [value, setValue] = useState(defaultValue);
const [operator, setOperator] = useState(defaultAggregae);
const [property, setProperty] = useState(defaultProperty);
const [goal, setGoal] = useState(defaultGoal); const [goal, setGoal] = useState(defaultGoal);
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const items = [ const items = [
{ label: formatMessage(labels.url), value: 'url' }, { label: formatMessage(labels.url), value: 'url' },
{ label: formatMessage(labels.event), value: 'event' }, { label: formatMessage(labels.event), value: 'event' },
{ label: formatMessage(labels.eventData), value: 'event-data' },
];
const operators = [
{ label: formatMessage(labels.count), value: 'count' },
{ label: formatMessage(labels.average), value: 'average' },
{ label: formatMessage(labels.sum), value: 'sum' },
]; ];
const isDisabled = !type || !value; const isDisabled = !type || !value;
const handleSave = () => { const handleSave = () => {
onChange({ type, value, goal }); onChange(
type === 'event-data' ? { type, value, goal, operator, property } : { type, value, goal },
);
setValue(''); setValue('');
setProperty('');
setGoal(10); setGoal(10);
}; };
@ -45,6 +66,10 @@ export function GoalsAddForm({
return items.find(item => item.value === value)?.label; return items.find(item => item.value === value)?.label;
}; };
const renderoperatorValue = (value: any) => {
return operators.find(item => item.value === value)?.label;
};
return ( return (
<Flexbox direction="column" gap={10}> <Flexbox direction="column" gap={10}>
<FormRow label={formatMessage(defaultValue ? labels.update : labels.add)}> <FormRow label={formatMessage(defaultValue ? labels.update : labels.add)}>
@ -70,6 +95,31 @@ export function GoalsAddForm({
/> />
</Flexbox> </Flexbox>
</FormRow> </FormRow>
{type === 'event-data' && (
<FormRow label={formatMessage(labels.property)}>
<Flexbox gap={10}>
<Dropdown
className={styles.dropdown}
items={operators}
value={operator}
renderValue={renderoperatorValue}
onChange={(value: any) => setOperator(value)}
>
{({ value, label }) => {
return <Item key={value}>{label}</Item>;
}}
</Dropdown>
<TextField
className={styles.input}
value={property}
onChange={e => handleChange(e, setProperty)}
autoFocus={true}
autoComplete="off"
onKeyDown={handleKeyDown}
/>
</Flexbox>
</FormRow>
)}
<FormRow label={formatMessage(labels.goal)}> <FormRow label={formatMessage(labels.goal)}>
<Flexbox gap={10}> <Flexbox gap={10}>
<TextField <TextField

View File

@ -11,19 +11,36 @@ export function GoalsChart({ className }: { className?: string; isLoading?: bool
const { data } = report || {}; const { data } = report || {};
const getLabel = type => {
let label = '';
switch (type) {
case 'url':
label = labels.viewedPage;
break;
case 'event':
label = labels.triggeredEvent;
break;
default:
label = labels.collectedData;
break;
}
return label;
};
return ( return (
<div className={classNames(styles.chart, className)}> <div className={classNames(styles.chart, className)}>
{data?.map(({ type, value, goal, result }, index: number) => { {data?.map(({ type, value, goal, result, property, operator }, index: number) => {
const percent = result > goal ? 100 : (result / goal) * 100; const percent = result > goal ? 100 : (result / goal) * 100;
return ( return (
<div key={index} className={styles.goal}> <div key={index} className={styles.goal}>
<div className={styles.card}> <div className={styles.card}>
<div className={styles.header}> <div className={styles.header}>
<span className={styles.label}> <span className={styles.label}>{formatMessage(getLabel(type))}</span>
{formatMessage(type === 'url' ? labels.viewedPage : labels.triggeredEvent)} <span className={styles.item}>{`${value}${
</span> type === 'event-data' ? `:(${operator}):${property}` : ''
<span className={styles.item}>{value}</span> }`}</span>
</div> </div>
<div className={styles.track}> <div className={styles.track}>
<div <div

View File

@ -4,6 +4,16 @@
font-weight: 600; font-weight: 600;
} }
.eventData {
color: var(--orange900);
background-color: var(--orange100);
font-size: 12px;
font-weight: 900;
padding: 2px 8px;
border-radius: 5px;
width: fit-content;
}
.goal { .goal {
color: var(--blue900); color: var(--blue900);
background-color: var(--blue100); background-color: var(--blue100);
@ -11,4 +21,5 @@
font-weight: 900; font-weight: 900;
padding: 2px 8px; padding: 2px 8px;
border-radius: 5px; border-radius: 5px;
width: fit-content;
} }

View File

@ -4,6 +4,7 @@ import { formatNumber } from 'lib/format';
import { useContext } from 'react'; import { useContext } from 'react';
import { import {
Button, Button,
Flexbox,
Form, Form,
FormButtons, FormButtons,
FormRow, FormRow,
@ -79,33 +80,53 @@ export function GoalsParameters() {
<BaseParameters allowWebsiteSelect={!id} /> <BaseParameters allowWebsiteSelect={!id} />
<FormRow label={formatMessage(labels.goals)} action={<AddGoalsButton />}> <FormRow label={formatMessage(labels.goals)} action={<AddGoalsButton />}>
<ParameterList> <ParameterList>
{goals.map((goal: { type: string; value: string; goal: number }, index: number) => { {goals.map(
return ( (
<PopupTrigger key={index}> goal: {
<ParameterList.Item type: string;
icon={goal.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />} value: string;
onRemove={() => handleRemoveGoals(index)} goal: number;
> operator?: string;
<div className={styles.value}>{goal.value}</div> property?: string;
<div className={styles.goal}> },
{formatMessage(labels.goal)}: {formatNumber(goal.goal)} index: number,
</div> ) => {
</ParameterList.Item> return (
<Popup alignment="start"> <PopupTrigger key={index}>
{(close: () => void) => ( <ParameterList.Item
<PopupForm> icon={goal.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />}
<GoalsAddForm onRemove={() => handleRemoveGoals(index)}
type={goal.type} >
value={goal.value} <Flexbox direction="column" gap={5}>
goal={goal.goal} <div className={styles.value}>{goal.value}</div>
onChange={handleUpdateGoals.bind(null, close, index)} {goal.type === 'event-data' && (
/> <div className={styles.eventData}>
</PopupForm> {formatMessage(labels[goal.operator])}: {goal.property}
)} </div>
</Popup> )}
</PopupTrigger> <div className={styles.goal}>
); {formatMessage(labels.goal)}: {formatNumber(goal.goal)}
})} </div>
</Flexbox>
</ParameterList.Item>
<Popup alignment="start">
{(close: () => void) => (
<PopupForm>
<GoalsAddForm
type={goal.type}
value={goal.value}
goal={goal.goal}
operator={goal.operator}
property={goal.property}
onChange={handleUpdateGoals.bind(null, close, index)}
/>
</PopupForm>
)}
</Popup>
</PopupTrigger>
);
},
)}
</ParameterList> </ParameterList>
</FormRow> </FormRow>
<FormButtons> <FormButtons>

View File

@ -95,6 +95,9 @@ export const labels = defineMessages({
devices: { id: 'label.devices', defaultMessage: 'Devices' }, devices: { id: 'label.devices', defaultMessage: 'Devices' },
countries: { id: 'label.countries', defaultMessage: 'Countries' }, countries: { id: 'label.countries', defaultMessage: 'Countries' },
languages: { id: 'label.languages', defaultMessage: 'Languages' }, languages: { id: 'label.languages', defaultMessage: 'Languages' },
count: { id: 'label.count', defaultMessage: 'Count' },
average: { id: 'label.average', defaultMessage: 'Average' },
sum: { id: 'label.sum', defaultMessage: 'Sum' },
event: { id: 'label.event', defaultMessage: 'Event' }, event: { id: 'label.event', defaultMessage: 'Event' },
events: { id: 'label.events', defaultMessage: 'Events' }, events: { id: 'label.events', defaultMessage: 'Events' },
query: { id: 'label.query', defaultMessage: 'Query' }, query: { id: 'label.query', defaultMessage: 'Query' },
@ -107,6 +110,7 @@ export const labels = defineMessages({
views: { id: 'label.views', defaultMessage: 'Views' }, views: { id: 'label.views', defaultMessage: 'Views' },
none: { id: 'label.none', defaultMessage: 'None' }, none: { id: 'label.none', defaultMessage: 'None' },
clearAll: { id: 'label.clear-all', defaultMessage: 'Clear all' }, clearAll: { id: 'label.clear-all', defaultMessage: 'Clear all' },
property: { id: 'label.property', defaultMessage: 'Property' },
today: { id: 'label.today', defaultMessage: 'Today' }, today: { id: 'label.today', defaultMessage: 'Today' },
lastHours: { id: 'label.last-hours', defaultMessage: 'Last {x} hours' }, lastHours: { id: 'label.last-hours', defaultMessage: 'Last {x} hours' },
yesterday: { id: 'label.yesterday', defaultMessage: 'Yesterday' }, yesterday: { id: 'label.yesterday', defaultMessage: 'Yesterday' },
@ -178,8 +182,6 @@ export const labels = defineMessages({
before: { id: 'label.before', defaultMessage: 'Before' }, before: { id: 'label.before', defaultMessage: 'Before' },
after: { id: 'label.after', defaultMessage: 'After' }, after: { id: 'label.after', defaultMessage: 'After' },
total: { id: 'label.total', defaultMessage: 'Total' }, total: { id: 'label.total', defaultMessage: 'Total' },
sum: { id: 'label.sum', defaultMessage: 'Sum' },
average: { id: 'label.average', defaultMessage: 'Average' },
min: { id: 'label.min', defaultMessage: 'Min' }, min: { id: 'label.min', defaultMessage: 'Min' },
max: { id: 'label.max', defaultMessage: 'Max' }, max: { id: 'label.max', defaultMessage: 'Max' },
unique: { id: 'label.unique', defaultMessage: 'Unique' }, unique: { id: 'label.unique', defaultMessage: 'Unique' },
@ -220,6 +222,10 @@ export const labels = defineMessages({
id: 'message.viewed-page', id: 'message.viewed-page',
defaultMessage: 'Viewed page', defaultMessage: 'Viewed page',
}, },
collectedData: {
id: 'message.collected-data',
defaultMessage: 'Collected data',
},
triggeredEvent: { triggeredEvent: {
id: 'message.triggered-event', id: 'message.triggered-event',
defaultMessage: 'Triggered event', defaultMessage: 'Triggered event',
@ -241,7 +247,6 @@ export const labels = defineMessages({
id: 'label.goals-description', id: 'label.goals-description',
defaultMessage: 'Track your goals for pageviews and events.', defaultMessage: 'Track your goals for pageviews and events.',
}, },
count: { id: 'label.count', defaultMessage: 'Count' },
journey: { id: 'label.journey', defaultMessage: 'Journey' }, journey: { id: 'label.journey', defaultMessage: 'Journey' },
journeyDescription: { journeyDescription: {
id: 'label.journey-description', id: 'label.journey-description',

View File

@ -28,9 +28,23 @@ const schema = {
.array() .array()
.of( .of(
yup.object().shape({ yup.object().shape({
type: yup.string().required(), type: yup
.string()
.matches(/url|event|event-data/i)
.required(),
value: yup.string().required(), value: yup.string().required(),
goal: yup.number().required(), goal: yup.number().required(),
operator: yup
.string()
.matches(/count|sum|average/i)
.when('type', {
is: 'eventData',
then: yup.string().required(),
}),
property: yup.string().when('type', {
is: 'eventData',
then: yup.string().required(),
}),
}), }),
) )
.min(1) .min(1)

View File

@ -8,7 +8,7 @@ export async function getGoals(
criteria: { criteria: {
startDate: Date; startDate: Date;
endDate: Date; endDate: Date;
goals: { type: string; value: string; goal: number }[]; goals: { type: string; value: string; goal: number; operator?: string }[];
}, },
] ]
) { ) {
@ -23,117 +23,30 @@ async function relationalQuery(
criteria: { criteria: {
startDate: Date; startDate: Date;
endDate: Date; endDate: Date;
goals: { type: string; value: string; goal: number }[]; goals: { type: string; value: string; goal: number; operator?: string }[];
}, },
): Promise<any> { ): Promise<any> {
const { startDate, endDate, goals } = criteria; const { startDate, endDate, goals } = criteria;
const { rawQuery } = prisma; const { rawQuery } = prisma;
const hasUrl = goals.some(a => a.type === 'url');
const hasEvent = goals.some(a => a.type === 'event');
function getParameters(goals: { type: string; value: string; goal: number }[]) {
const urls = goals
.filter(a => a.type === 'url')
.reduce((acc, cv, i) => {
acc[`${cv.type}${i}`] = cv.value;
return acc;
}, {});
const events = goals
.filter(a => a.type === 'event')
.reduce((acc, cv, i) => {
acc[`${cv.type}${i}`] = cv.value;
return acc;
}, {});
return {
urls: { ...urls, startDate, endDate, websiteId },
events: { ...events, startDate, endDate, websiteId },
};
}
function getColumns(goals: { type: string; value: string; goal: number }[]) {
const urls = goals
.filter(a => a.type === 'url')
.map((a, i) => `COUNT(CASE WHEN url_path = {{url${i}}} THEN 1 END) AS URL${i}`)
.join('\n');
const events = goals
.filter(a => a.type === 'event')
.map((a, i) => `COUNT(CASE WHEN url_path = {{event${i}}} THEN 1 END) AS EVENT${i}`)
.join('\n');
return { urls, events };
}
function getWhere(goals: { type: string; value: string; goal: number }[]) {
const urls = goals
.filter(a => a.type === 'url')
.map((a, i) => `{{url${i}}}`)
.join(',');
const events = goals
.filter(a => a.type === 'event')
.map((a, i) => `{{event${i}}}`)
.join(',');
return { urls: `and url_path in (${urls})`, events: `and event_name in (${events})` };
}
const parameters = getParameters(goals);
const columns = getColumns(goals);
const where = getWhere(goals);
const urls = hasUrl
? await rawQuery(
`
select
${columns.urls}
from website_event
where websiteId = {{websiteId::uuid}}
${where.urls}
and created_at between {{startDate}} and {{endDate}}
`,
parameters.urls,
)
: [];
const events = hasEvent
? await rawQuery(
`
select
${columns.events}
from website_event
where websiteId = {{websiteId::uuid}}
${where.events}
and created_at between {{startDate}} and {{endDate}}
`,
parameters.events,
)
: [];
return [...urls, ...events];
}
async function clickhouseQuery(
websiteId: string,
criteria: {
startDate: Date;
endDate: Date;
goals: { type: string; value: string; goal: number }[];
},
): Promise<{ type: string; value: string; goal: number; result: number }[]> {
const { startDate, endDate, goals } = criteria;
const { rawQuery } = clickhouse;
const urls = goals.filter(a => a.type === 'url'); const urls = goals.filter(a => a.type === 'url');
const events = goals.filter(a => a.type === 'event'); const events = goals.filter(a => a.type === 'event');
const eventData = goals.filter(a => a.type === 'event-data');
const hasUrl = urls.length > 0; const hasUrl = urls.length > 0;
const hasEvent = events.length > 0; const hasEvent = events.length > 0;
const hasEventData = eventData.length > 0;
function getParameters( function getParameters(
urls: { type: string; value: string; goal: number }[], urls: { type: string; value: string; goal: number }[],
events: { type: string; value: string; goal: number }[], events: { type: string; value: string; goal: number }[],
eventData: {
type: string;
value: string;
goal: number;
operator?: string;
property?: string;
}[],
) { ) {
const urlParam = urls.reduce((acc, cv, i) => { const urlParam = urls.reduce((acc, cv, i) => {
acc[`${cv.type}${i}`] = cv.value; acc[`${cv.type}${i}`] = cv.value;
@ -145,41 +58,258 @@ async function clickhouseQuery(
return acc; return acc;
}, {}); }, {});
const eventDataParam = eventData.reduce((acc, cv, i) => {
acc[`eventData${i}`] = cv.value;
acc[`property${i}`] = cv.property;
return acc;
}, {});
return { return {
urls: { ...urlParam, startDate, endDate, websiteId }, urls: { ...urlParam, startDate, endDate, websiteId },
events: { ...eventParam, startDate, endDate, websiteId }, events: { ...eventParam, startDate, endDate, websiteId },
eventData: { ...eventDataParam, startDate, endDate, websiteId },
}; };
} }
function getColumns( function getColumns(
urls: { type: string; value: string; goal: number }[], urls: { type: string; value: string; goal: number }[],
events: { type: string; value: string; goal: number }[], events: { type: string; value: string; goal: number }[],
eventData: {
type: string;
value: string;
goal: number;
operator?: string;
property?: string;
}[],
) {
const urlColumns = urls
.map((a, i) => `COUNT(CASE WHEN url_path = {{url${i}}} THEN 1 END) AS URL${i},`)
.join('\n')
.slice(0, -1);
const eventColumns = events
.map((a, i) => `COUNT(CASE WHEN event_name = {{event${i}}} THEN 1 END) AS EVENT${i},`)
.join('\n')
.slice(0, -1);
const eventDataColumns = eventData
.map(
(a, i) =>
`${
a.operator === 'average' ? 'avg' : a.operator
}(CASE WHEN event_name = {{eventData${i}}} AND data_key = {{property${i}}} THEN ${
a.operator === 'count' ? '1' : 'number_value'
} END) AS EVENT_DATA${i},`,
)
.join('\n')
.slice(0, -1);
return { urls: urlColumns, events: eventColumns, eventData: eventDataColumns };
}
function getWhere(
urls: { type: string; value: string; goal: number }[],
events: { type: string; value: string; goal: number }[],
eventData: {
type: string;
value: string;
goal: number;
operator?: string;
property?: string;
}[],
) {
const urlWhere = urls.map((a, i) => `{{url${i}}}`).join(',');
const eventWhere = events.map((a, i) => `{{event${i}}}`).join(',');
const eventDataNameWhere = eventData.map((a, i) => `{{eventData${i}}}`).join(',');
const eventDataKeyWhere = eventData.map((a, i) => `{{property${i}}}`).join(',');
return {
urls: `and url_path in (${urlWhere})`,
events: `and event_name in (${eventWhere})`,
eventData: `and event_name in (${eventDataNameWhere}) and data_key in (${eventDataKeyWhere})`,
};
}
const parameters = getParameters(urls, events, eventData);
const columns = getColumns(urls, events, eventData);
const where = getWhere(urls, events, eventData);
const urlResults = hasUrl
? await rawQuery(
`
select
${columns.urls}
from website_event
where website_id = {{websiteId::uuid}}
${where.urls}
and created_at between {{startDate}} and {{endDate}}
`,
parameters.urls,
).then(a => {
const results = a[0];
return Object.keys(results).map((key, i) => ({
...urls[i],
goal: Number(urls[i].goal),
result: Number(results[key]),
}));
})
: [];
const eventResults = hasEvent
? await rawQuery(
`
select
${columns.events}
from website_event
where website_id = {{websiteId::uuid}}
${where.events}
and created_at between {{startDate}} and {{endDate}}
`,
parameters.events,
).then(a => {
const results = a[0];
return Object.keys(results).map((key, i) => {
return { ...events[i], goal: Number(events[i].goal), result: Number(results[key]) };
});
})
: [];
const eventDataResults = hasEventData
? await rawQuery(
`
select
${columns.eventData}
from website_event w
join event_data d
on d.website_event_id = w.event_id
where w.website_id = {{websiteId::uuid}}
${where.eventData}
and w.created_at between {{startDate}} and {{endDate}}
`,
parameters.eventData,
).then(a => {
const results = a[0];
return Object.keys(results).map((key, i) => {
return { ...eventData[i], goal: Number(eventData[i].goal), result: Number(results[key]) };
});
})
: [];
return [...urlResults, ...eventResults, ...eventDataResults];
}
async function clickhouseQuery(
websiteId: string,
criteria: {
startDate: Date;
endDate: Date;
goals: { type: string; value: string; goal: number; operator?: string; property?: string }[];
},
): Promise<{ type: string; value: string; goal: number; result: number }[]> {
const { startDate, endDate, goals } = criteria;
const { rawQuery } = clickhouse;
const urls = goals.filter(a => a.type === 'url');
const events = goals.filter(a => a.type === 'event');
const eventData = goals.filter(a => a.type === 'event-data');
const hasUrl = urls.length > 0;
const hasEvent = events.length > 0;
const hasEventData = eventData.length > 0;
function getParameters(
urls: { type: string; value: string; goal: number }[],
events: { type: string; value: string; goal: number }[],
eventData: {
type: string;
value: string;
goal: number;
operator?: string;
property?: string;
}[],
) {
const urlParam = urls.reduce((acc, cv, i) => {
acc[`${cv.type}${i}`] = cv.value;
return acc;
}, {});
const eventParam = events.reduce((acc, cv, i) => {
acc[`${cv.type}${i}`] = cv.value;
return acc;
}, {});
const eventDataParam = eventData.reduce((acc, cv, i) => {
acc[`eventData${i}`] = cv.value;
acc[`property${i}`] = cv.property;
return acc;
}, {});
return {
urls: { ...urlParam, startDate, endDate, websiteId },
events: { ...eventParam, startDate, endDate, websiteId },
eventData: { ...eventDataParam, startDate, endDate, websiteId },
};
}
function getColumns(
urls: { type: string; value: string; goal: number }[],
events: { type: string; value: string; goal: number }[],
eventData: {
type: string;
value: string;
goal: number;
operator?: string;
property?: string;
}[],
) { ) {
const urlColumns = urls const urlColumns = urls
.map((a, i) => `countIf(url_path = {url${i}:String}) AS URL${i},`) .map((a, i) => `countIf(url_path = {url${i}:String}) AS URL${i},`)
.join('\n') .join('\n')
.slice(0, -1); .slice(0, -1);
const eventColumns = events const eventColumns = events
.map((a, i) => `countIf(event_name = {event${i}:String}) AS EVENT${i}`) .map((a, i) => `countIf(event_name = {event${i}:String}) AS EVENT${i},`)
.join('\n')
.slice(0, -1);
const eventDataColumns = eventData
.map(
(a, i) =>
`${a.operator === 'average' ? 'avg' : a.operator}If(${
a.operator !== 'count' ? 'number_value, ' : ''
}event_name = {eventData${i}:String} AND data_key = {property${i}:String}) AS EVENT_DATA${i},`,
)
.join('\n') .join('\n')
.slice(0, -1); .slice(0, -1);
return { url: urlColumns, events: eventColumns }; return { url: urlColumns, events: eventColumns, eventData: eventDataColumns };
} }
function getWhere( function getWhere(
urls: { type: string; value: string; goal: number }[], urls: { type: string; value: string; goal: number }[],
events: { type: string; value: string; goal: number }[], events: { type: string; value: string; goal: number }[],
eventData: {
type: string;
value: string;
goal: number;
operator?: string;
property?: string;
}[],
) { ) {
const urlWhere = urls.map((a, i) => `{url${i}:String}`).join(','); const urlWhere = urls.map((a, i) => `{url${i}:String}`).join(',');
const eventWhere = events.map((a, i) => `{event${i}:String}`).join(','); const eventWhere = events.map((a, i) => `{event${i}:String}`).join(',');
const eventDataNameWhere = eventData.map((a, i) => `{eventData${i}:String}`).join(',');
const eventDataKeyWhere = eventData.map((a, i) => `{property${i}:String}`).join(',');
return { urls: `and url_path in (${urlWhere})`, events: `and event_name in (${eventWhere})` }; return {
urls: `and url_path in (${urlWhere})`,
events: `and event_name in (${eventWhere})`,
eventData: `and event_name in (${eventDataNameWhere}) and data_key in (${eventDataKeyWhere})`,
};
} }
const parameters = getParameters(urls, events); const parameters = getParameters(urls, events, eventData);
const columns = getColumns(urls, events); const columns = getColumns(urls, events, eventData);
const where = getWhere(urls, events); const where = getWhere(urls, events, eventData);
const urlResults = hasUrl const urlResults = hasUrl
? await rawQuery<any>( ? await rawQuery<any>(
@ -221,5 +351,25 @@ async function clickhouseQuery(
}) })
: []; : [];
return [...urlResults, ...eventResults]; const eventDataResults = hasEventData
? await rawQuery<any>(
`
select
${columns.eventData}
from event_data
where website_id = {websiteId:UUID}
${where.eventData}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
`,
parameters.eventData,
).then(a => {
const results = a[0];
return Object.keys(results).map((key, i) => {
return { ...eventData[i], goal: Number(eventData[i].goal), result: Number(results[key]) };
});
})
: [];
return [...urlResults, ...eventResults, ...eventDataResults];
} }