mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #19448 from overleaf/jdt-experiments-max-subscribers
Enforce a maximum participant cap on experiments GitOrigin-RevId: 1d9263cd34a3d0c831c0ed43867bb4e6430eb06c
This commit is contained in:
parent
01b7896717
commit
837fea03b9
6 changed files with 64 additions and 21 deletions
|
@ -12,7 +12,7 @@ async function getMetric(key, defaultValue = 0) {
|
|||
if (!metric) {
|
||||
return defaultValue
|
||||
}
|
||||
return metric
|
||||
return metric.value
|
||||
}
|
||||
|
||||
async function setMetric(key, value) {
|
||||
|
|
|
@ -406,6 +406,7 @@
|
|||
"example_project": "",
|
||||
"existing_plan_active_until_term_end": "",
|
||||
"expand": "",
|
||||
"experiment_full": "",
|
||||
"expired": "",
|
||||
"expired_confirmation_code": "",
|
||||
"expires": "",
|
||||
|
@ -1401,6 +1402,7 @@
|
|||
"this_action_cannot_be_undone": "",
|
||||
"this_address_will_be_shown_on_the_invoice": "",
|
||||
"this_could_be_because_we_cant_support_some_elements_of_the_table": "",
|
||||
"this_experiment_isnt_accepting_new_participants": "",
|
||||
"this_field_is_required": "",
|
||||
"this_grants_access_to_features_2": "",
|
||||
"this_is_a_labs_experiment": "",
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { ReactNode, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/shared/components/badge'
|
||||
import Tooltip from '@/shared/components/tooltip'
|
||||
import { postJSON } from '@/infrastructure/fetch-json'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
export type UserFeatures = {
|
||||
[key: string]: boolean
|
||||
}
|
||||
|
||||
type IntegrationLinkingWidgetProps = {
|
||||
logo: ReactNode
|
||||
title: string
|
||||
|
@ -17,6 +14,8 @@ type IntegrationLinkingWidgetProps = {
|
|||
labsEnabled?: boolean
|
||||
experimentName: string
|
||||
setErrorMessage: (message: string) => void
|
||||
optedIn: boolean
|
||||
setOptedIn: (optedIn: boolean) => void
|
||||
}
|
||||
|
||||
export function LabsExperimentWidget({
|
||||
|
@ -27,45 +26,47 @@ export function LabsExperimentWidget({
|
|||
labsEnabled,
|
||||
experimentName,
|
||||
setErrorMessage,
|
||||
optedIn,
|
||||
setOptedIn,
|
||||
}: IntegrationLinkingWidgetProps) {
|
||||
const { t } = useTranslation()
|
||||
const userFeatures = getMeta('ol-features') as UserFeatures
|
||||
|
||||
const [enabled, setEnabled] = useState(() => {
|
||||
return userFeatures[experimentName] === true
|
||||
})
|
||||
|
||||
const experimentsErrorMessage = t(
|
||||
'we_are_unable_to_opt_you_into_this_experiment'
|
||||
)
|
||||
|
||||
const allowedExperiments = getMeta('ol-allowedExperiments')
|
||||
const disabled = !allowedExperiments.includes(experimentName) && !optedIn
|
||||
|
||||
const handleEnable = useCallback(async () => {
|
||||
try {
|
||||
const enablePath = `/labs/participate/experiments/${experimentName}/opt-in`
|
||||
await postJSON(enablePath)
|
||||
setEnabled(true)
|
||||
setOptedIn(true)
|
||||
} catch (err) {
|
||||
setErrorMessage(experimentsErrorMessage)
|
||||
}
|
||||
}, [experimentName, setErrorMessage, experimentsErrorMessage])
|
||||
}, [experimentName, setErrorMessage, experimentsErrorMessage, setOptedIn])
|
||||
|
||||
const handleDisable = useCallback(async () => {
|
||||
try {
|
||||
const disablePath = `/labs/participate/experiments/${experimentName}/opt-out`
|
||||
await postJSON(disablePath)
|
||||
setEnabled(false)
|
||||
setOptedIn(false)
|
||||
} catch (err) {
|
||||
setErrorMessage(experimentsErrorMessage)
|
||||
}
|
||||
}, [experimentName, setErrorMessage, experimentsErrorMessage])
|
||||
}, [experimentName, setErrorMessage, experimentsErrorMessage, setOptedIn])
|
||||
|
||||
return (
|
||||
<div className="labs-experiment-widget-container">
|
||||
<div
|
||||
className={`labs-experiment-widget-container ${disabled ? 'disabled-experiment' : ''}`}
|
||||
>
|
||||
<div className="p-2">{logo}</div>
|
||||
<div className="description-container">
|
||||
<div className="title-row">
|
||||
<h3 className="h4">{title}</h3>
|
||||
{enabled && <Badge bsStyle="info">{t('enabled')}</Badge>}
|
||||
{optedIn && <Badge bsStyle="info">{t('enabled')}</Badge>}
|
||||
</div>
|
||||
<p className="small">
|
||||
{description}{' '}
|
||||
|
@ -76,12 +77,16 @@ export function LabsExperimentWidget({
|
|||
)}
|
||||
</p>
|
||||
</div>
|
||||
{disabled && (
|
||||
<div className="disabled-explanation">{t('experiment_full')}</div>
|
||||
)}
|
||||
<div>
|
||||
{labsEnabled && (
|
||||
<ActionButton
|
||||
enabled={enabled}
|
||||
optedIn={optedIn}
|
||||
handleDisable={handleDisable}
|
||||
handleEnable={handleEnable}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -90,19 +95,21 @@ export function LabsExperimentWidget({
|
|||
}
|
||||
|
||||
type ActionButtonProps = {
|
||||
enabled?: boolean
|
||||
optedIn?: boolean
|
||||
disabled?: boolean
|
||||
handleEnable: () => void
|
||||
handleDisable: () => void
|
||||
}
|
||||
|
||||
function ActionButton({
|
||||
enabled,
|
||||
optedIn,
|
||||
disabled,
|
||||
handleEnable,
|
||||
handleDisable,
|
||||
}: ActionButtonProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (enabled) {
|
||||
if (optedIn) {
|
||||
return (
|
||||
<Button
|
||||
bsStyle="secondary"
|
||||
|
@ -112,6 +119,18 @@ function ActionButton({
|
|||
{t('turn_off')}
|
||||
</Button>
|
||||
)
|
||||
} else if (disabled) {
|
||||
return (
|
||||
<Tooltip
|
||||
id="experiment-disabled"
|
||||
description={t('this_experiment_isnt_accepting_new_participants')}
|
||||
overlayProps={{ delay: 0 }}
|
||||
>
|
||||
<Button bsStyle="secondary" className="btn btn-primary" disabled>
|
||||
{t('turn_on')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Button
|
||||
|
|
|
@ -52,6 +52,7 @@ import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
|||
export interface Meta {
|
||||
'ol-ExposedSettings': ExposedSettings
|
||||
'ol-allInReconfirmNotificationPeriods': UserEmailData[]
|
||||
'ol-allowedExperiments': string[]
|
||||
'ol-allowedImageNames': AllowedImageName[]
|
||||
'ol-anonymous': boolean
|
||||
'ol-bootstrapVersion': 3 | 5
|
||||
|
|
|
@ -81,3 +81,22 @@
|
|||
margin-top: 8em;
|
||||
margin-bottom: 12em;
|
||||
}
|
||||
|
||||
.labs-experiment-widget-container.disabled-experiment {
|
||||
grid-template-columns: 40px 3fr 1fr auto;
|
||||
|
||||
.disabled-explanation {
|
||||
color: @content-secondary;
|
||||
}
|
||||
|
||||
h3,
|
||||
p {
|
||||
color: @content-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled-experiment {
|
||||
.ai-error-assistant-avatar {
|
||||
filter: grayscale(0.6);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -584,6 +584,7 @@
|
|||
"exclusive_access_with_labs": "Exclusive access to early-stage experiments",
|
||||
"existing_plan_active_until_term_end": "Your existing plan and its features will remain active until the end of the current billing period.",
|
||||
"expand": "Expand",
|
||||
"experiment_full": "Sorry, this experiment is full",
|
||||
"expired": "Expired",
|
||||
"expired_confirmation_code": "Your confirmation code has expired. Click <0>Resend confirmation code</0> to get a new one.",
|
||||
"expires": "Expires",
|
||||
|
@ -2017,6 +2018,7 @@
|
|||
"this_action_cannot_be_undone": "This action cannot be undone.",
|
||||
"this_address_will_be_shown_on_the_invoice": "This address will be shown on the invoice",
|
||||
"this_could_be_because_we_cant_support_some_elements_of_the_table": "This could be because we can’t yet support some elements of the table in the table preview. Or there may be an error in the table’s LaTeX code.",
|
||||
"this_experiment_isnt_accepting_new_participants": "This experiment isn’t accepting new participants.",
|
||||
"this_field_is_required": "This field is required",
|
||||
"this_grants_access_to_features_2": "This grants you access to <0>__appName__</0> <0>__featureType__</0> features.",
|
||||
"this_is_a_labs_experiment": "This is a Labs experiment",
|
||||
|
|
Loading…
Reference in a new issue