Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/PostHog/posthog into expe…
Browse files Browse the repository at this point in the history
…riments-saved-metrics
  • Loading branch information
jurajmajerik committed Dec 25, 2024
2 parents 9fc4b53 + 34a4f6b commit c553266
Show file tree
Hide file tree
Showing 73 changed files with 2,100 additions and 1,802 deletions.
2 changes: 1 addition & 1 deletion .run/PostHog.run.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="PostHog" type="Python.DjangoServer" factoryName="Django server">
<module name="posthog" />
<option name="ENV_FILES" value="" />
<option name="ENV_FILES" value="$PROJECT_DIR$/.env" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
Expand Down
85 changes: 0 additions & 85 deletions cypress/e2e/experiments.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,89 +43,4 @@ describe('Experiments', () => {
// Save experiment
cy.get('[data-attr="save-experiment"]').first().click()
})

const createExperimentInNewUi = (): void => {
cy.visit('/experiments')

// Name, flag key, description
cy.get('[data-attr=create-experiment]').first().click()
cy.get('[data-attr=experiment-name]').click().type(`${experimentName}`).should('have.value', experimentName)
cy.get('[data-attr=experiment-feature-flag-key]')
.click()
.type(`${featureFlagKey}`)
.should('have.value', featureFlagKey)
cy.get('[data-attr=experiment-description]')
.click()
.type('This is the description of the experiment')
.should('have.value', 'This is the description of the experiment')

// Edit variants
cy.get('[data-attr="add-test-variant"]').click()
cy.get('input[data-attr="experiment-variant-key"][data-key-index="1"]')
.clear()
.type('test-variant-1')
.should('have.value', 'test-variant-1')
cy.get('input[data-attr="experiment-variant-key"][data-key-index="2"]')
.clear()
.type('test-variant-2')
.should('have.value', 'test-variant-2')

// Save experiment
cy.get('[data-attr="save-experiment"]').first().click()

// Set the experiment goal once the experiment is drafted
cy.get('[data-attr="add-experiment-goal"]').click()

// Wait for the goal modal to open and click the confirmation button
cy.get('.LemonModal__layout').should('be.visible')
cy.contains('Change experiment goal').should('be.visible')
cy.get('.LemonModal__content').contains('button', 'Add funnel step').click()
cy.get('.LemonModal__footer').contains('button', 'Save').click()
}

it('create, launch and stop experiment with new ui', () => {
createExperimentInNewUi()
cy.get('[data-attr="experiment-status"]').contains('draft').should('be.visible')

cy.get('[data-attr="experiment-creation-date"]').contains('a few seconds ago').should('be.visible')
cy.get('[data-attr="experiment-start-date"]').should('not.exist')

cy.wait(1000)
cy.get('[data-attr="launch-experiment"]').first().click()
cy.get('[data-attr="experiment-creation-date"]').should('not.exist')
cy.get('[data-attr="experiment-start-date"]').contains('a few seconds ago').should('be.visible')

cy.get('[data-attr="stop-experiment"]').first().click()
// Wait for the dialog to appear and click the confirmation button
cy.get('.LemonModal__layout').should('be.visible')
cy.contains('Stop this experiment?').should('be.visible')
cy.get('.LemonModal__footer').contains('button', 'Stop').click()
// Wait for the dialog to disappear
cy.get('[data-attr="experiment-creation-date"]').should('not.exist')
cy.get('[data-attr="experiment-start-date"]').contains('a few seconds ago').should('be.visible')
cy.get('[data-attr="experiment-end-date"]').contains('a few seconds ago').should('be.visible')
})

it('move start date', () => {
createExperimentInNewUi()

cy.wait(1000)
cy.get('[data-attr="launch-experiment"]').first().click()

cy.get('[data-attr="move-experiment-start-date"]').first().click()
cy.get('[data-attr="experiment-start-date-picker"]').should('exist')
cy.get('[data-attr="lemon-calendar-month-previous"]').first().click()
cy.get('[data-attr="lemon-calendar-day"]').first().click()
cy.get('[data-attr="lemon-calendar-select-apply"]').first().click()
cy.get('[data-attr="experiment-start-date"]')
.contains(/months? ago/)
.should('be.visible')

cy.reload()

// Check that the start date persists
cy.get('[data-attr="experiment-start-date"]')
.contains(/months? ago/)
.should('be.visible')
})
})
Binary file modified frontend/__snapshots__/lemon-ui-lemon-input--search--dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/lemon-ui-lemon-input--search--light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/services/snapchat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 26 additions & 1 deletion frontend/src/lib/components/Alerts/alertFormLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ import { AlertType, AlertTypeWrite } from './types'

export type AlertFormType = Pick<
AlertType,
'name' | 'enabled' | 'created_at' | 'threshold' | 'condition' | 'subscribed_users' | 'checks' | 'config'
| 'name'
| 'enabled'
| 'created_at'
| 'calculation_interval'
| 'threshold'
| 'condition'
| 'subscribed_users'
| 'checks'
| 'config'
| 'skip_weekend'
> & {
id?: AlertType['id']
created_by?: AlertType['created_by'] | null
Expand Down Expand Up @@ -48,6 +57,7 @@ export const alertFormLogic = kea<alertFormLogicType>([
config: {
type: 'TrendsAlertConfig',
series_index: 0,
check_ongoing_interval: false,
},
threshold: { configuration: { type: InsightThresholdType.ABSOLUTE, bounds: {} } },
condition: {
Expand All @@ -56,6 +66,7 @@ export const alertFormLogic = kea<alertFormLogicType>([
subscribed_users: [],
checks: [],
calculation_interval: AlertCalculationInterval.DAILY,
skip_weekend: false,
insight: props.insightId,
} as AlertFormType),
errors: ({ name }) => ({
Expand All @@ -66,6 +77,20 @@ export const alertFormLogic = kea<alertFormLogicType>([
...alert,
subscribed_users: alert.subscribed_users?.map(({ id }) => id),
insight: props.insightId,
// can only skip weekends for hourly/daily alerts
skip_weekend:
(alert.calculation_interval === AlertCalculationInterval.DAILY ||
alert.calculation_interval === AlertCalculationInterval.HOURLY) &&
alert.skip_weekend,
// can only check ongoing interval for absolute value/increase alerts with upper threshold
config: {
...alert.config,
check_ongoing_interval:
(alert.condition.type === AlertConditionType.ABSOLUTE_VALUE ||
alert.condition.type === AlertConditionType.RELATIVE_INCREASE) &&
alert.threshold.configuration.bounds?.upper != null &&
alert.config.check_ongoing_interval,
},
}

// absolute value alert can only have absolute threshold
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/components/Alerts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface AlertTypeBase {
enabled: boolean
insight: QueryBasedInsightModel
config: AlertConfig
skip_weekend?: boolean
}

export interface AlertTypeWrite extends Omit<AlertTypeBase, 'insight'> {
Expand Down
55 changes: 55 additions & 0 deletions frontend/src/lib/components/Alerts/views/EditAlertModal.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { IconInfo } from '@posthog/icons'
import {
LemonBanner,
LemonCheckbox,
LemonInput,
LemonSegmentedButton,
LemonSelect,
SpinnerOverlay,
Tooltip,
} from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { Form, Group } from 'kea-forms'
Expand Down Expand Up @@ -97,6 +99,11 @@ export function EditAlertModal({
const { alertSeries, isNonTimeSeriesDisplay, isBreakdownValid, formula } = useValues(trendsLogic)

const creatingNewAlert = alertForm.id === undefined
// can only check ongoing interval for absolute value/increase alerts with upper threshold
const can_check_ongoing_interval =
(alertForm?.condition.type === AlertConditionType.ABSOLUTE_VALUE ||
alertForm?.condition.type === AlertConditionType.RELATIVE_INCREASE) &&
alertForm.threshold.configuration.bounds?.upper != null

return (
<LemonModal onClose={onClose} isOpen={isOpen} width={600} simple title="">
Expand Down Expand Up @@ -320,7 +327,55 @@ export function EditAlertModal({
</div>
</div>
</div>

<div className="space-y-2">
<h3 className="text-muted-alt">Advanced</h3>
<Group name={['config']}>
<div className="flex gap-1">
<LemonField name="check_ongoing_interval">
<LemonCheckbox
checked={
can_check_ongoing_interval &&
alertForm?.config.check_ongoing_interval
}
data-attr="alertForm-check-ongoing-interval"
fullWidth
label="Check ongoing period"
disabledReason={
!can_check_ongoing_interval &&
'Can only alert for ongoing period when checking for absolute value/increase above threshold'
}
/>
</LemonField>
<Tooltip
title={`Checks the insight value for the on going period (current week/month) that hasn't yet completed. Use this if you want to be alerted right away when the insight value rises/increases above threshold`}
placement="right"
delayMs={0}
>
<IconInfo />
</Tooltip>
</div>
</Group>
<LemonField name="skip_weekend">
<LemonCheckbox
checked={
(alertForm?.calculation_interval === AlertCalculationInterval.DAILY ||
alertForm?.calculation_interval === AlertCalculationInterval.HOURLY) &&
alertForm?.skip_weekend
}
data-attr="alertForm-skip-weekend"
fullWidth
label="Skip checking on weekends"
disabledReason={
alertForm?.calculation_interval !== AlertCalculationInterval.DAILY &&
alertForm?.calculation_interval !== AlertCalculationInterval.HOURLY &&
'Can only skip weekend checking for hourly/daily alerts'
}
/>
</LemonField>
</div>
</div>

{alert && <AlertStateTable alert={alert} />}
</LemonModal.Content>

Expand Down
1 change: 0 additions & 1 deletion frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ export const WEBHOOK_SERVICES: Record<string, string> = {
export const FEATURE_FLAGS = {
// Experiments / beta features
FUNNELS_CUE_OPT_OUT: 'funnels-cue-opt-out-7301', // owner: @neilkakkar
KAFKA_INSPECTOR: 'kafka-inspector', // owner: @yakkomajuri
HISTORICAL_EXPORTS_V2: 'historical-exports-v2', // owner @macobo
INGESTION_WARNINGS_ENABLED: 'ingestion-warnings-enabled', // owner: @tiina303
SESSION_RESET_ON_LOAD: 'session-reset-on-load', // owner: @benjackwhite
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lib/integrations/integrationsLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import IconGoogleCloudStorage from 'public/services/google-cloud-storage.png'
import IconHubspot from 'public/services/hubspot.png'
import IconSalesforce from 'public/services/salesforce.png'
import IconSlack from 'public/services/slack.png'
import IconSnapchat from 'public/services/snapchat.png'
import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic'
import { urls } from 'scenes/urls'

Expand All @@ -24,6 +25,7 @@ const ICONS: Record<IntegrationKind, any> = {
'google-pubsub': IconGoogleCloud,
'google-cloud-storage': IconGoogleCloudStorage,
'google-ads': IconGoogleAds,
snapchat: IconSnapchat,
}

export const integrationsLogic = kea<integrationsLogicType>([
Expand Down
1 change: 0 additions & 1 deletion frontend/src/lib/lemon-ui/LemonInput/LemonInput.scss
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@
&.LemonInput--type-search {
// NOTE Design: Search inputs are given a specific small width
max-width: 240px;
border-radius: 0;
}

&.LemonInput--type-number {
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12519,6 +12519,9 @@
"TrendsAlertConfig": {
"additionalProperties": false,
"properties": {
"check_ongoing_interval": {
"type": "boolean"
},
"series_index": {
"type": "integer"
},
Expand Down
1 change: 1 addition & 0 deletions frontend/src/queries/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2379,6 +2379,7 @@ export enum AlertCalculationInterval {
export interface TrendsAlertConfig {
type: 'TrendsAlertConfig'
series_index: integer
check_ongoing_interval?: boolean
}

export interface HogCompileResponse {
Expand Down
25 changes: 19 additions & 6 deletions frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { WebExperimentImplementationDetails } from 'scenes/experiments/WebExperi

import { ExperimentImplementationDetails } from '../ExperimentImplementationDetails'
import { experimentLogic } from '../experimentLogic'
import { PrimaryMetricModal } from '../Metrics/PrimaryMetricModal'
import { MetricModal } from '../Metrics/MetricModal'
import { MetricsView } from '../MetricsView/MetricsView'
import {
ExperimentLoadingAnimation,
Expand Down Expand Up @@ -52,7 +52,11 @@ const ResultsTab = (): JSX.Element => {
)}
</>
)}
<SecondaryMetricsTable experimentId={experiment.id} />
{featureFlags[FEATURE_FLAGS.EXPERIMENTS_MULTIPLE_METRICS] ? (
<MetricsView isSecondary={true} />
) : (
<SecondaryMetricsTable experimentId={experiment.id} />
)}
</div>
)
}
Expand All @@ -70,8 +74,15 @@ const VariantsTab = (): JSX.Element => {
}

export function ExperimentView(): JSX.Element {
const { experimentLoading, metricResultsLoading, experimentId, metricResults, tabKey, featureFlags } =
useValues(experimentLogic)
const {
experimentLoading,
metricResultsLoading,
secondaryMetricResultsLoading,
experimentId,
metricResults,
tabKey,
featureFlags,
} = useValues(experimentLogic)

const { setTabKey } = useActions(experimentLogic)
const result = metricResults?.[0]
Expand All @@ -86,7 +97,7 @@ export function ExperimentView(): JSX.Element {
) : (
<>
<Info />
{metricResultsLoading ? (
{metricResultsLoading || secondaryMetricResultsLoading ? (
<ExperimentLoadingAnimation />
) : (
<>
Expand Down Expand Up @@ -130,7 +141,9 @@ export function ExperimentView(): JSX.Element {
/>
</>
)}
<PrimaryMetricModal experimentId={experimentId} />
<MetricModal experimentId={experimentId} isSecondary={true} />
<MetricModal experimentId={experimentId} isSecondary={false} />

<DistributionModal experimentId={experimentId} />
<ReleaseConditionsModal experimentId={experimentId} />
</>
Expand Down
19 changes: 8 additions & 11 deletions frontend/src/scenes/experiments/ExperimentView/Goal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export function MetricDisplayOld({ filters }: { filters?: FilterType }): JSX.Ele

export function ExposureMetric({ experimentId }: { experimentId: Experiment['id'] }): JSX.Element {
const { experiment, featureFlags } = useValues(experimentLogic({ experimentId }))
const { updateExperimentExposure, loadExperiment, setExperiment } = useActions(experimentLogic({ experimentId }))
const { updateExperimentGoal, loadExperiment, setExperiment } = useActions(experimentLogic({ experimentId }))
const [isModalOpen, setIsModalOpen] = useState(false)

const metricIdx = 0
Expand Down Expand Up @@ -213,16 +213,13 @@ export function ExposureMetric({ experimentId }: { experimentId: Experiment['id'
status="danger"
size="xsmall"
onClick={() => {
// :FLAG: CLEAN UP AFTER MIGRATION
if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
setExperiment({
...experiment,
metrics: experiment.metrics.map((metric, idx) =>
idx === metricIdx ? { ...metric, exposure_query: undefined } : metric
),
})
}
updateExperimentExposure(null)
setExperiment({
...experiment,
metrics: experiment.metrics.map((metric, idx) =>
idx === metricIdx ? { ...metric, exposure_query: undefined } : metric
),
})
updateExperimentGoal()
}}
>
Reset
Expand Down
Loading

0 comments on commit c553266

Please sign in to comment.