From 837fea03b9efe92ea376a5e40276d83fa230b05f Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Mon, 29 Jul 2024 11:27:30 -0400 Subject: [PATCH] Merge pull request #19448 from overleaf/jdt-experiments-max-subscribers Enforce a maximum participant cap on experiments GitOrigin-RevId: 1d9263cd34a3d0c831c0ed43867bb4e6430eb06c --- .../GlobalMetrics/GlobalMetricsManager.js | 2 +- .../web/frontend/extracted-translations.json | 2 + .../labs/labs-experiments-widget.tsx | 59 ++++++++++++------- services/web/frontend/js/utils/meta.ts | 1 + .../frontend/stylesheets/modules/labs.less | 19 ++++++ services/web/locales/en.json | 2 + 6 files changed, 64 insertions(+), 21 deletions(-) diff --git a/services/web/app/src/Features/GlobalMetrics/GlobalMetricsManager.js b/services/web/app/src/Features/GlobalMetrics/GlobalMetricsManager.js index b3c304724c..75cbca1965 100644 --- a/services/web/app/src/Features/GlobalMetrics/GlobalMetricsManager.js +++ b/services/web/app/src/Features/GlobalMetrics/GlobalMetricsManager.js @@ -12,7 +12,7 @@ async function getMetric(key, defaultValue = 0) { if (!metric) { return defaultValue } - return metric + return metric.value } async function setMetric(key, value) { diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 69356c578b..d4436ce45b 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -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": "", diff --git a/services/web/frontend/js/shared/components/labs/labs-experiments-widget.tsx b/services/web/frontend/js/shared/components/labs/labs-experiments-widget.tsx index 941165d61b..a5f6001588 100644 --- a/services/web/frontend/js/shared/components/labs/labs-experiments-widget.tsx +++ b/services/web/frontend/js/shared/components/labs/labs-experiments-widget.tsx @@ -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 ( -
+
{logo}

{title}

- {enabled && {t('enabled')}} + {optedIn && {t('enabled')}}

{description}{' '} @@ -76,12 +77,16 @@ export function LabsExperimentWidget({ )}

+ {disabled && ( +
{t('experiment_full')}
+ )}
{labsEnabled && ( )}
@@ -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 ( + + ) } else { return (