diff --git a/src/app/(main)/boards/[boardId]/BoardComponentRenderer.tsx b/src/app/(main)/boards/[boardId]/BoardComponentRenderer.tsx
index f6449fbd4..ec2a30df1 100644
--- a/src/app/(main)/boards/[boardId]/BoardComponentRenderer.tsx
+++ b/src/app/(main)/boards/[boardId]/BoardComponentRenderer.tsx
@@ -6,9 +6,11 @@ import { getComponentDefinition } from '../boardComponentRegistry';
function BoardComponentRendererComponent({
config,
websiteId,
+ entityType,
}: {
config: BoardComponentConfig;
websiteId?: string;
+ entityType?: string;
}) {
const definition = getComponentDefinition(config.type);
@@ -20,7 +22,8 @@ function BoardComponentRendererComponent({
);
}
- const Component = definition.component;
+ const Component =
+ (entityType && definition.componentByEntityType?.[entityType]) || definition.component;
if (!websiteId && definition.requiresWebsite !== false) {
return (
@@ -36,7 +39,9 @@ function BoardComponentRendererComponent({
export const BoardComponentRenderer = memo(
BoardComponentRendererComponent,
(prevProps, nextProps) =>
- prevProps.websiteId === nextProps.websiteId && prevProps.config === nextProps.config,
+ prevProps.websiteId === nextProps.websiteId &&
+ prevProps.entityType === nextProps.entityType &&
+ prevProps.config === nextProps.config,
);
BoardComponentRenderer.displayName = 'BoardComponentRenderer';
diff --git a/src/app/(main)/boards/[boardId]/BoardComponentSelect.tsx b/src/app/(main)/boards/[boardId]/BoardComponentSelect.tsx
index 16c77d269..3a03ffea0 100644
--- a/src/app/(main)/boards/[boardId]/BoardComponentSelect.tsx
+++ b/src/app/(main)/boards/[boardId]/BoardComponentSelect.tsx
@@ -177,14 +177,16 @@ export function BoardComponentSelect({
onSelect(config);
};
- const previewConfig: BoardComponentConfig | null = selectedDef
- ? {
- type: selectedDef.type,
- title,
- description,
- props: { ...selectedDef.defaultProps, ...configValues },
- }
- : null;
+ const previewConfig: BoardComponentConfig | null = useMemo(
+ () =>
+ selectedDef
+ ? {
+ type: selectedDef.type,
+ props: { ...selectedDef.defaultProps, ...configValues },
+ }
+ : null,
+ [selectedDef, configValues],
+ );
const canSave = !!selectedDef && isSelectedDefSupported && (!needsWebsite || !!resolvedEntityId);
const availableDefinitions = useMemo(
@@ -274,12 +276,16 @@ export function BoardComponentSelect({
)}
@@ -364,7 +370,7 @@ export function BoardComponentSelect({
Preview
{hasSelectedEntity && previewConfig && (!needsWebsite || resolvedEntityId) ? (
-
+
) : (
diff --git a/src/app/(main)/boards/[boardId]/BoardEditColumn.tsx b/src/app/(main)/boards/[boardId]/BoardEditColumn.tsx
index 32cfc7c26..0a89ea25c 100644
--- a/src/app/(main)/boards/[boardId]/BoardEditColumn.tsx
+++ b/src/app/(main)/boards/[boardId]/BoardEditColumn.tsx
@@ -42,14 +42,14 @@ export function BoardEditColumn({
const boardType = getBoardType(board);
const { entityType: boardEntityType, entityId: boardEntityId } = getBoardEntity(board);
const definition = component ? getComponentDefinition(component.type) : undefined;
- const { entityId } = getResolvedComponentEntity(board, component);
+ const { entityType, entityId } = getResolvedComponentEntity(board, component);
const renderedComponent = useMemo(() => {
if (!component || (!entityId && definition?.requiresWebsite !== false)) {
return null;
}
- return ;
- }, [component, definition?.requiresWebsite, entityId]);
+ return ;
+ }, [component, definition?.requiresWebsite, entityId, entityType]);
const handleSelect = (config: BoardComponentConfig) => {
onSetComponent(id, config);
diff --git a/src/app/(main)/boards/[boardId]/BoardViewColumn.tsx b/src/app/(main)/boards/[boardId]/BoardViewColumn.tsx
index 580209442..5bb2f8b65 100644
--- a/src/app/(main)/boards/[boardId]/BoardViewColumn.tsx
+++ b/src/app/(main)/boards/[boardId]/BoardViewColumn.tsx
@@ -43,7 +43,7 @@ export function BoardViewColumn({
{description && {description}}
-
+
diff --git a/src/app/(main)/boards/boardComponentRegistry.tsx b/src/app/(main)/boards/boardComponentRegistry.tsx
index 31d2f2c81..2af1c7006 100644
--- a/src/app/(main)/boards/boardComponentRegistry.tsx
+++ b/src/app/(main)/boards/boardComponentRegistry.tsx
@@ -1,5 +1,6 @@
-import type { ComponentType } from 'react';
import { TextBlock } from '@/app/(main)/boards/TextBlock';
+import { LinkMetricsBar } from '@/app/(main)/links/[linkId]/LinkMetricsBar';
+import { PixelMetricsBar } from '@/app/(main)/pixels/[pixelId]/PixelMetricsBar';
import { WebsiteChart } from '@/app/(main)/websites/[websiteId]/WebsiteChart';
import { WebsiteMetricsBar } from '@/app/(main)/websites/[websiteId]/WebsiteMetricsBar';
import {
@@ -15,12 +16,14 @@ import { EventsChart } from '@/components/metrics/EventsChart';
import { MetricsTable } from '@/components/metrics/MetricsTable';
import { WeeklyTraffic } from '@/components/metrics/WeeklyTraffic';
import { WorldMap } from '@/components/metrics/WorldMap';
+import type { ComponentType } from 'react';
export interface ConfigField {
name: string;
label: string;
type: 'select' | 'number' | 'text' | 'textarea';
options?: { label: string; value: string }[];
+ optionsByEntityType?: Record;
defaultValue?: any;
}
@@ -31,6 +34,7 @@ export interface ComponentDefinition {
category: string;
icon: ComponentType;
component: ComponentType;
+ componentByEntityType?: Record>;
defaultProps?: Record;
configFields?: ConfigField[];
requiresWebsite?: boolean;
@@ -44,31 +48,60 @@ export const CATEGORIES = [
] as const;
const METRIC_TYPES = [
- { label: 'Pages', value: 'path' },
- { label: 'Entry pages', value: 'entry' },
- { label: 'Exit pages', value: 'exit' },
- { label: 'Referrers', value: 'referrer' },
- { label: 'Channels', value: 'channel' },
- { label: 'Browsers', value: 'browser' },
+ { label: 'Path', value: 'path' },
+ { label: 'Entry page', value: 'entry' },
+ { label: 'Exit page', value: 'exit' },
+ { label: 'Title', value: 'title' },
+ { label: 'Query', value: 'query' },
+ { label: 'Referrer', value: 'referrer' },
+ { label: 'Channel', value: 'channel' },
+ { label: 'Country', value: 'country' },
+ { label: 'Region', value: 'region' },
+ { label: 'City', value: 'city' },
+ { label: 'Browser', value: 'browser' },
{ label: 'OS', value: 'os' },
- { label: 'Devices', value: 'device' },
- { label: 'Countries', value: 'country' },
- { label: 'Regions', value: 'region' },
- { label: 'Cities', value: 'city' },
- { label: 'Languages', value: 'language' },
- { label: 'Screens', value: 'screen' },
- { label: 'Query parameters', value: 'query' },
- { label: 'Page titles', value: 'title' },
- { label: 'Hosts', value: 'host' },
- { label: 'Events', value: 'event' },
+ { label: 'Device', value: 'device' },
+ { label: 'Language', value: 'language' },
+ { label: 'Screen', value: 'screen' },
+ { label: 'UTM Source', value: 'utmSource' },
+ { label: 'UTM Medium', value: 'utmMedium' },
+ { label: 'UTM Campaign', value: 'utmCampaign' },
+ { label: 'UTM Content', value: 'utmContent' },
+ { label: 'UTM Term', value: 'utmTerm' },
+ { label: 'Event', value: 'event' },
+ { label: 'Hostname', value: 'hostname' },
];
+const PIXEL_LINK_METRIC_TYPES = METRIC_TYPES.filter(({ value }) =>
+ [
+ 'referrer',
+ 'country',
+ 'region',
+ 'city',
+ 'browser',
+ 'os',
+ 'device',
+ 'query',
+ 'utmSource',
+ 'utmMedium',
+ 'utmCampaign',
+ 'utmContent',
+ 'utmTerm',
+ ].includes(value),
+);
+
const LIMIT_OPTIONS = [
{ label: '5', value: '5' },
{ label: '10', value: '10' },
{ label: '20', value: '20' },
];
+const PixelMetricsBarAdapter = ({ websiteId }: { websiteId?: string }) =>
+ websiteId ? : null;
+
+const LinkMetricsBarAdapter = ({ websiteId }: { websiteId?: string }) =>
+ websiteId ? : null;
+
const componentDefinitions: ComponentDefinition[] = [
// Overview
{
@@ -78,6 +111,10 @@ const componentDefinitions: ComponentDefinition[] = [
category: 'overview',
icon: PanelTop,
component: WebsiteMetricsBar,
+ componentByEntityType: {
+ pixel: PixelMetricsBarAdapter,
+ link: LinkMetricsBarAdapter,
+ },
},
{
type: 'WebsiteChart',
@@ -103,6 +140,10 @@ const componentDefinitions: ComponentDefinition[] = [
label: 'Metric type',
type: 'select',
options: METRIC_TYPES,
+ optionsByEntityType: {
+ pixel: PIXEL_LINK_METRIC_TYPES,
+ link: PIXEL_LINK_METRIC_TYPES,
+ },
defaultValue: 'path',
},
{
diff --git a/src/app/(main)/links/[linkId]/LinkMetricsBar.tsx b/src/app/(main)/links/[linkId]/LinkMetricsBar.tsx
index ec07962c6..e8c946bfe 100644
--- a/src/app/(main)/links/[linkId]/LinkMetricsBar.tsx
+++ b/src/app/(main)/links/[linkId]/LinkMetricsBar.tsx
@@ -14,7 +14,7 @@ export function LinkMetricsBar({
}) {
const { isAllTime } = useDateRange();
const { t, labels } = useMessages();
- const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(linkId);
+ const { data, isLoading, isFetching, error } = useWebsiteStatsQuery({ websiteId: linkId });
const { pageviews, visitors, visits, comparison } = data || {};
diff --git a/src/app/(main)/pixels/[pixelId]/PixelMetricsBar.tsx b/src/app/(main)/pixels/[pixelId]/PixelMetricsBar.tsx
index 3a46291e1..d17bc78c6 100644
--- a/src/app/(main)/pixels/[pixelId]/PixelMetricsBar.tsx
+++ b/src/app/(main)/pixels/[pixelId]/PixelMetricsBar.tsx
@@ -14,7 +14,7 @@ export function PixelMetricsBar({
}) {
const { isAllTime } = useDateRange();
const { t, labels } = useMessages();
- const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(pixelId);
+ const { data, isLoading, isFetching, error } = useWebsiteStatsQuery({ websiteId: pixelId });
const { pageviews, visitors, visits, comparison } = data || {};
diff --git a/src/queries/sql/getChannelExpandedMetrics.ts b/src/queries/sql/getChannelExpandedMetrics.ts
index c3201dfa6..c43e2544b 100644
--- a/src/queries/sql/getChannelExpandedMetrics.ts
+++ b/src/queries/sql/getChannelExpandedMetrics.ts
@@ -60,6 +60,7 @@ async function relationalQuery(
website_event.utm_source,
website_event.session_id,
website_event.visit_id,
+ website_event.hostname,
count(*) c,
min(website_event.created_at) min_time,
max(website_event.created_at) max_time
@@ -77,7 +78,8 @@ async function relationalQuery(
website_event.utm_medium,
website_event.utm_source,
website_event.session_id,
- website_event.visit_id),
+ website_event.visit_id,
+ website_event.hostname),
channels as (
select case
diff --git a/src/queries/sql/getChannelMetrics.ts b/src/queries/sql/getChannelMetrics.ts
index 487c5dd74..da28c0492 100644
--- a/src/queries/sql/getChannelMetrics.ts
+++ b/src/queries/sql/getChannelMetrics.ts
@@ -39,7 +39,8 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
website_event.url_query,
website_event.utm_medium,
website_event.utm_source,
- website_event.session_id
+ website_event.session_id,
+ website_event.hostname
from website_event
${cohortQuery}
${excludeBounceQuery}
@@ -61,7 +62,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
when ${toPostgresLikeClause('referrer_domain', EMAIL_DOMAINS)} or utm_medium ilike '%mail%' then 'email'
when ${toPostgresLikeClause('referrer_domain', SHOPPING_DOMAINS)} or utm_medium ilike '%shop%' then concat(prefix, 'Shopping')
when ${toPostgresLikeClause('referrer_domain', VIDEO_DOMAINS)} or utm_medium ilike '%video%' then concat(prefix, 'Video')
- wwhen referrer_domain != regexp_replace(hostname, '^www.', '') and referrer_domain != '' then 'referral'
+ when referrer_domain != regexp_replace(hostname, '^www.', '') and referrer_domain != '' then 'referral'
else '' end AS x,
count(distinct session_id) y
from prefix