diff --git a/assets/js/dashboard/extra/exploration.js b/assets/js/dashboard/extra/exploration.js
index b483ea6c2ef3..87dd6b9a4822 100644
--- a/assets/js/dashboard/extra/exploration.js
+++ b/assets/js/dashboard/extra/exploration.js
@@ -52,11 +52,11 @@ function fetchNextWithFunnel(
)
}
-function fetchSuggestedJourney(site, dashboardState) {
+function fetchInterestingFunnel(site, dashboardState) {
return api.post(
url.apiPath(site, '/exploration/interesting-funnel'),
dashboardState,
- {}
+ { max_steps: 2, max_candidates: 6 }
)
}
@@ -219,55 +219,16 @@ export function FunnelExploration() {
// real funnel response arrives. Prevents from flashing "0 visitors"
// during the loading window.
const [provisionalFunnelEntries, setProvisionalFunnelEntries] = useState({})
- // track in flight "Suggest a journey" request
- const [isSuggestingJourney, setIsSuggestingJourney] = useState(false)
- // counter to detect and discard stale suggestion responses
- const suggestionRequestIdRef = useRef(0)
// Tracks the steps/direction/dashboardState values from the previous effect
// run so we can tell whether the journey changed (needs funnel) or only the
// search filter changed (next steps only, no funnel).
const prevStepsRef = useRef(steps)
const prevDirectionRef = useRef(direction)
const prevDashboardStateRef = useRef(dashboardState)
-
- function cancelPendingSuggestion() {
- suggestionRequestIdRef.current += 1
- setIsSuggestingJourney(false)
- }
-
- function handleSuggestJourney() {
- if (isSuggestingJourney) {
- return
- }
-
- const requestId = ++suggestionRequestIdRef.current
- setIsSuggestingJourney(true)
-
- fetchSuggestedJourney(site, dashboardState)
- .then((response) => {
- // newer request (or an explicit cancel)
- if (suggestionRequestIdRef.current !== requestId) {
- return
- }
-
- if (response && response.length > 0) {
- setSteps(response.map(({ step }) => step))
- setFunnel(response)
- }
- })
- .catch(() => {})
- .finally(() => {
- if (suggestionRequestIdRef.current === requestId) {
- setIsSuggestingJourney(false)
- }
- })
- }
+ const preloadFiredRef = useRef(false)
+ const funnelFromPreloadRef = useRef(false)
function handleSelect(columnIndex, selected) {
- if (isSuggestingJourney) {
- cancelPendingSuggestion()
- }
-
// Reset the active-column filter whenever the journey changes
setActiveColumnFilter('')
@@ -307,10 +268,6 @@ export function FunnelExploration() {
function handleDirectionSelect(nextDirection) {
if (nextDirection === direction) return
- if (isSuggestingJourney) {
- cancelPendingSuggestion()
- }
-
setDirection(nextDirection)
setSteps(steps.toReversed())
setFunnel([])
@@ -319,14 +276,13 @@ export function FunnelExploration() {
setProvisionalFunnelEntries({})
}
- // Fetch next step suggestions (and funnel, if the journey changed) whenever
- // the journey, direction, dashboard filters, or search term change.
- // Funnel is only re-fetched when steps or direction/dashboardState change,
- // search doesn't affect it.
+ // On first render fire the interesting-funnel preload and skip the normal
+ // next-with-funnel fetch. Once the preload resolves it sets steps
+ // and funnel, which re-triggers this effect for the next-step candidates fetch.
+ //
+ // On subsequent renders (via user interaction) fetch next steps and,
+ // if the journey changed, also refetch the funnel.
useEffect(() => {
- setActiveColumnLoading(true)
- setActiveColumnResults([])
-
const journeyChanged =
prevStepsRef.current !== steps ||
prevDirectionRef.current !== direction ||
@@ -336,14 +292,65 @@ export function FunnelExploration() {
prevDirectionRef.current = direction
prevDashboardStateRef.current = dashboardState
- const includeFunnel = journeyChanged && steps.length > 0
+ let cancelled = false
+
+ if (!preloadFiredRef.current) {
+ preloadFiredRef.current = true
+ setActiveColumnLoading(true)
+
+ fetchInterestingFunnel(site, dashboardState)
+ .then((response) => {
+ if (cancelled) return
+ if (response && response.length > 0) {
+ funnelFromPreloadRef.current = true
+ setSteps(response.map(({ step }) => step))
+ setFunnel(response)
+ } else {
+ // Nothing to preload, fall back to a plain next-steps fetch
+ fetchNextWithFunnel(site, dashboardState, [], '', direction, false)
+ .then((r) => {
+ if (!cancelled) setActiveColumnResults(r?.next || [])
+ })
+ .catch(() => {
+ if (!cancelled) setActiveColumnResults([])
+ })
+ .finally(() => {
+ if (!cancelled) setActiveColumnLoading(false)
+ })
+ }
+ })
+ .catch(() => {
+ if (cancelled) return
+ fetchNextWithFunnel(site, dashboardState, [], '', direction, false)
+ .then((r) => {
+ if (!cancelled) setActiveColumnResults(r?.next || [])
+ })
+ .catch(() => {
+ if (!cancelled) setActiveColumnResults([])
+ })
+ .finally(() => {
+ if (!cancelled) setActiveColumnLoading(false)
+ })
+ })
+
+ return () => {
+ cancelled = true
+ }
+ }
+
+ setActiveColumnLoading(true)
+ setActiveColumnResults([])
+
+ const funnelAlreadyLoaded = funnelFromPreloadRef.current
+ funnelFromPreloadRef.current = false
+
+ const includeFunnel =
+ journeyChanged && steps.length > 0 && !funnelAlreadyLoaded
if (journeyChanged && steps.length === 0) {
setFunnel([])
}
- let cancelled = false
-
fetchNextWithFunnel(
site,
dashboardState,
@@ -393,16 +400,6 @@ export function FunnelExploration() {
- {steps.length === 0 &&
- direction === EXPLORATION_DIRECTIONS.FORWARD && (
-
- )}