From b91407416b4f9d9d2be541f4357bcc8a7e04c9f6 Mon Sep 17 00:00:00 2001 From: Shaheer Kochai Date: Tue, 22 Jul 2025 17:43:09 +0430 Subject: [PATCH] feat: add support for single step funnels while creating from span details (#8492) * feat: add support for single step funnels while creating from span details * fix: fix the UI for loading state --- .../AddSpanToFunnelModal.styles.scss | 108 ++++++++++-------- .../AddSpanToFunnelModal.tsx | 27 +++-- .../pages/TracesFunnelDetails/constants.ts | 18 ++- .../src/pages/TracesFunnels/FunnelContext.tsx | 21 +++- 4 files changed, 109 insertions(+), 65 deletions(-) diff --git a/frontend/src/container/TraceWaterfall/AddSpanToFunnelModal/AddSpanToFunnelModal.styles.scss b/frontend/src/container/TraceWaterfall/AddSpanToFunnelModal/AddSpanToFunnelModal.styles.scss index f7712562673e..3a78cc40e50b 100644 --- a/frontend/src/container/TraceWaterfall/AddSpanToFunnelModal/AddSpanToFunnelModal.styles.scss +++ b/frontend/src/container/TraceWaterfall/AddSpanToFunnelModal/AddSpanToFunnelModal.styles.scss @@ -1,66 +1,74 @@ // Modal base styles -.add-span-to-funnel-modal-container { - .ant-modal { - &-content, - &-header { - background: var(--bg-ink-500); - } - - &-header { - border-bottom: none; - - .ant-modal-title { - color: var(--bg-vanilla-100); +.add-span-to-funnel-modal { + &__loading-spinner { + display: flex; + align-items: center; + justify-content: center; + height: 400px; + } + &-container { + .ant-modal { + &-content, + &-header { + background: var(--bg-ink-500); } - } - &-body { - padding: 14px 16px !important; - padding-bottom: 0 !important; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } + &-header { + border-bottom: none; - &-footer { - margin-top: 0; - background: var(--bg-ink-400); - border-top: 1px solid var(--bg-slate-500); - padding: 16px !important; - .add-span-to-funnel-modal { - &__save-button { - display: flex; - align-items: center; - justify-content: center; - gap: 4px; + .ant-modal-title { color: var(--bg-vanilla-100); - font-size: 12px; - font-weight: 500; - line-height: 24px; - width: 135px; + } + } - .ant-btn-icon { + &-body { + padding: 14px 16px !important; + padding-bottom: 0 !important; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + + &-footer { + margin-top: 0; + background: var(--bg-ink-400); + border-top: 1px solid var(--bg-slate-500); + padding: 16px !important; + .add-span-to-funnel-modal { + &__save-button { display: flex; - } - &:disabled { - color: var(--bg-vanilla-400); + align-items: center; + justify-content: center; + gap: 4px; + color: var(--bg-vanilla-100); + font-size: 12px; + font-weight: 500; + line-height: 24px; + width: 135px; + .ant-btn-icon { - svg { - stroke: var(--bg-vanilla-400); + display: flex; + } + &:disabled { + color: var(--bg-vanilla-400); + .ant-btn-icon { + svg { + stroke: var(--bg-vanilla-400); + } } } } + &__discard-button { + background: var(--bg-slate-500); + } } - &__discard-button { - background: var(--bg-slate-500); + .ant-btn { + border-radius: 2px; + padding: 4px 8px; + margin: 0 !important; + border: none; + box-shadow: none; } } - .ant-btn { - border-radius: 2px; - padding: 4px 8px; - margin: 0 !important; - border: none; - box-shadow: none; - } } } } @@ -89,7 +97,7 @@ } .steps-content { - height: 500px; + max-height: 500px; } } } diff --git a/frontend/src/container/TraceWaterfall/AddSpanToFunnelModal/AddSpanToFunnelModal.tsx b/frontend/src/container/TraceWaterfall/AddSpanToFunnelModal/AddSpanToFunnelModal.tsx index 92d3f5d7801d..683b9fb7a7de 100644 --- a/frontend/src/container/TraceWaterfall/AddSpanToFunnelModal/AddSpanToFunnelModal.tsx +++ b/frontend/src/container/TraceWaterfall/AddSpanToFunnelModal/AddSpanToFunnelModal.tsx @@ -99,6 +99,7 @@ function AddSpanToFunnelModal({ const [triggerSave, setTriggerSave] = useState(false); const [isUnsavedChanges, setIsUnsavedChanges] = useState(false); const [triggerDiscard, setTriggerDiscard] = useState(false); + const [isCreatedFromSpan, setIsCreatedFromSpan] = useState(false); const handleSearch = (e: ChangeEvent): void => { setSearchQuery(e.target.value); @@ -126,6 +127,7 @@ function AddSpanToFunnelModal({ const handleFunnelClick = (funnel: FunnelData): void => { setSelectedFunnelId(funnel.funnel_id); setActiveView(ModalView.DETAILS); + setIsCreatedFromSpan(false); }; const handleBack = (): void => { @@ -133,6 +135,7 @@ function AddSpanToFunnelModal({ setSelectedFunnelId(undefined); setIsUnsavedChanges(false); setTriggerSave(false); + setIsCreatedFromSpan(false); }; const handleCreateNewClick = (): void => { @@ -188,6 +191,7 @@ function AddSpanToFunnelModal({ if (funnelId) { setSelectedFunnelId(funnelId); setActiveView(ModalView.DETAILS); + setIsCreatedFromSpan(true); } setIsCreateModalOpen(false); }} @@ -206,15 +210,18 @@ function AddSpanToFunnelModal({ All funnels - } - > -
-
+
+
+ } + > {selectedFunnelId && funnelDetails?.payload && ( - + )} -
+
- +
); diff --git a/frontend/src/pages/TracesFunnelDetails/constants.ts b/frontend/src/pages/TracesFunnelDetails/constants.ts index 33b539e4744c..c48bbf9a31d5 100644 --- a/frontend/src/pages/TracesFunnelDetails/constants.ts +++ b/frontend/src/pages/TracesFunnelDetails/constants.ts @@ -1,7 +1,7 @@ import { FunnelStepData, LatencyOptions } from 'types/api/traceFunnels'; import { v4 } from 'uuid'; -export const initialStepsData: FunnelStepData[] = [ +export const createInitialStepsData = (): FunnelStepData[] => [ { id: v4(), step_order: 1, @@ -12,7 +12,6 @@ export const initialStepsData: FunnelStepData[] = [ op: 'and', }, latency_pointer: 'start', - latency_type: undefined, has_errors: false, }, { @@ -30,6 +29,21 @@ export const initialStepsData: FunnelStepData[] = [ }, ]; +export const createSingleStepData = (): FunnelStepData[] => [ + { + id: v4(), + step_order: 1, + service_name: '', + span_name: '', + filters: { + items: [], + op: 'and', + }, + latency_pointer: 'start', + has_errors: false, + }, +]; + export const LatencyPointers: { value: FunnelStepData['latency_pointer']; key: string; diff --git a/frontend/src/pages/TracesFunnels/FunnelContext.tsx b/frontend/src/pages/TracesFunnels/FunnelContext.tsx index 021ecf4da73c..9a0092b6d8e8 100644 --- a/frontend/src/pages/TracesFunnels/FunnelContext.tsx +++ b/frontend/src/pages/TracesFunnels/FunnelContext.tsx @@ -10,7 +10,10 @@ import { normalizeSteps } from 'hooks/TracesFunnels/useFunnelConfiguration'; import { useValidateFunnelSteps } from 'hooks/TracesFunnels/useFunnels'; import getStartEndRangeTime from 'lib/getStartEndRangeTime'; import { isEqual } from 'lodash-es'; -import { initialStepsData } from 'pages/TracesFunnelDetails/constants'; +import { + createInitialStepsData, + createSingleStepData, +} from 'pages/TracesFunnelDetails/constants'; import { createContext, Dispatch, @@ -68,9 +71,11 @@ const FunnelContext = createContext(undefined); export function FunnelProvider({ children, funnelId, + hasSingleStep = false, }: { children: React.ReactNode; funnelId: string; + hasSingleStep?: boolean; }): JSX.Element { const { selectedTime } = useSelector( (state) => state.globalTime, @@ -89,7 +94,13 @@ export function FunnelProvider({ funnelId, ]); const funnel = data?.payload; - const initialSteps = funnel?.steps?.length ? funnel.steps : initialStepsData; + + const defaultSteps = useMemo( + () => (hasSingleStep ? createSingleStepData() : createInitialStepsData()), + [hasSingleStep], + ); + + const initialSteps = funnel?.steps?.length ? funnel.steps : defaultSteps; const [steps, setSteps] = useState(initialSteps); const [triggerSave, setTriggerSave] = useState(false); const [isUpdatingFunnel, setIsUpdatingFunnel] = useState(false); @@ -155,7 +166,7 @@ export function FunnelProvider({ setSteps((prev) => [ ...prev, { - ...initialStepsData[0], + ...createInitialStepsData()[0], id: v4(), step_order: prev.length + 1, }, @@ -296,6 +307,10 @@ export function FunnelProvider({ ); } +FunnelProvider.defaultProps = { + hasSingleStep: false, +}; + export function useFunnelContext(): FunnelContextType { const context = useContext(FunnelContext); if (context === undefined) {