choose correct Metricsbar based on component type, fix metric types list based on component type, fix chart previews reanimating. fix hostname broken query on channel query
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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({
|
||||
<Select
|
||||
value={String(configValues[field.name] ?? field.defaultValue ?? '')}
|
||||
onChange={(value: string) => handleConfigChange(field.name, value)}
|
||||
maxHeight={300}
|
||||
popoverProps={{ style: { width: 220 } }}
|
||||
>
|
||||
{field.options?.map(option => (
|
||||
<ListItem key={option.value} id={option.value}>
|
||||
{option.label}
|
||||
</ListItem>
|
||||
))}
|
||||
{(field.optionsByEntityType?.[activeEntityType] ?? field.options)?.map(
|
||||
option => (
|
||||
<ListItem key={option.value} id={option.value}>
|
||||
{option.label}
|
||||
</ListItem>
|
||||
),
|
||||
)}
|
||||
</Select>
|
||||
)}
|
||||
|
||||
@@ -364,7 +370,7 @@ export function BoardComponentSelect({
|
||||
<Text weight="bold">Preview</Text>
|
||||
<Column border="left" paddingLeft="4" height="100%" style={{ minWidth: 0 }}>
|
||||
{hasSelectedEntity && previewConfig && (!needsWebsite || resolvedEntityId) ? (
|
||||
<BoardComponentRenderer config={previewConfig} websiteId={resolvedEntityId} />
|
||||
<BoardComponentRenderer config={previewConfig} websiteId={resolvedEntityId} entityType={resolvedEntityType} />
|
||||
) : (
|
||||
<Column alignItems="center" justifyContent="center" height="100%">
|
||||
<Text color="muted">
|
||||
|
||||
@@ -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 <BoardComponentRenderer config={component} websiteId={entityId} />;
|
||||
}, [component, definition?.requiresWebsite, entityId]);
|
||||
return <BoardComponentRenderer config={component} websiteId={entityId} entityType={entityType} />;
|
||||
}, [component, definition?.requiresWebsite, entityId, entityType]);
|
||||
|
||||
const handleSelect = (config: BoardComponentConfig) => {
|
||||
onSetComponent(id, config);
|
||||
|
||||
@@ -43,7 +43,7 @@ export function BoardViewColumn({
|
||||
{description && <Text color="muted">{description}</Text>}
|
||||
<Column width="100%" height="100%" style={{ minHeight: 0 }}>
|
||||
<Column width="100%" flexGrow={1} style={{ minHeight: 0 }}>
|
||||
<BoardComponentRenderer config={component} websiteId={entityId} />
|
||||
<BoardComponentRenderer config={component} websiteId={entityId} entityType={entityType} />
|
||||
</Column>
|
||||
</Column>
|
||||
</Panel>
|
||||
|
||||
@@ -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<string, { label: string; value: string }[]>;
|
||||
defaultValue?: any;
|
||||
}
|
||||
|
||||
@@ -31,6 +34,7 @@ export interface ComponentDefinition {
|
||||
category: string;
|
||||
icon: ComponentType<any>;
|
||||
component: ComponentType<any>;
|
||||
componentByEntityType?: Record<string, ComponentType<any>>;
|
||||
defaultProps?: Record<string, any>;
|
||||
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 ? <PixelMetricsBar pixelId={websiteId} /> : null;
|
||||
|
||||
const LinkMetricsBarAdapter = ({ websiteId }: { websiteId?: string }) =>
|
||||
websiteId ? <LinkMetricsBar linkId={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',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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 || {};
|
||||
|
||||
|
||||
@@ -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 || {};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user