Skip to content

Commit

Permalink
Merge branch 'master' into dn-fix/error-clustering-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
daibhin committed Mar 13, 2024
2 parents 8d1fcc1 + a2c5efa commit 53e8068
Show file tree
Hide file tree
Showing 104 changed files with 9,945 additions and 2,747 deletions.
8 changes: 4 additions & 4 deletions cypress/e2e/experiments.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ describe('Experiments', () => {

// Select goal type
cy.get('[data-attr="experiment-goal-type-select"]').click()
cy.contains('Trend').should('be.visible')
cy.contains('Conversion funnel').should('be.visible')
cy.get('.Popover__content').contains('Trend').should('be.visible')
cy.get('.Popover__content').contains('Conversion funnel').should('be.visible')

// Add secondary metric
const secondaryMetricName = `Secondary metric ${Math.floor(Math.random() * 10000000)}`
Expand All @@ -65,8 +65,8 @@ describe('Experiments', () => {
.type(secondaryMetricName)
.should('have.value', secondaryMetricName)
cy.get('[data-attr="metrics-selector"]').click()
cy.contains('Trends').should('be.visible')
cy.contains('Funnels').should('be.visible')
cy.get('.Popover__content').contains('Funnels').should('be.visible')
cy.get('.Popover__content').contains('Trends').should('be.visible')
cy.get('[data-attr="create-annotation-submit"]').click()
cy.contains(secondaryMetricName).should('exist')

Expand Down
10 changes: 8 additions & 2 deletions cypress/productAnalytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ export const insight = {
const networkInterceptAlias = interceptInsightLoad(tabName)

cy.get(`[data-attr="insight-${(tabName === 'PATHS' ? 'PATH' : tabName).toLowerCase()}-tab"]`).click()
cy.wait(`@${networkInterceptAlias}`)
if (tabName !== 'FUNNELS') {
// funnel insights require two steps before making an api call
cy.wait(`@${networkInterceptAlias}`)
}
},
newInsight: (insightType: string = 'TRENDS'): void => {
const networkInterceptAlias = interceptInsightLoad(insightType)
Expand All @@ -94,7 +97,10 @@ export const insight = {
cy.get(`[data-attr-insight-type="${insightType}"]`).click()
}

cy.wait(`@${networkInterceptAlias}`)
if (insightType !== 'FUNNELS') {
// funnel insights require two steps before making an api call
cy.wait(`@${networkInterceptAlias}`)
}
},
visitInsight: (insightName: string): void => {
cy.clickNavMenu('savedinsights')
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 1 addition & 4 deletions frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1928,13 +1928,10 @@ const api = {
password: string,
schema: string
): Promise<ExternalDataPostgresSchema[]> {
const queryParams = toParams({ host, port, dbname, user, password, schema })

return await new ApiRequest()
.externalDataSources()
.withAction('database_schema')
.withQueryString(queryParams)
.get()
.create({ data: { host, port, dbname, user, password, schema } })
},
},

Expand Down
86 changes: 82 additions & 4 deletions frontend/src/lib/components/PayGateMini/PayGateMini.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { IconInfo } from '@posthog/icons'
import { Link, Tooltip } from '@posthog/lemon-ui'
import { LemonButton, Link, Tooltip } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { FEATURE_FLAGS } from 'lib/constants'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { lowercaseFirstLetter } from 'lib/utils'
import { billingLogic } from 'scenes/billing/billingLogic'
import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic'
Expand All @@ -12,6 +13,8 @@ import { userLogic } from 'scenes/userLogic'

import { AvailableFeature } from '~/types'

import { PayGateMiniButton } from './PayGateMiniButton'

export interface PayGateMiniProps {
feature: AvailableFeature
currentUsage?: number
Expand All @@ -38,7 +41,8 @@ export function PayGateMini({
}: PayGateMiniProps): JSX.Element | null {
const { preflight, isCloudOrDev } = useValues(preflightLogic)
const { hasAvailableFeature, availableFeature } = useValues(userLogic)
const { billing } = useValues(billingLogic)
const { billing, billingLoading } = useValues(billingLogic)
const { featureFlags } = useValues(featureFlagLogic)
const { hideUpgradeModal } = useActions(sceneLogic)

const product = billing?.products.find((product) => product.features?.some((f) => f.key === feature))
Expand All @@ -59,11 +63,85 @@ export function PayGateMini({
}
}

if (billingLoading) {
return null
}

if (gateVariant && preflight?.instance_preferences?.disable_paid_fs) {
return null // Don't show anything if paid features are explicitly disabled
}

return gateVariant && product && featureInfo ? (
return featureFlags[FEATURE_FLAGS.SUBSCRIBE_FROM_PAYGATE] === 'test' ? (
gateVariant && product && featureInfo ? (
<div
className={clsx(
className,
background && 'bg-side border border-border',
'PayGateMini rounded flex flex-col items-center p-4 text-center'
)}
>
<div className="flex text-4xl text-warning">{getProductIcon(product.name, featureInfo.icon_key)}</div>
<h3>{featureInfo.name}</h3>
{featureDetailsWithLimit?.limit && gateVariant !== 'move-to-cloud' ? (
<div>
<p>
You've reached your usage limit for{' '}
<Tooltip title={featureInfo.description}>
<span>
<b>{featureInfo.name}</b>
<IconInfo className="ml-0.5 text-muted" />
</span>
</Tooltip>
.
</p>
<p className="border border-border bg-side rounded p-4">
<b>Your current plan limit:</b>{' '}
<span>
{featureDetailsWithLimit.limit} {featureDetailsWithLimit.unit}
</span>
</p>
<p>
Please upgrade your <b>{product.name}</b> plan to create more {featureInfo.name}
</p>
</div>
) : (
<>
<p>{featureInfo.description}</p>
<p>
{gateVariant === 'move-to-cloud' ? (
<>This feature is only available on PostHog Cloud.</>
) : (
<>
Upgrade your <b>{product?.name}</b> plan to use this feature.
</>
)}
</p>
</>
)}
{isGrandfathered && (
<div className="flex gap-x-2 bg-side p-4 rounded text-left mb-4">
<IconInfo className="text-muted text-2xl" />
<p className="text-muted mb-0">
Your plan does not include this feature, but previously set settings may remain. Please
upgrade your plan to regain access.
</p>
</div>
)}
{featureInfo.docsUrl && (
<div className="mb-4">
<>
<Link to={featureInfo.docsUrl} target="_blank">
Learn more in PostHog Docs.
</Link>
</>
</div>
)}
<PayGateMiniButton product={product} featureInfo={featureInfo} gateVariant={gateVariant} />
</div>
) : (
<div className={className}>{children}</div>
)
) : gateVariant && product && featureInfo ? (
<div
className={clsx(
className,
Expand Down
56 changes: 56 additions & 0 deletions frontend/src/lib/components/PayGateMini/PayGateMiniButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { LemonButton } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { billingProductLogic } from 'scenes/billing/billingProductLogic'
import { PlanComparisonModal } from 'scenes/billing/PlanComparison'
import { urls } from 'scenes/urls'

import { BillingProductV2Type, BillingV2FeatureType } from '~/types'

export const PayGateMiniButton = ({
product,
gateVariant,
featureInfo,
onClick,
}: {
product: BillingProductV2Type
featureInfo: BillingV2FeatureType
gateVariant: 'add-card' | 'contact-sales' | 'move-to-cloud'
onClick?: () => void
}): JSX.Element => {
const { isPlanComparisonModalOpen } = useValues(billingProductLogic({ product }))
const { toggleIsPlanComparisonModalOpen } = useActions(billingProductLogic({ product }))

return (
<>
<LemonButton
to={
gateVariant === 'contact-sales'
? `mailto:[email protected]?subject=Inquiring about ${featureInfo.name}`
: gateVariant === 'move-to-cloud'
? urls.moveToPostHogCloud()
: undefined
}
type="primary"
center
onClick={() => {
if (gateVariant === 'add-card') {
toggleIsPlanComparisonModalOpen(featureInfo.key)
}
onClick?.()
}}
>
{gateVariant === 'add-card'
? `Compare plans`
: gateVariant === 'contact-sales'
? 'Contact sales'
: 'Move to PostHog Cloud'}
</LemonButton>
<PlanComparisonModal
key={`modal-${featureInfo.key}`}
product={product}
modalOpen={isPlanComparisonModalOpen}
onClose={() => toggleIsPlanComparisonModalOpen()}
/>
</>
)
}
3 changes: 3 additions & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export const FEATURE_FLAGS = {
SESSION_REPLAY_LINKED_VARIANTS: 'session-replay-linked-variants', // owner: #team-replay
REPLAY_ERROR_CLUSTERING: 'session-replay-error-clustering', // owner: #team-replay
AUDIT_LOGS_ACCESS: 'audit-logs-access', // owner: #team-growth
SUBSCRIBE_FROM_PAYGATE: 'subscribe-from-paygate', // owner: #team-growth
} as const
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]

Expand Down Expand Up @@ -287,3 +288,5 @@ export const SESSION_REPLAY_MINIMUM_DURATION_OPTIONS: LemonSelectOptions<number
value: 15000,
},
]

export const UNSUBSCRIBE_SURVEY_ID = '018b6e13-590c-0000-decb-c727a2b3f462'
24 changes: 23 additions & 1 deletion frontend/src/queries/nodes/DataNode/dataNodeLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,14 @@ import {
PersonsNode,
QueryTiming,
} from '~/queries/schema'
import { isActorsQuery, isEventsQuery, isInsightActorsQuery, isInsightQueryNode, isPersonsNode } from '~/queries/utils'
import {
isActorsQuery,
isEventsQuery,
isFunnelsQuery,
isInsightActorsQuery,
isInsightQueryNode,
isPersonsNode,
} from '~/queries/utils'

import type { dataNodeLogicType } from './dataNodeLogicType'

Expand All @@ -65,6 +72,7 @@ const LOAD_MORE_ROWS_LIMIT = 10000

const concurrencyController = new ConcurrencyController(Infinity)

/** Compares two queries for semantic equality to prevent double-fetching of data. */
const queryEqual = (a: DataNode, b: DataNode): boolean => {
if (isInsightQueryNode(a) && isInsightQueryNode(b)) {
return compareInsightQuery(a, b, true)
Expand All @@ -73,6 +81,16 @@ const queryEqual = (a: DataNode, b: DataNode): boolean => {
}
}

/** Tests wether a query is valid to prevent unnecessary requests. */
const queryValid = (q: DataNode): boolean => {
if (isFunnelsQuery(q)) {
// funnels require at least two steps
return q.series.length >= 2
} else {
return true
}
}

export const dataNodeLogic = kea<dataNodeLogicType>([
path(['queries', 'nodes', 'dataNodeLogic']),
key((props) => props.key),
Expand Down Expand Up @@ -152,6 +170,10 @@ export const dataNodeLogic = kea<dataNodeLogicType>([
return null
}

if (!queryValid(props.query)) {
return null
}

actions.abortAnyRunningQuery()
const abortController = new AbortController()
cache.abortController = abortController
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/queries/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,12 @@ export async function query<N extends DataNode = DataNode>(
res2 = res2[0]?.people.map((n: any) => n.id)
res1 = res1.map((n: any) => n[0].id)
// Sort, since the order of the results is not guaranteed
res1.sort()
res2.sort()
res1.sort((a: any, b: any) =>
(a.breakdown_value ?? a.label ?? a).localeCompare(b.breakdown_value ?? b.label ?? b)
)
res2.sort((a: any, b: any) =>
(a.breakdown_value ?? a.label ?? a).localeCompare(b.breakdown_value ?? b.label ?? b)
)
}

const getTimingDiff = (): undefined | { diff: number; legacy: number; hogql: number } => {
Expand Down
Loading

0 comments on commit 53e8068

Please sign in to comment.