Skip to content

Commit

Permalink
feat(web-analytics): Hide persons for web analytics (#18445)
Browse files Browse the repository at this point in the history
* Hide persons from web analytics world map

* More general hiding of persons from Charts

* Improve test on web analytics dashboard

* Hide session analysis warning on web analytics insights

* Move suppressSessionAnalysisWarning to insightViz

* Move chartRenderingMetadata out of InsightLogicProps

* Only allow countries with a count > 0 to be clicked

* Fix nullability issue
  • Loading branch information
robbie-c authored Nov 9, 2023
1 parent 8f8f0e2 commit 1f50637
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 66 deletions.
36 changes: 23 additions & 13 deletions frontend/src/queries/nodes/InsightViz/InsightContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,6 @@ import { FunnelCorrelation } from 'scenes/insights/views/Funnels/FunnelCorrelati
import { InsightResultMetadata } from './InsightResultMetadata'
import { Link } from '@posthog/lemon-ui'

const VIEW_MAP = {
[`${InsightType.TRENDS}`]: <TrendInsight view={InsightType.TRENDS} />,
[`${InsightType.STICKINESS}`]: <TrendInsight view={InsightType.STICKINESS} />,
[`${InsightType.LIFECYCLE}`]: <TrendInsight view={InsightType.LIFECYCLE} />,
[`${InsightType.FUNNELS}`]: <FunnelInsight />,
[`${InsightType.RETENTION}`]: <RetentionContainer />,
[`${InsightType.PATHS}`]: <Paths />,
}

export function InsightContainer({
disableHeader,
disableTable,
Expand Down Expand Up @@ -82,11 +73,11 @@ export function InsightContainer({
trendsFilter,
funnelsFilter,
supportsDisplay,
isUsingSessionAnalysis,
samplingFactor,
insightDataLoading,
erroredQueryId,
timedOutQueryId,
shouldShowSessionAnalysisWarning,
} = useValues(insightVizDataLogic(insightProps))
const { exportContext } = useValues(insightDataLogic(insightProps))

Expand Down Expand Up @@ -135,6 +126,25 @@ export function InsightContainer({
return null
})()

function renderActiveView(): JSX.Element | null {
switch (activeView) {
case InsightType.TRENDS:
return <TrendInsight view={InsightType.TRENDS} context={context} />
case InsightType.STICKINESS:
return <TrendInsight view={InsightType.STICKINESS} context={context} />
case InsightType.LIFECYCLE:
return <TrendInsight view={InsightType.LIFECYCLE} context={context} />
case InsightType.FUNNELS:
return <FunnelInsight />
case InsightType.RETENTION:
return <RetentionContainer />
case InsightType.PATHS:
return <Paths />
default:
return null
}
}

function renderTable(): JSX.Element | null {
if (
isFunnels &&
Expand Down Expand Up @@ -197,7 +207,7 @@ export function InsightContainer({

return (
<>
{isUsingSessionAnalysis ? (
{shouldShowSessionAnalysisWarning ? (
<div className="mb-4">
<LemonBanner type="info">
When using sessions and session properties, events without session IDs will be excluded from the
Expand Down Expand Up @@ -255,13 +265,13 @@ export function InsightContainer({
BlockingEmptyState
) : supportsDisplay && showLegend ? (
<div className="insights-graph-container-row flex flex-nowrap">
<div className="insights-graph-container-row-left">{VIEW_MAP[activeView]}</div>
<div className="insights-graph-container-row-left">{renderActiveView()}</div>
<div className="insights-graph-container-row-right">
<InsightLegend />
</div>
</div>
) : (
VIEW_MAP[activeView]
renderActiveView()
)}
</div>
)}
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1686,6 +1686,9 @@
"description": "Show with most visual options enabled. Used in insight scene.",
"type": "boolean"
},
"hidePersonsModal": {
"type": "boolean"
},
"kind": {
"const": "InsightVizNode",
"type": "string"
Expand Down Expand Up @@ -1713,6 +1716,9 @@
},
"source": {
"$ref": "#/definitions/InsightQueryNode"
},
"suppressSessionAnalysisWarning": {
"type": "boolean"
}
},
"required": ["kind", "source"],
Expand Down Expand Up @@ -2528,6 +2534,9 @@
"description": "Show with most visual options enabled. Used in insight scene.",
"type": "boolean"
},
"hidePersonsModal": {
"type": "boolean"
},
"kind": {
"const": "SavedInsightNode",
"type": "string"
Expand Down Expand Up @@ -2619,6 +2628,9 @@
"showTimings": {
"description": "Show a detailed query timing breakdown",
"type": "boolean"
},
"suppressSessionAnalysisWarning": {
"type": "boolean"
}
},
"required": ["kind", "shortId"],
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/queries/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,8 @@ interface InsightVizNodeViewProps {
showResults?: boolean
/** Query is embedded inside another bordered component */
embedded?: boolean
suppressSessionAnalysisWarning?: boolean
hidePersonsModal?: boolean
}

/** Base class for insight query nodes. Should not be used directly. */
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/queries/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InsightLogicProps } from '~/types'
import { ChartDisplayType, InsightLogicProps, TrendResult } from '~/types'
import { ComponentType, HTMLProps } from 'react'
import { DataTableNode } from '~/queries/schema'

Expand All @@ -15,6 +15,15 @@ export interface QueryContext {
emptyStateHeading?: string
emptyStateDetail?: string
rowProps?: (record: unknown) => Omit<HTMLProps<HTMLTableRowElement>, 'key'>
/** chart-specific rendering context **/
chartRenderingMetadata?: ChartRenderingMetadata
}

/** Pass custom rendering metadata to specific kinds of charts **/
export interface ChartRenderingMetadata {
[ChartDisplayType.WorldMap]?: {
countryProps?: (countryCode: string, countryData: TrendResult | undefined) => Omit<HTMLProps<SVGElement>, 'key'>
}
}

export type QueryContextColumnTitleComponent = ComponentType<{
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/scenes/insights/insightLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { isInsightVizNode } from '~/queries/utils'
import { userLogic } from 'scenes/userLogic'
import { transformLegacyHiddenLegendKeys } from 'scenes/funnels/funnelUtils'
import { summarizeInsight } from 'scenes/insights/summarizeInsight'
import { InsightVizNode } from '~/queries/schema'

const IS_TEST_MODE = process.env.NODE_ENV === 'test'
export const UNSAVED_INSIGHT_MIN_REFRESH_INTERVAL_MINUTES = 3
Expand Down Expand Up @@ -540,6 +541,7 @@ export const insightLogic = kea<insightLogicType>([
)
},
],
showPersonsModal: [() => [(_, p) => p.query], (query?: InsightVizNode) => !query || !query.hidePersonsModal],
}),
listeners(({ actions, selectors, values }) => ({
setFiltersMerge: ({ filters }) => {
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/scenes/insights/insightVizDataLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,11 @@ export const insightVizDataLogic = kea<insightVizDataLogicType>([
)
},
],

shouldShowSessionAnalysisWarning: [
(s) => [s.isUsingSessionAnalysis, s.query],
(isUsingSessionAnalysis, query) =>
isUsingSessionAnalysis && !(isInsightVizNode(query) && query.suppressSessionAnalysisWarning),
],
isNonTimeSeriesDisplay: [
(s) => [s.display],
(display) => !!display && NON_TIME_SERIES_DISPLAY_TYPES.includes(display),
Expand Down
51 changes: 39 additions & 12 deletions frontend/src/scenes/insights/views/WorldMap/WorldMap.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useValues, useActions } from 'kea'
import React, { useEffect, useRef } from 'react'
import React, { HTMLProps, useEffect, useRef } from 'react'
import { insightLogic } from 'scenes/insights/insightLogic'
import { ChartParams, TrendResult } from '~/types'
import { ChartDisplayType, ChartParams, TrendResult } from '~/types'
import './WorldMap.scss'
import { InsightTooltip } from 'scenes/insights/InsightTooltip/InsightTooltip'
import { SeriesDatum } from '../../InsightTooltip/insightTooltipUtils'
Expand Down Expand Up @@ -104,6 +104,10 @@ interface WorldMapSVGProps extends ChartParams {
showTooltip: (countryCode: string, countrySeries: TrendResult | null) => void
hideTooltip: () => void
updateTooltipCoordinates: (x: number, y: number) => void
worldMapCountryProps?: (
countryCode: string,
countrySeries: TrendResult | undefined
) => Omit<HTMLProps<SVGElement>, 'key'>
}

const WorldMapSVG = React.memo(
Expand All @@ -116,6 +120,7 @@ const WorldMapSVG = React.memo(
showTooltip,
hideTooltip,
updateTooltipCoordinates,
worldMapCountryProps,
},
ref
) => {
Expand All @@ -139,15 +144,23 @@ const WorldMapSVG = React.memo(
const fill = aggregatedValue
? gradateColor(BRAND_BLUE_HSL, aggregatedValue / maxAggregatedValue, SATURATION_FLOOR)
: undefined
return React.cloneElement(countryElement, {
key: countryCode,
style: { color: fill, cursor: showPersonsModal && countrySeries ? 'pointer' : undefined },
onMouseEnter: () => showTooltip(countryCode, countrySeries || null),
onMouseLeave: () => hideTooltip(),
onMouseMove: (e: MouseEvent) => {
updateTooltipCoordinates(e.clientX, e.clientY)
},
onClick: () => {

const {
onClick: propsOnClick,
style,
...props
} = worldMapCountryProps
? worldMapCountryProps(countryCode, countrySeries)
: { onClick: undefined, style: undefined }

let onClick: typeof propsOnClick
if (propsOnClick) {
onClick = (e) => {
propsOnClick(e)
hideTooltip()
}
} else if (showPersonsModal && countrySeries) {
onClick = () => {
if (showPersonsModal && countrySeries) {
if (countrySeries.persons?.url) {
openPersonsModal({
Expand All @@ -167,7 +180,19 @@ const WorldMapSVG = React.memo(
})
}
}
}
}

return React.cloneElement(countryElement, {
key: countryCode,
style: { color: fill, cursor: onClick ? 'pointer' : undefined, ...style },
onMouseEnter: () => showTooltip(countryCode, countrySeries || null),
onMouseLeave: () => hideTooltip(),
onMouseMove: (e: MouseEvent) => {
updateTooltipCoordinates(e.clientX, e.clientY)
},
onClick,
...props,
})
})}
</svg>
Expand All @@ -176,10 +201,11 @@ const WorldMapSVG = React.memo(
)
)

export function WorldMap({ showPersonsModal = true }: ChartParams): JSX.Element {
export function WorldMap({ showPersonsModal = true, context }: ChartParams): JSX.Element {
const { insightProps } = useValues(insightLogic)
const { countryCodeToSeries, maxAggregatedValue } = useValues(worldMapLogic(insightProps))
const { showTooltip, hideTooltip, updateTooltipCoordinates } = useActions(worldMapLogic(insightProps))
const renderingMetadata = context?.chartRenderingMetadata?.[ChartDisplayType.WorldMap]

const svgRef = useWorldMapTooltip(showPersonsModal)

Expand All @@ -192,6 +218,7 @@ export function WorldMap({ showPersonsModal = true }: ChartParams): JSX.Element
hideTooltip={hideTooltip}
updateTooltipCoordinates={updateTooltipCoordinates}
ref={svgRef}
worldMapCountryProps={renderingMetadata?.countryProps}
/>
)
}
16 changes: 9 additions & 7 deletions frontend/src/scenes/trends/Trends.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import { WorldMap } from 'scenes/insights/views/WorldMap'
import { BoldNumber } from 'scenes/insights/views/BoldNumber'
import { LemonButton } from '@posthog/lemon-ui'
import { trendsDataLogic } from './trendsDataLogic'
import { QueryContext } from '~/queries/types'

interface Props {
view: InsightType
context?: QueryContext
}

export function TrendInsight({ view }: Props): JSX.Element {
export function TrendInsight({ view, context }: Props): JSX.Element {
const { insightMode } = useValues(insightSceneLogic)
const { insightProps } = useValues(insightLogic)
const { insightProps, showPersonsModal } = useValues(insightLogic)

const { display, series, breakdown, loadMoreBreakdownUrl, breakdownValuesLoading } = useValues(
trendsDataLogic(insightProps)
Expand All @@ -30,10 +32,10 @@ export function TrendInsight({ view }: Props): JSX.Element {
display === ChartDisplayType.ActionsAreaGraph ||
display === ChartDisplayType.ActionsBar
) {
return <ActionsLineGraph />
return <ActionsLineGraph showPersonsModal={showPersonsModal} context={context} />
}
if (display === ChartDisplayType.BoldNumber) {
return <BoldNumber />
return <BoldNumber showPersonsModal={showPersonsModal} context={context} />
}
if (display === ChartDisplayType.ActionsTable) {
const ActionsTable = InsightsTable
Expand All @@ -47,13 +49,13 @@ export function TrendInsight({ view }: Props): JSX.Element {
)
}
if (display === ChartDisplayType.ActionsPie) {
return <ActionsPie />
return <ActionsPie showPersonsModal={showPersonsModal} context={context} />
}
if (display === ChartDisplayType.ActionsBarValue) {
return <ActionsHorizontalBar />
return <ActionsHorizontalBar showPersonsModal={showPersonsModal} context={context} />
}
if (display === ChartDisplayType.WorldMap) {
return <WorldMap />
return <WorldMap showPersonsModal={showPersonsModal} context={context} />
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { QueryContext, QueryContextColumnComponent, QueryContextColumnTitleComponent } from '~/queries/types'
import { DataTableNode, NodeKind, WebStatsBreakdown } from '~/queries/schema'
import { DataTableNode, InsightVizNode, NodeKind, WebStatsBreakdown } from '~/queries/schema'
import { UnexpectedNeverError } from 'lib/utils'
import { useActions } from 'kea'
import { webAnalyticsLogic } from 'scenes/web-analytics/webAnalyticsLogic'
import { useCallback, useMemo } from 'react'
import { Query } from '~/queries/Query/Query'
import { countryCodeToFlag, countryCodeToName } from 'scenes/insights/views/WorldMap'
import { PropertyFilterType } from '~/types'
import { ChartDisplayType } from '~/types'

const PercentageCell: QueryContextColumnComponent = ({ value }) => {
if (typeof value === 'number') {
Expand Down Expand Up @@ -171,6 +172,32 @@ export const webAnalyticsDataTableQueryContext: QueryContext = {
},
}

export const WebStatsTrendTile = ({ query }: { query: InsightVizNode }): JSX.Element => {
const { togglePropertyFilter } = useActions(webAnalyticsLogic)
const { key: worldMapPropertyName } = webStatsBreakdownToPropertyName(WebStatsBreakdown.Country)
const onWorldMapClick = useCallback(
(breakdownValue: string) => {
togglePropertyFilter(PropertyFilterType.Event, worldMapPropertyName, breakdownValue)
},
[togglePropertyFilter, worldMapPropertyName]
)

const context = useMemo((): QueryContext => {
return {
...webAnalyticsDataTableQueryContext,
chartRenderingMetadata: {
[ChartDisplayType.WorldMap]: {
countryProps: (countryCode, values) => ({
onClick: values && values.count > 0 ? () => onWorldMapClick(countryCode) : undefined,
}),
},
},
}
}, [onWorldMapClick])

return <Query query={query} readOnly={true} context={context} />
}

export const WebStatsTableTile = ({
query,
breakdownBy,
Expand Down
Loading

0 comments on commit 1f50637

Please sign in to comment.