0
0
mirror of https://github.com/PostHog/posthog.git synced 2024-11-21 13:39:22 +01:00

feat(experiments): add variant screenshots (#25397)

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Juraj Majerik 2024-10-07 17:04:39 +02:00 committed by GitHub
parent d691a1a92b
commit fb92840378
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 145 additions and 3 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 188 KiB

View File

@ -8,6 +8,7 @@ import { MultivariateFlagVariant } from '~/types'
import { experimentLogic } from '../experimentLogic'
import { VariantTag } from './components'
import { VariantScreenshot } from './VariantScreenshot'
export function DistributionTable(): JSX.Element {
const { experimentId, experiment, experimentResults } = useValues(experimentLogic)
@ -32,6 +33,18 @@ export function DistributionTable(): JSX.Element {
return <div>{`${item.rollout_percentage}%`}</div>
},
},
{
className: 'w-1/3',
key: 'variant_screenshot',
title: 'Screenshot',
render: function Key(_, item): JSX.Element {
return (
<div className="my-2">
<VariantScreenshot variantKey={item.key} rolloutPercentage={item.rollout_percentage} />
</div>
)
},
},
]
return (

View File

@ -0,0 +1,114 @@
import { IconUpload, IconX } from '@posthog/icons'
import { LemonButton, LemonDivider, LemonFileInput, LemonModal, LemonSkeleton, lemonToast } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { useUploadFiles } from 'lib/hooks/useUploadFiles'
import { useState } from 'react'
import { experimentLogic } from '../experimentLogic'
import { VariantTag } from './components'
export function VariantScreenshot({
variantKey,
rolloutPercentage,
}: {
variantKey: string
rolloutPercentage: number
}): JSX.Element {
const { experiment } = useValues(experimentLogic)
const { updateExperimentVariantImages } = useActions(experimentLogic)
const [mediaId, setMediaId] = useState(experiment.parameters?.variant_screenshot_media_ids?.[variantKey] || null)
const [isLoadingImage, setIsLoadingImage] = useState(true)
const [isModalOpen, setIsModalOpen] = useState(false)
const { setFilesToUpload, filesToUpload, uploading } = useUploadFiles({
onUpload: (_, __, id) => {
setMediaId(id)
if (id) {
const updatedVariantImages = {
...experiment.parameters?.variant_screenshot_media_ids,
[variantKey]: id,
}
updateExperimentVariantImages(updatedVariantImages)
}
},
onError: (detail) => {
lemonToast.error(`Error uploading image: ${detail}`)
},
})
return (
<div className="space-y-4">
{!mediaId ? (
<LemonFileInput
accept="image/*"
multiple={false}
onChange={setFilesToUpload}
loading={uploading}
value={filesToUpload}
callToAction={
<>
<IconUpload className="text-2xl" />
<span>Upload a preview of this variant's UI</span>
</>
}
/>
) : (
<div className="relative">
<div className="text-muted inline-flex flow-row items-center gap-1 cursor-pointer">
<div onClick={() => setIsModalOpen(true)} className="cursor-zoom-in relative">
<div className="relative flex overflow-hidden select-none size-20 w-full rounded before:absolute before:inset-0 before:border before:rounded">
{isLoadingImage && <LemonSkeleton className="absolute inset-0" />}
<img
className="size-full object-cover"
src={mediaId.startsWith('data:') ? mediaId : `/uploaded_media/${mediaId}`}
onError={() => setIsLoadingImage(false)}
onLoad={() => setIsLoadingImage(false)}
/>
</div>
<div className="absolute -inset-2 group">
<LemonButton
icon={<IconX />}
onClick={(e) => {
e.stopPropagation()
setMediaId(null)
const updatedVariantImages = {
...experiment.parameters?.variant_screenshot_media_ids,
}
delete updatedVariantImages[variantKey]
updateExperimentVariantImages(updatedVariantImages)
}}
size="small"
tooltip="Remove"
tooltipPlacement="right"
noPadding
className="group-hover:flex hidden absolute right-0 top-0"
/>
</div>
</div>
</div>
</div>
)}
<LemonModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
title={
<div className="flex items-center gap-2">
<span>Screenshot</span>
<LemonDivider className="my-0 mx-1" vertical />
<VariantTag experimentId={experiment.id} variantKey={variantKey} />
{rolloutPercentage !== undefined && (
<span className="text-muted text-sm">({rolloutPercentage}% rollout)</span>
)}
</div>
}
>
<img
src={mediaId?.startsWith('data:') ? mediaId : `/uploaded_media/${mediaId}`}
alt={`Screenshot: ${variantKey}`}
className="max-w-full max-h-[80vh] overflow-auto"
/>
</LemonModal>
</div>
)
}

View File

@ -169,6 +169,7 @@ export const experimentLogic = kea<experimentLogicType>([
closeShipVariantModal: true,
setCurrentFormStep: (stepIndex: number) => ({ stepIndex }),
moveToNextFormStep: true,
updateExperimentVariantImages: (variantPreviewMediaIds: Record<string, string>) => ({ variantPreviewMediaIds }),
}),
reducers({
experiment: [
@ -587,15 +588,12 @@ export const experimentLogic = kea<experimentLogicType>([
},
updateExperimentSuccess: async ({ experiment }) => {
actions.updateExperiments(experiment)
if (values.changingGoalMetric) {
actions.loadExperimentResults()
}
if (values.changingSecondaryMetrics) {
actions.loadSecondaryMetricResults()
}
if (values.experiment?.start_date) {
actions.loadExperimentResults()
}
@ -717,6 +715,22 @@ export const experimentLogic = kea<experimentLogicType>([
lemonToast.error(error)
actions.closeShipVariantModal()
},
updateExperimentVariantImages: async ({ variantPreviewMediaIds }) => {
try {
const updatedParameters = {
...values.experiment.parameters,
variant_screenshot_media_ids: variantPreviewMediaIds,
}
await api.update(`api/projects/${values.currentTeamId}/experiments/${values.experimentId}`, {
parameters: updatedParameters,
})
actions.setExperiment({
parameters: updatedParameters,
})
} catch (error) {
lemonToast.error('Failed to update experiment variant images')
}
},
})),
loaders(({ actions, props, values }) => ({
experiment: {

View File

@ -3219,6 +3219,7 @@ export interface Experiment {
feature_flag_variants: MultivariateFlagVariant[]
custom_exposure_filter?: FilterType
aggregation_group_type_index?: integer
variant_screenshot_media_ids?: Record<string, string>
}
start_date?: string | null
end_date?: string | null