- {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;
return (
-
- {formatMessage(type === 'url' ? labels.viewedPage : labels.triggeredEvent)}
-
- {value}
+ {formatMessage(getLabel(type))}
+ {`${value}${
+ type === 'event-data' ? `:(${operator}):${property}` : ''
+ }`}
}>
- {goals.map((goal: { type: string; value: string; goal: number }, index: number) => {
- return (
-
- : }
- onRemove={() => handleRemoveGoals(index)}
- >
- {goal.value}
-
- {formatMessage(labels.goal)}: {formatNumber(goal.goal)}
-
-
-
- {(close: () => void) => (
-
-
-
- )}
-
-
- );
- })}
+ {goals.map(
+ (
+ goal: {
+ type: string;
+ value: string;
+ goal: number;
+ operator?: string;
+ property?: string;
+ },
+ index: number,
+ ) => {
+ return (
+
+ : }
+ onRemove={() => handleRemoveGoals(index)}
+ >
+
+ {goal.value}
+ {goal.type === 'event-data' && (
+
+ {formatMessage(labels[goal.operator])}: {goal.property}
+
+ )}
+
+ {formatMessage(labels.goal)}: {formatNumber(goal.goal)}
+
+
+
+
+ {(close: () => void) => (
+
+
+
+ )}
+
+
+ );
+ },
+ )}
diff --git a/src/components/messages.ts b/src/components/messages.ts
index 238ebf52..08448476 100644
--- a/src/components/messages.ts
+++ b/src/components/messages.ts
@@ -95,6 +95,9 @@ export const labels = defineMessages({
devices: { id: 'label.devices', defaultMessage: 'Devices' },
countries: { id: 'label.countries', defaultMessage: 'Countries' },
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' },
events: { id: 'label.events', defaultMessage: 'Events' },
query: { id: 'label.query', defaultMessage: 'Query' },
@@ -107,6 +110,7 @@ export const labels = defineMessages({
views: { id: 'label.views', defaultMessage: 'Views' },
none: { id: 'label.none', defaultMessage: 'None' },
clearAll: { id: 'label.clear-all', defaultMessage: 'Clear all' },
+ property: { id: 'label.property', defaultMessage: 'Property' },
today: { id: 'label.today', defaultMessage: 'Today' },
lastHours: { id: 'label.last-hours', defaultMessage: 'Last {x} hours' },
yesterday: { id: 'label.yesterday', defaultMessage: 'Yesterday' },
@@ -178,8 +182,6 @@ export const labels = defineMessages({
before: { id: 'label.before', defaultMessage: 'Before' },
after: { id: 'label.after', defaultMessage: 'After' },
total: { id: 'label.total', defaultMessage: 'Total' },
- sum: { id: 'label.sum', defaultMessage: 'Sum' },
- average: { id: 'label.average', defaultMessage: 'Average' },
min: { id: 'label.min', defaultMessage: 'Min' },
max: { id: 'label.max', defaultMessage: 'Max' },
unique: { id: 'label.unique', defaultMessage: 'Unique' },
@@ -220,6 +222,10 @@ export const labels = defineMessages({
id: 'message.viewed-page',
defaultMessage: 'Viewed page',
},
+ collectedData: {
+ id: 'message.collected-data',
+ defaultMessage: 'Collected data',
+ },
triggeredEvent: {
id: 'message.triggered-event',
defaultMessage: 'Triggered event',
@@ -241,7 +247,6 @@ export const labels = defineMessages({
id: 'label.goals-description',
defaultMessage: 'Track your goals for pageviews and events.',
},
- count: { id: 'label.count', defaultMessage: 'Count' },
journey: { id: 'label.journey', defaultMessage: 'Journey' },
journeyDescription: {
id: 'label.journey-description',
diff --git a/src/pages/api/reports/goals.ts b/src/pages/api/reports/goals.ts
index bb766775..f775dc3c 100644
--- a/src/pages/api/reports/goals.ts
+++ b/src/pages/api/reports/goals.ts
@@ -28,9 +28,23 @@ const schema = {
.array()
.of(
yup.object().shape({
- type: yup.string().required(),
+ type: yup
+ .string()
+ .matches(/url|event|event-data/i)
+ .required(),
value: yup.string().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)
diff --git a/src/queries/analytics/reports/getGoals.ts b/src/queries/analytics/reports/getGoals.ts
index d26998d0..66c1d951 100644
--- a/src/queries/analytics/reports/getGoals.ts
+++ b/src/queries/analytics/reports/getGoals.ts
@@ -8,7 +8,7 @@ export async function getGoals(
criteria: {
startDate: Date;
endDate: Date;
- goals: { type: string; value: string; goal: number }[];
+ goals: { type: string; value: string; goal: number; operator?: string }[];
},
]
) {
@@ -23,7 +23,7 @@ async function relationalQuery(
criteria: {
startDate: Date;
endDate: Date;
- goals: { type: string; value: string; goal: number }[];
+ goals: { type: string; value: string; goal: number; operator?: string }[];
},
): Promise {
const { startDate, endDate, goals } = criteria;
@@ -119,7 +119,7 @@ async function clickhouseQuery(
criteria: {
startDate: Date;
endDate: Date;
- goals: { type: string; value: string; goal: number }[];
+ 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;
@@ -127,13 +127,22 @@ async function clickhouseQuery(
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;
@@ -145,41 +154,77 @@ async function clickhouseQuery(
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
.map((a, i) => `countIf(url_path = {url${i}:String}) AS URL${i},`)
.join('\n')
.slice(0, -1);
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')
.slice(0, -1);
- return { url: urlColumns, events: eventColumns };
+ return { url: 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}: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 columns = getColumns(urls, events);
- const where = getWhere(urls, events);
+ const parameters = getParameters(urls, events, eventData);
+ const columns = getColumns(urls, events, eventData);
+ const where = getWhere(urls, events, eventData);
const urlResults = hasUrl
? await rawQuery(
@@ -221,5 +266,25 @@ async function clickhouseQuery(
})
: [];
- return [...urlResults, ...eventResults];
+ const eventDataResults = hasEventData
+ ? await rawQuery(
+ `
+ 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];
}