Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(dashboards): convert dashboard templates from filters to queries #26389

Merged
merged 13 commits into from
Dec 13, 2024
Merged
135 changes: 113 additions & 22 deletions frontend/src/scenes/dashboard/newDashboardLogic.test.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,132 @@
import { NodeKind } from '~/queries/schema'

import { applyTemplate } from './newDashboardLogic'

describe('template function in newDashboardLogic', () => {
it('ignores unused variables', () => {
expect(
applyTemplate({ a: 'hello', b: 'hi' }, [
{
id: 'VARIABLE_1',
name: 'a',
default: {
event: '$pageview',
applyTemplate(
{ a: 'hello', b: 'hi' },
[
{
id: 'VARIABLE_1',
name: 'a',
default: {
event: '$pageview',
},
description: 'The description of the variable',
required: true,
type: 'event',
},
description: 'The description of the variable',
required: true,
type: 'event',
},
])
],
null
)
).toEqual({ a: 'hello', b: 'hi' })
})
it('uses identified variables', () => {
expect(
applyTemplate({ a: '{VARIABLE_1}', b: 'hi' }, [
{
id: 'VARIABLE_1',
name: 'a',
default: {
event: '$pageview',
applyTemplate(
{ a: '{VARIABLE_1}', b: 'hi' },
[
{
id: 'VARIABLE_1',
name: 'a',
default: {
event: '$pageview',
},
description: 'The description of the variable',
required: true,
type: 'event',
},
description: 'The description of the variable',
required: true,
type: 'event',
},
])
],
null
)
).toEqual({
a: {
event: '$pageview',
},
b: 'hi',
})
})

it('replaces variables in query based tiles', () => {
expect(
applyTemplate(
{ a: '{VARIABLE_1}' },
[
{
id: 'VARIABLE_1',
name: 'a',
default: {
id: '$pageview',
},
description: 'The description of the variable',
required: true,
type: 'event',
},
],
NodeKind.TrendsQuery
)
).toEqual({
a: {
event: '$pageview',
kind: 'EventsNode',
math: 'total',
},
})
})

it("removes the math property from query based tiles that don't support it", () => {
expect(
applyTemplate(
{ a: '{VARIABLE_1}' },
[
{
id: 'VARIABLE_1',
name: 'a',
default: {
id: '$pageview',
},
description: 'The description of the variable',
required: true,
type: 'event',
},
],
NodeKind.LifecycleQuery
)
).toEqual({
a: {
event: '$pageview',
kind: 'EventsNode',
},
})
})

it('removes the math property from retention insight tiles', () => {
expect(
applyTemplate(
{ a: '{VARIABLE_1}' },
[
{
id: 'VARIABLE_1',
name: 'a',
default: {
id: '$pageview',
math: 'dau' as any,
type: 'events' as any,
},
description: 'The description of the variable',
required: true,
type: 'event',
},
],
NodeKind.RetentionQuery
)
).toEqual({
a: {
id: '$pageview',
type: 'events',
},
})
})
})
43 changes: 39 additions & 4 deletions frontend/src/scenes/dashboard/newDashboardLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import api from 'lib/api'
import { DashboardRestrictionLevel } from 'lib/constants'
import { lemonToast } from 'lib/lemon-ui/LemonToast/LemonToast'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { MathAvailability } from 'scenes/insights/filters/ActionFilter/ActionFilterRow/ActionFilterRow'
import { teamLogic } from 'scenes/teamLogic'
import { urls } from 'scenes/urls'

import { dashboardsModel } from '~/models/dashboardsModel'
import { legacyEntityToNode, sanitizeRetentionEntity } from '~/queries/nodes/InsightQuery/utils/filtersToQueryNode'
import { getQueryBasedDashboard } from '~/queries/nodes/InsightViz/utils'
import { NodeKind } from '~/queries/schema'
import { isInsightVizNode } from '~/queries/utils'
import { DashboardTemplateType, DashboardTemplateVariableType, DashboardTile, DashboardType, JsonType } from '~/types'

import type { newDashboardLogicType } from './newDashboardLogicType'
Expand All @@ -35,32 +39,63 @@ export interface NewDashboardLogicProps {
}

// Currently this is a very generic recursive function incase we want to add template variables to aspects beyond events
export function applyTemplate(obj: DashboardTile | JsonType, variables: DashboardTemplateVariableType[]): JsonType {
export function applyTemplate(
obj: DashboardTile | JsonType,
variables: DashboardTemplateVariableType[],
queryKind: NodeKind | null
): JsonType {
if (typeof obj === 'string') {
if (obj.startsWith('{') && obj.endsWith('}')) {
const variableId = obj.substring(1, obj.length - 1)
const variable = variables.find((variable) => variable.id === variableId)
if (variable && variable.default) {
// added for future compatibility - at the moment we only have event variables
const isEventVariable = variable.type === 'event'

if (queryKind && isEventVariable) {
let mathAvailability = MathAvailability.None
if (queryKind === NodeKind.TrendsQuery) {
mathAvailability = MathAvailability.All
} else if (queryKind === NodeKind.StickinessQuery) {
mathAvailability = MathAvailability.ActorsOnly
} else if (queryKind === NodeKind.FunnelsQuery) {
mathAvailability = MathAvailability.FunnelsOnly
}
return (
queryKind === NodeKind.RetentionQuery
? sanitizeRetentionEntity(variable.default as any)
: legacyEntityToNode(variable.default as any, true, mathAvailability)
) as JsonType
}

return variable.default as JsonType
}
return obj
}
}
if (Array.isArray(obj)) {
return obj.map((item) => applyTemplate(item, variables))
return obj.map((item) => applyTemplate(item, variables, queryKind))
}
if (typeof obj === 'object' && obj !== null) {
const newObject: JsonType = {}
for (const [key, value] of Object.entries(obj)) {
newObject[key] = applyTemplate(value, variables)
newObject[key] = applyTemplate(value, variables, queryKind)
}
return newObject
}
return obj
}

function makeTilesUsingVariables(tiles: DashboardTile[], variables: DashboardTemplateVariableType[]): JsonType[] {
return tiles.map((tile: DashboardTile) => applyTemplate(tile, variables))
return tiles.map((tile: DashboardTile) => {
const isQueryBased = 'query' in tile && tile.query != null
const queryKind: NodeKind | null = isQueryBased
? isInsightVizNode(tile.query as any)
? (tile.query as any)?.source.kind
: (tile.query as any)?.kind
: null
return applyTemplate(tile, variables, queryKind)
})
}

export const newDashboardLogic = kea<newDashboardLogicType>([
Expand Down
3 changes: 0 additions & 3 deletions mypy-baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,6 @@ posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Item "No
posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Argument 1 to "float" has incompatible type "Any | None"; expected "str | Buffer | SupportsFloat | SupportsIndex" [arg-type]
posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Argument 1 to "clean_display" has incompatible type "Any | None"; expected "str" [arg-type]
posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Dict entry 0 has incompatible type "str": "FunnelsFilter"; expected "str": "TrendsFilter" [dict-item]
posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Dict entry 0 has incompatible type "str": "RetentionFilter"; expected "str": "TrendsFilter" [dict-item]
posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Argument 1 to "to_base_entity_dict" has incompatible type "Any | None"; expected "dict[Any, Any]" [arg-type]
posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Argument 1 to "to_base_entity_dict" has incompatible type "Any | None"; expected "dict[Any, Any]" [arg-type]
posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Dict entry 0 has incompatible type "str": "PathsFilter"; expected "str": "TrendsFilter" [dict-item]
posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Dict entry 0 has incompatible type "str": "LifecycleFilter"; expected "str": "TrendsFilter" [dict-item]
posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Dict entry 0 has incompatible type "str": "StickinessFilter"; expected "str": "TrendsFilter" [dict-item]
Expand Down
Loading
Loading