signoz/frontend/src/pages/TracesFunnels/FunnelContext.tsx
Shaheer Kochai 6334e09a60
feat: Funnel Details Page Base Structure (#7364)
* feat: funnels list page basic UI

* feat: get funnels list data from mock API, and handle data, loading and empty states

* feat: implement funnel rename

* chore: move useFunnels to hooks/TracesFunnels

* feat: create traces funnels details basic page + funnel -> details redirection

* fix: properly display created at in funnels list item + preventDefault

* chore: add tab bar to trace funnel details page

* chore: traces funnel details page overall skeleton

* chore: traces funnel details results skeleton

* fix: hide step count for add button only

* feat: funnel details page steps and configuration (#7424)

* chore: add a new tab for traces funnels

* feat: funnels list page basic UI

* feat: get funnels list data from mock API, and handle data, loading and empty states

* feat: implement funnel rename

* refactor: overall improvements

* feat: implement sorting in traces funnels list page

* feat: add sort column key and order to url params

* chore: move useFunnels to hooks/TracesFunnels

* feat: implement traces funnels search and refactor search and sort by extracting to custom hooks

* chore: overall improvements to rename trace funnel modal

* chore: make the rename input auto-focusable

* feat: handle create funnel modal

* feat: delete funnel modal and functionality

* fix: fix the layout shift in funnel item caused by getContainer={false}

* chore: overall improvements and use live api in traces funnels

* feat: create traces funnels details basic page + funnel -> details redirection

* fix: funnels traces light mode UI

* fix: properly display created at in funnels list item + preventDefault

* refactor: extract FunnelItemPopover into a separate component

* chore: hide funnel tab from traces explorer

* chore: add check to display trace funnels tab only in dev environment

* chore: improve funnels modals light mode

* chore: overall improvements

* fix: properly pass funnel details link

* chore: address PR review changes

* chore: add tab bar to trace funnel details page

* feat: funnel step UI with service, span, and where filters

* feat: build radio button component

* refactor: use the SignozRadioButton in funnel results -> step transitions radio buttons

* feat: inter step config (i.e. latency type) UI

* chore: improve steps header styles by removing divider width

* feat: funnel steps title, description, popover UI + pass data from API

* chore: update FilterSelect component to conditionally add url params and accept on change

* fix: fix funnel step where clause and update the state variables for filters

* chore: add support for isMultiple and fix the type in FilterSelect

* feat: centralize the steps state management in StepsContent

* fix: move steps state up + pass steps count from state

* feat: implement auto save for updating the steps whenever any step changes

* feat: implement auto save for validating steps if service name or span names change

* feat: impelement funnel step removal

* feat: implement add details modal for funnel steps

* fix: fix the overflowing time range picker

* feat: funnel details empty state

* feat: add support for saving funnel description

* chore: overall improvements

* fix: fix the light mode styles

* fix: fix the failing build + broken search UI

* refactor: remove the reference of useLocation from traceFunnel item in TraceModulePage constant

* fix: fix the issue of update steps getting triggered on initial render if we have filters

* fix: fix the edge case of stale state causing filters to be re-added after removing

* feat: funnel details page results (#7451)

* feat: funnel metrics table component

* feat: funnel metrics and steps transition metrics components UI

* feat: funnel table component

* feat: slowest traces and traces with error components

* fix: overall light theme fixes

* fix: fix the warning

* chore: add empty and loading states to FunnelMetricsTable

* feat: get overall funnel metrics from the API

* fix: fix the empty state of funnel metrics table

* feat: get data for slowest traces and traces with errors

* fix: link trace id to trace details page

* fix: get data for funnel step transition metrics and refactor the existing data fetching logic

* refactor: add funnel context + overall refactoring and optimizations

* refactor: move steps states to funnel context + handle empty and run funnel disabled states

* feat: handle run funnel

* fix: improve empty state

* chore: rename isValidateStepsMutationLoading -> isValidateStepsLoading

* chore: improve query key

* fix: display loading state if funnel results are fetching

* refactor: move steps validation fetching and states to the context API

* fix: display loading state in funnel results while steps validation is fetching

* fix: call validate steps API only on changing the service name or span name of any step

* refactor: move validateStepsQuery key out of useEffect and update the dependencies

* chore: centralize hasIncompleteSteps and run validate only if steps have service and spans

* fix: handle all empty fields state + overall improvements

* fix: handle long where query tags

* feat: build the funnel result graph component

* feat: build the funnel result graph component

* feat: handle loading, error, empty states in funnel graph

* fix: don't display change percentage if % is 0

* refactor: overall improvements

* feat: get funnel steps graph data from API + move logic to custom hook

* fix: improve empty and error states

* fix: handle funnel graph legends width using css

* fix: redirect to trace funnels list page on clicking delete from funnel details

* fix: update the query cache while updating steps

* fix: implement debounced search for funnel list search

* fix: refetch steps graph data query on clicking run funnel / sync button

* fix: improve the step footer spacing

* chore: add gap between divider to inter-step-config

* fix: handle loading state while fetching

* feat: add span to funnel flow (from trace details page) (#7477)

* chore: display add to funnel icon on hovering any span in trace details page

* chore: add className to funnel item actions popover

* feat: add funnels tab to trace details v2 tab bar

* feat: add span to funnel flow

* chore: hide actions popover button from funnel item in span -> funnel flows

* chore: improve the funnel details UI in add span to funnel modal

* fix: display empty state + don't redirect to funnels list on delete success + overall improvements

* chore: add null check

* fix: display add to funnel button based on feature flag

* fix: display funnels tab in trace details based on feature flag

* fix: remove maxTagCount

* feat: change ms to ns

* chore: address review comments

* chore: remove feature flag and display trace funnels only in dev envirnoment

* fix: handle restoring steps if updating funnel steps fail

* refactor: update the get and delete funnel endpoints to adjust to the BE changes (#7697)

* refactor: address review comments

* fix: handle nested funnel response structure to fix missing funnel_id… (#7740)

* fix: handle nested funnel response structure to fix missing funnel_id in updates

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* chore: remove console.og

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* chore: revert explicitly passing funnelId to updateFunnelSteps

---------

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
Co-authored-by: ahmadshaheer <ashaheerki@gmail.com>

* chore: fix api endpoint

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* refactor: incorporate the recent funnel details API changes (#7760)

* chore: trace funnels feedback changes (#7772)

* chore: change the copy from x traces to valid traces found / not found

* chore: add open funnel button in add span to funnel modal

* feat: display buttons for adding step details and funnel description + copy to clipboard

* feat: highlight funnel graph column based on selected (total / error span) from the legend items

* chore: trace funnel changes (#7780)

* refactor: handle funnels list search on frontend

* refactor: use funnel steps update API for adding / updating step title and description

* feat: allow selecting user's typed option in trace funnel service and span name dropdowns

* chore: properly render the -> between steps in funnel results

* fix: sync funnel step name with add details modal text fields

---------

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
Co-authored-by: Yunus M <myounis.ar@live.com>
Co-authored-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
2025-05-12 10:16:26 +05:30

245 lines
6.3 KiB
TypeScript

import { ValidateFunnelResponse } from 'api/traceFunnels';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { Time } from 'container/TopNav/DateTimeSelection/config';
import {
CustomTimeType,
Time as TimeV2,
} from 'container/TopNav/DateTimeSelectionV2/config';
import { useValidateFunnelSteps } from 'hooks/TracesFunnels/useFunnels';
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
import { initialStepsData } from 'pages/TracesFunnelDetails/constants';
import {
createContext,
Dispatch,
SetStateAction,
useCallback,
useContext,
useMemo,
useState,
} from 'react';
import { useQueryClient } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { FunnelData, FunnelStepData } from 'types/api/traceFunnels';
import { GlobalReducer } from 'types/reducer/globalTime';
import { v4 } from 'uuid';
interface FunnelContextType {
startTime: number;
endTime: number;
selectedTime: CustomTimeType | Time | TimeV2;
validTracesCount: number;
funnelId: string;
steps: FunnelStepData[];
setSteps: Dispatch<SetStateAction<FunnelStepData[]>>;
initialSteps: FunnelStepData[];
handleAddStep: () => boolean;
handleStepChange: (index: number, newStep: Partial<FunnelStepData>) => void;
handleStepRemoval: (index: number) => void;
handleRunFunnel: () => void;
validationResponse:
| SuccessResponse<ValidateFunnelResponse>
| ErrorResponse
| undefined;
isValidateStepsLoading: boolean;
hasIncompleteStepFields: boolean;
setHasIncompleteStepFields: Dispatch<SetStateAction<boolean>>;
hasAllEmptyStepFields: boolean;
setHasAllEmptyStepFields: Dispatch<SetStateAction<boolean>>;
handleReplaceStep: (
index: number,
serviceName: string,
spanName: string,
) => void;
handleRestoreSteps: (oldSteps: FunnelStepData[]) => void;
}
const FunnelContext = createContext<FunnelContextType | undefined>(undefined);
export function FunnelProvider({
children,
funnelId,
}: {
children: React.ReactNode;
funnelId: string;
}): JSX.Element {
const { selectedTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const { start, end } = getStartEndRangeTime({
type: 'GLOBAL_TIME',
interval: selectedTime,
});
const startTime = Math.floor(Number(start) * 1e9);
const endTime = Math.floor(Number(end) * 1e9);
const queryClient = useQueryClient();
const data = queryClient.getQueryData<{ payload: FunnelData }>([
REACT_QUERY_KEY.GET_FUNNEL_DETAILS,
funnelId,
]);
const funnel = data?.payload;
const initialSteps = funnel?.steps?.length ? funnel.steps : initialStepsData;
const [steps, setSteps] = useState<FunnelStepData[]>(initialSteps);
const [hasIncompleteStepFields, setHasIncompleteStepFields] = useState(
steps.some((step) => step.service_name === '' || step.span_name === ''),
);
const [hasAllEmptyStepFields, setHasAllEmptyStepFields] = useState(
steps.every((step) => step.service_name === '' && step.span_name === ''),
);
const {
data: validationResponse,
isLoading: isValidationLoading,
isFetching: isValidationFetching,
} = useValidateFunnelSteps({
funnelId,
selectedTime,
startTime,
endTime,
});
const validTracesCount = useMemo(
() => validationResponse?.payload?.data?.length || 0,
[validationResponse],
);
// Step modifications
const handleStepUpdate = useCallback(
(index: number, newStep: Partial<FunnelStepData>) => {
setSteps((prev) =>
prev.map((step, i) => (i === index ? { ...step, ...newStep } : step)),
);
},
[],
);
const addNewStep = useCallback(() => {
if (steps.length >= 3) return false;
setSteps((prev) => [
...prev,
{
...initialStepsData[0],
id: v4(),
step_order: prev.length + 1,
},
]);
return true;
}, [steps.length]);
const handleStepRemoval = useCallback((index: number) => {
setSteps((prev) =>
prev
// remove the step in the index
.filter((_, i) => i !== index)
// reset the step_order for the remaining steps
.map((step, newIndex) => ({
...step,
step_order: newIndex + 1,
})),
);
}, []);
const handleRestoreSteps = useCallback((oldSteps: FunnelStepData[]) => {
setSteps(oldSteps);
}, []);
const handleReplaceStep = useCallback(
(index: number, serviceName: string, spanName: string) => {
handleStepUpdate(index, {
service_name: serviceName,
span_name: spanName,
});
},
[handleStepUpdate],
);
if (!funnelId) {
throw new Error('Funnel ID is required');
}
const handleRunFunnel = useCallback(async (): Promise<void> => {
if (validTracesCount === 0) return;
queryClient.refetchQueries([
REACT_QUERY_KEY.GET_FUNNEL_OVERVIEW,
funnelId,
selectedTime,
]);
queryClient.refetchQueries([
REACT_QUERY_KEY.GET_FUNNEL_STEPS_GRAPH_DATA,
funnelId,
selectedTime,
]);
queryClient.refetchQueries([
REACT_QUERY_KEY.GET_FUNNEL_ERROR_TRACES,
funnelId,
selectedTime,
]);
queryClient.refetchQueries([
REACT_QUERY_KEY.GET_FUNNEL_SLOW_TRACES,
funnelId,
selectedTime,
]);
}, [funnelId, queryClient, selectedTime, validTracesCount]);
const value = useMemo<FunnelContextType>(
() => ({
funnelId,
startTime,
endTime,
validTracesCount,
selectedTime,
steps,
setSteps,
initialSteps,
handleStepChange: handleStepUpdate,
handleAddStep: addNewStep,
handleStepRemoval,
handleRunFunnel,
validationResponse,
isValidateStepsLoading: isValidationLoading || isValidationFetching,
hasIncompleteStepFields,
setHasIncompleteStepFields,
hasAllEmptyStepFields,
setHasAllEmptyStepFields,
handleReplaceStep,
handleRestoreSteps,
}),
[
funnelId,
startTime,
endTime,
validTracesCount,
selectedTime,
steps,
initialSteps,
handleStepUpdate,
addNewStep,
handleStepRemoval,
handleRunFunnel,
validationResponse,
isValidationLoading,
isValidationFetching,
hasIncompleteStepFields,
setHasIncompleteStepFields,
hasAllEmptyStepFields,
setHasAllEmptyStepFields,
handleReplaceStep,
handleRestoreSteps,
],
);
return (
<FunnelContext.Provider value={value}>{children}</FunnelContext.Provider>
);
}
export function useFunnelContext(): FunnelContextType {
const context = useContext(FunnelContext);
if (context === undefined) {
throw new Error('useFunnelContext must be used within a FunnelProvider');
}
return context;
}