Skip to content

Commit

Permalink
Merge branch 'master' into dw-scroll-tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
EDsCODE authored Dec 13, 2024
2 parents df697e3 + 646de76 commit ad8a4d4
Show file tree
Hide file tree
Showing 58 changed files with 4,519 additions and 2,249 deletions.
657 changes: 537 additions & 120 deletions ee/clickhouse/views/test/__snapshots__/test_clickhouse_experiments.ambr

Large diffs are not rendered by default.

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions frontend/src/lib/integrations/IntegrationScopesWarning.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import api from 'lib/api'
import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
import { Link } from 'lib/lemon-ui/Link'
import { useMemo } from 'react'

import { HogFunctionInputSchemaType, IntegrationType } from '~/types'

export function IntegrationScopesWarning({
integration,
schema,
}: {
integration: IntegrationType
schema?: HogFunctionInputSchemaType
}): JSX.Element {
const getScopes = useMemo((): string[] => {
const scopes: any[] = []
const possibleScopeLocation = [integration.config.scope, integration.config.scopes]

possibleScopeLocation.map((scope) => {
if (typeof scope === 'string') {
scopes.push(scope.split(' '))
scopes.push(scope.split(','))
}
if (typeof scope === 'object') {
scopes.push(scope)
}
})
return scopes
.filter((scope: any) => typeof scope === 'object')
.reduce((a, b) => (a.length > b.length ? a : b), [])
}, [integration.config])

const requiredScopes = schema?.requiredScopes?.split(' ') || []
const missingScopes = requiredScopes.filter((scope: string) => !getScopes.includes(scope))

if (missingScopes.length === 0 || getScopes.length === 0) {
return <></>
}
return (
<div className="p-2">
<LemonBanner
type="error"
action={{
children: 'Reconnect',
disableClientSideRouting: true,
to: api.integrations.authorizeUrl({
kind: integration.kind,
next: window.location.pathname,
}),
}}
>
<span>Required scopes are missing: [{missingScopes.join(', ')}].</span>
{integration.kind === 'hubspot' ? (
<span>
Note that some features may not be available on your current HubSpot plan. Check out{' '}
<Link to="https://developers.hubspot.com/beta-docs/guides/apps/authentication/scopes">
this page
</Link>{' '}
for more details.
</span>
) : null}
</LemonBanner>
</div>
)
}
9 changes: 7 additions & 2 deletions frontend/src/lib/integrations/IntegrationView.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { LemonBanner } from '@posthog/lemon-ui'
import api from 'lib/api'
import { UserActivityIndicator } from 'lib/components/UserActivityIndicator/UserActivityIndicator'
import { IntegrationScopesWarning } from 'lib/integrations/IntegrationScopesWarning'

import { IntegrationType } from '~/types'
import { HogFunctionInputSchemaType, IntegrationType } from '~/types'

export function IntegrationView({
integration,
suffix,
schema,
}: {
integration: IntegrationType
suffix?: JSX.Element
schema?: HogFunctionInputSchemaType
}): JSX.Element {
const errors = (integration.errors && integration.errors?.split(',')) || []

Expand All @@ -36,7 +39,7 @@ export function IntegrationView({
{suffix}
</div>

{errors.length > 0 && (
{errors.length > 0 ? (
<div className="p-2">
<LemonBanner
type="error"
Expand All @@ -54,6 +57,8 @@ export function IntegrationView({
: `There was an error with this integration: ${errors[0]}`}
</LemonBanner>
</div>
) : (
<IntegrationScopesWarning integration={integration} schema={schema} />
)}
</div>
)
Expand Down
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
26 changes: 17 additions & 9 deletions frontend/src/scenes/experiments/ExperimentView/Goal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,12 @@ export function Goal(): JSX.Element {
const [isModalOpen, setIsModalOpen] = useState(false)
const metricType = getMetricType(0)

// :FLAG: CLEAN UP AFTER MIGRATION
const isDataWarehouseMetric =
featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL] &&
metricType === InsightType.TRENDS &&
(experiment.metrics[0] as ExperimentTrendsQuery).count_query?.series[0].kind === NodeKind.DataWarehouseNode

return (
<div>
<div>
Expand Down Expand Up @@ -322,16 +328,18 @@ export function Goal(): JSX.Element {
Change goal
</LemonButton>
</div>
{metricType === InsightType.TRENDS && !experimentMathAggregationForTrends() && (
<>
<LemonDivider className="" vertical />
<div className="">
<div className="mt-auto ml-auto">
<ExposureMetric experimentId={experimentId} />
{metricType === InsightType.TRENDS &&
!experimentMathAggregationForTrends() &&
!isDataWarehouseMetric && (
<>
<LemonDivider className="" vertical />
<div className="">
<div className="mt-auto ml-auto">
<ExposureMetric experimentId={experimentId} />
</div>
</div>
</div>
</>
)}
</>
)}
</div>
)}
<PrimaryMetricModal
Expand Down
Loading

0 comments on commit ad8a4d4

Please sign in to comment.