diff --git a/services/web/app/src/Features/Tutorial/TutorialController.js b/services/web/app/src/Features/Tutorial/TutorialController.js
index 3cf1d6140f..0c01bcdfb6 100644
--- a/services/web/app/src/Features/Tutorial/TutorialController.js
+++ b/services/web/app/src/Features/Tutorial/TutorialController.js
@@ -9,6 +9,7 @@ const VALID_KEYS = [
'bib-file-tpr-prompt',
'ai-error-assistant-consent',
'code-editor-mode-prompt',
+ 'history-restore-promo',
]
async function completeTutorial(req, res, next) {
diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index c02cce0fa3..709b5f0153 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -614,6 +614,8 @@
"history_label_project_current_state": "",
"history_label_this_version": "",
"history_new_label_name": "",
+ "history_restore_promo_content": "",
+ "history_restore_promo_title": "",
"history_resync": "",
"history_view_a11y_description": "",
"history_view_all": "",
diff --git a/services/web/frontend/js/features/history/components/change-list/all-history-list.tsx b/services/web/frontend/js/features/history/components/change-list/all-history-list.tsx
index 6cc979685b..2c0abd1a2f 100644
--- a/services/web/frontend/js/features/history/components/change-list/all-history-list.tsx
+++ b/services/web/frontend/js/features/history/components/change-list/all-history-list.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useRef, useState } from 'react'
+import { useEffect, useRef, useState } from 'react'
import HistoryVersion from './history-version'
import LoadingSpinner from '../../../../shared/components/loading-spinner'
import { OwnerPaywallPrompt } from './owner-paywall-prompt'
@@ -12,9 +12,8 @@ import { Overlay, Popover } from 'react-bootstrap'
import Close from '@/shared/components/close'
import { Trans, useTranslation } from 'react-i18next'
import MaterialIcon from '@/shared/components/material-icon'
-import useAsync from '@/shared/hooks/use-async'
-import { completeHistoryTutorial } from '../../services/api'
-import { debugConsole } from '@/utils/debugging'
+import useTutorial from '@/shared/hooks/promotions/use-tutorial'
+import { useFeatureFlag } from '@/shared/context/split-test-context'
function AllHistoryList() {
const { id: currentUserId } = useUserContext()
@@ -92,23 +91,29 @@ function AllHistoryList() {
}
}, [updatesLoadingState])
- const { inactiveTutorials, deactivateTutorial } = useEditorContext()
-
- const [showPopover, setShowPopover] = useState(() => {
- // only show tutorial popover if they haven't dismissed ("completed") it yet
- return !inactiveTutorials.includes('react-history-buttons-tutorial')
+ const { inactiveTutorials } = useEditorContext()
+ const {
+ showPopup: showHistoryTutorial,
+ tryShowingPopup: tryShowingHistoryTutorial,
+ hideUntilReload: hideHistoryTutorialUntilReload,
+ completeTutorial: completeHistoryTutorial,
+ } = useTutorial('react-history-buttons-tutorial', {
+ name: 'react-history-buttons-tutorial',
})
- const completeTutorial = useCallback(() => {
- setShowPopover(false)
- deactivateTutorial('react-history-buttons-tutorial')
- }, [deactivateTutorial])
+ const {
+ showPopup: showRestorePromo,
+ tryShowingPopup: tryShowingRestorePromo,
+ hideUntilReload: hideRestorePromoUntilReload,
+ completeTutorial: completeRestorePromo,
+ } = useTutorial('history-restore-promo', {
+ name: 'history-restore-promo',
+ })
+ const inFileRestoreSplitTest = useFeatureFlag('revert-file')
+ const inProjectRestoreSplitTest = useFeatureFlag('revert-project')
- const { runAsync } = useAsync()
-
- const { t } = useTranslation()
-
- // wait for the layout to settle before showing popover, to avoid a flash/ instant move
+ const hasVisibleUpdates = visibleUpdates.length > 0
+ const isMoreThanOneVersion = visibleUpdates.length > 1
const [layoutSettled, setLayoutSettled] = useState(false)
// When there is a paywall and only two version's to compare,
@@ -117,30 +122,60 @@ function AllHistoryList() {
const isPaywallAndNonComparable =
visibleUpdates.length === 2 && updatesInfo.freeHistoryLimitHit
- const isMoreThanOneVersion = visibleUpdates.length > 1
+ useEffect(() => {
+ const hasCompletedHistoryTutorial = inactiveTutorials.includes(
+ 'react-history-buttons-tutorial'
+ )
+ const hasCompletedRestorePromotion = inactiveTutorials.includes(
+ 'history-restore-promo'
+ )
+
+ // wait for the layout to settle before showing popover, to avoid a flash/ instant move
+ if (!layoutSettled) {
+ return
+ }
+ if (
+ !hasCompletedHistoryTutorial &&
+ isMoreThanOneVersion &&
+ !isPaywallAndNonComparable
+ ) {
+ tryShowingHistoryTutorial()
+ } else if (
+ !hasCompletedRestorePromotion &&
+ inFileRestoreSplitTest &&
+ inProjectRestoreSplitTest &&
+ hasVisibleUpdates
+ ) {
+ tryShowingRestorePromo()
+ }
+ }, [
+ hasVisibleUpdates,
+ inFileRestoreSplitTest,
+ inProjectRestoreSplitTest,
+ tryShowingRestorePromo,
+ inactiveTutorials,
+ isMoreThanOneVersion,
+ isPaywallAndNonComparable,
+ layoutSettled,
+ tryShowingHistoryTutorial,
+ ])
+
+ const { t } = useTranslation()
+
let popover = null
// hiding is different from dismissing, as we wont save a full dismissal to the user
// meaning the tutorial will show on page reload/ re-navigation
const hidePopover = () => {
- completeTutorial()
+ hideHistoryTutorialUntilReload()
+ hideRestorePromoUntilReload()
}
- if (
- isMoreThanOneVersion &&
- showPopover &&
- !isPaywallAndNonComparable &&
- layoutSettled
- ) {
- const dismissModal = () => {
- completeTutorial()
- runAsync(completeHistoryTutorial()).catch(debugConsole.error)
- }
-
+ if (showHistoryTutorial) {
popover = (
{t('react_history_tutorial_title')}{' '}
- dismissModal()} />
+
+ completeHistoryTutorial({
+ event: 'promo-click',
+ action: 'complete',
+ })
+ }
+ />
}
className="dark-themed history-popover"
@@ -172,6 +215,49 @@ function AllHistoryList() {
)
+ } else if (showRestorePromo) {
+ popover = (
+
+
+ {t('history_restore_promo_title')}
+
+ completeRestorePromo({
+ event: 'promo-click',
+ action: 'complete',
+ })
+ }
+ />
+
+ }
+ className="dark-themed history-popover"
+ >
+ ,
+ ]}
+ />
+
+
+ )
}
// give the components time to position before showing popover so we don't get an instant position change
diff --git a/services/web/frontend/js/features/history/services/api.ts b/services/web/frontend/js/features/history/services/api.ts
index 91d2573dab..a9c926dc86 100644
--- a/services/web/frontend/js/features/history/services/api.ts
+++ b/services/web/frontend/js/features/history/services/api.ts
@@ -50,10 +50,6 @@ export function deleteLabel(
return deleteJSON(`/project/${projectId}/labels/${labelId}`, { signal })
}
-export function completeHistoryTutorial() {
- return postJSON('/tutorial/react-history-buttons-tutorial/complete')
-}
-
export function diffFiles(
projectId: string,
fromV: number,
diff --git a/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx b/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx
index 5b898fbe5f..2d5e814b5c 100644
--- a/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx
+++ b/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx
@@ -81,6 +81,11 @@ export const useTutorial = (
[setCurrentPopup, setShowPopup, tutorialKey, eventData]
)
+ const hideUntilReload = useCallback(() => {
+ clearPopup()
+ deactivateTutorial(tutorialKey)
+ }, [clearPopup, deactivateTutorial, tutorialKey])
+
return {
completeTutorial,
dismissTutorial,
@@ -89,6 +94,7 @@ export const useTutorial = (
clearPopup,
clearAndShow,
showPopup,
+ hideUntilReload,
}
}
diff --git a/services/web/frontend/stylesheets/app/editor/history-react.less b/services/web/frontend/stylesheets/app/editor/history-react.less
index 372e368f93..f000c3b6b6 100644
--- a/services/web/frontend/stylesheets/app/editor/history-react.less
+++ b/services/web/frontend/stylesheets/app/editor/history-react.less
@@ -427,6 +427,9 @@ history-root {
color: @neutral-10;
vertical-align: top;
}
+.history-restore-promo-icon {
+ vertical-align: middle;
+}
.history-file-tree {
display: flex !important; // To work around jQuery layout's inline styles
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 7724a8722d..ab8dd86ed9 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -870,6 +870,8 @@
"history_label_project_current_state": "Current state",
"history_label_this_version": "Label this version",
"history_new_label_name": "New label name",
+ "history_restore_promo_content": "Now you can restore a single file or your whole project to a previous version, including comments and tracked changes. Click Restore this version to restore the selected file or use the <0>0> menu in the history entry to restore the full project.",
+ "history_restore_promo_title": "Need to turn back time?",
"history_resync": "History resync",
"history_view_a11y_description": "Show all of the project history or only labelled versions.",
"history_view_all": "All history",