mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-18 16:07:10 +00:00
chore: funnel run and save flow changes (#8231)
* feat: while the funnel steps are invalid, handle auto save in local storage * chore: handle lightmode style in 'add span to funnel' modal * fix: don't save incomplete steps state in local storage if last saved configuration has valid steps * chore: close the 'Add span to funnel' modal on clicking save or discard * chore: deprecate the run funnel flow for unexecuted funnel * feat: change the funnel configuration save logic, and deprecate auto save * refactor: send all steps in the payload of analytics/overview * refactor: send all steps in the payload of analytics/steps (graph API) * chore: send all steps in the payload of analytics/steps/overview API * chore: send funnel steps with slow and error traces + deprecate the refetch on latency type change * chore: overall improvements * chore: change the save funnel icon + increase the width of funnel steps * fix: make the changes w.r.t. the updated funnel steps validation API + bugfixes * fix: remove funnelId from funnel results APIs * fix: handle edge case i.e. refetch funnel results on deleting a funnel step * chore: remove funnel steps configuration cache on removing funnel * chore: don't refetch the results on changing the latency type * fix: fix the edge cases of save funnel button being enabled even after saving the funnel steps * chore: remove the span count column from top traces tables * fix: fix the failing CI check by removing unnecessary props / fixing the types
This commit is contained in:
parent
66affb0ece
commit
bed3dbc698
@ -119,6 +119,7 @@ export const updateFunnelSteps = async (
|
|||||||
export interface ValidateFunnelPayload {
|
export interface ValidateFunnelPayload {
|
||||||
start_time: number;
|
start_time: number;
|
||||||
end_time: number;
|
end_time: number;
|
||||||
|
steps: FunnelStepData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ValidateFunnelResponse {
|
export interface ValidateFunnelResponse {
|
||||||
@ -132,12 +133,11 @@ export interface ValidateFunnelResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const validateFunnelSteps = async (
|
export const validateFunnelSteps = async (
|
||||||
funnelId: string,
|
|
||||||
payload: ValidateFunnelPayload,
|
payload: ValidateFunnelPayload,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<SuccessResponse<ValidateFunnelResponse> | ErrorResponse> => {
|
): Promise<SuccessResponse<ValidateFunnelResponse> | ErrorResponse> => {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/validate`,
|
`${FUNNELS_BASE_PATH}/analytics/validate`,
|
||||||
payload,
|
payload,
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
@ -185,6 +185,7 @@ export interface FunnelOverviewPayload {
|
|||||||
end_time: number;
|
end_time: number;
|
||||||
step_start?: number;
|
step_start?: number;
|
||||||
step_end?: number;
|
step_end?: number;
|
||||||
|
steps: FunnelStepData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FunnelOverviewResponse {
|
export interface FunnelOverviewResponse {
|
||||||
@ -202,12 +203,11 @@ export interface FunnelOverviewResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getFunnelOverview = async (
|
export const getFunnelOverview = async (
|
||||||
funnelId: string,
|
|
||||||
payload: FunnelOverviewPayload,
|
payload: FunnelOverviewPayload,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<SuccessResponse<FunnelOverviewResponse> | ErrorResponse> => {
|
): Promise<SuccessResponse<FunnelOverviewResponse> | ErrorResponse> => {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/overview`,
|
`${FUNNELS_BASE_PATH}/analytics/overview`,
|
||||||
payload,
|
payload,
|
||||||
{
|
{
|
||||||
signal,
|
signal,
|
||||||
@ -235,12 +235,11 @@ export interface SlowTraceData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getFunnelSlowTraces = async (
|
export const getFunnelSlowTraces = async (
|
||||||
funnelId: string,
|
|
||||||
payload: FunnelOverviewPayload,
|
payload: FunnelOverviewPayload,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<SuccessResponse<SlowTraceData> | ErrorResponse> => {
|
): Promise<SuccessResponse<SlowTraceData> | ErrorResponse> => {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/slow-traces`,
|
`${FUNNELS_BASE_PATH}/analytics/slow-traces`,
|
||||||
payload,
|
payload,
|
||||||
{
|
{
|
||||||
signal,
|
signal,
|
||||||
@ -273,7 +272,7 @@ export const getFunnelErrorTraces = async (
|
|||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<SuccessResponse<ErrorTraceData> | ErrorResponse> => {
|
): Promise<SuccessResponse<ErrorTraceData> | ErrorResponse> => {
|
||||||
const response: AxiosResponse = await axios.post(
|
const response: AxiosResponse = await axios.post(
|
||||||
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/error-traces`,
|
`${FUNNELS_BASE_PATH}/analytics/error-traces`,
|
||||||
payload,
|
payload,
|
||||||
{
|
{
|
||||||
signal,
|
signal,
|
||||||
@ -291,6 +290,7 @@ export const getFunnelErrorTraces = async (
|
|||||||
export interface FunnelStepsPayload {
|
export interface FunnelStepsPayload {
|
||||||
start_time: number;
|
start_time: number;
|
||||||
end_time: number;
|
end_time: number;
|
||||||
|
steps: FunnelStepData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FunnelStepGraphMetrics {
|
export interface FunnelStepGraphMetrics {
|
||||||
@ -307,12 +307,11 @@ export interface FunnelStepsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getFunnelSteps = async (
|
export const getFunnelSteps = async (
|
||||||
funnelId: string,
|
|
||||||
payload: FunnelStepsPayload,
|
payload: FunnelStepsPayload,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<SuccessResponse<FunnelStepsResponse> | ErrorResponse> => {
|
): Promise<SuccessResponse<FunnelStepsResponse> | ErrorResponse> => {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/steps`,
|
`${FUNNELS_BASE_PATH}/analytics/steps`,
|
||||||
payload,
|
payload,
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
@ -330,6 +329,7 @@ export interface FunnelStepsOverviewPayload {
|
|||||||
end_time: number;
|
end_time: number;
|
||||||
step_start?: number;
|
step_start?: number;
|
||||||
step_end?: number;
|
step_end?: number;
|
||||||
|
steps: FunnelStepData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FunnelStepsOverviewResponse {
|
export interface FunnelStepsOverviewResponse {
|
||||||
@ -341,12 +341,11 @@ export interface FunnelStepsOverviewResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getFunnelStepsOverview = async (
|
export const getFunnelStepsOverview = async (
|
||||||
funnelId: string,
|
|
||||||
payload: FunnelStepsOverviewPayload,
|
payload: FunnelStepsOverviewPayload,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<SuccessResponse<FunnelStepsOverviewResponse> | ErrorResponse> => {
|
): Promise<SuccessResponse<FunnelStepsOverviewResponse> | ErrorResponse> => {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/steps/overview`,
|
`${FUNNELS_BASE_PATH}/analytics/steps/overview`,
|
||||||
payload,
|
payload,
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
|
|||||||
@ -30,5 +30,5 @@ export enum LOCALSTORAGE {
|
|||||||
SHOW_EXCEPTIONS_QUICK_FILTERS = 'SHOW_EXCEPTIONS_QUICK_FILTERS',
|
SHOW_EXCEPTIONS_QUICK_FILTERS = 'SHOW_EXCEPTIONS_QUICK_FILTERS',
|
||||||
BANNER_DISMISSED = 'BANNER_DISMISSED',
|
BANNER_DISMISSED = 'BANNER_DISMISSED',
|
||||||
QUICK_FILTERS_SETTINGS_ANNOUNCEMENT = 'QUICK_FILTERS_SETTINGS_ANNOUNCEMENT',
|
QUICK_FILTERS_SETTINGS_ANNOUNCEMENT = 'QUICK_FILTERS_SETTINGS_ANNOUNCEMENT',
|
||||||
UNEXECUTED_FUNNELS = 'UNEXECUTED_FUNNELS',
|
FUNNEL_STEPS = 'FUNNEL_STEPS',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -241,6 +241,15 @@
|
|||||||
&-title {
|
&-title {
|
||||||
color: var(--bg-ink-500);
|
color: var(--bg-ink-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-footer {
|
||||||
|
border-top-color: var(--bg-vanilla-300);
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
.add-span-to-funnel-modal__discard-button {
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
color: var(--bg-ink-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -72,7 +72,6 @@ function FunnelDetailsView({
|
|||||||
funnel={funnel}
|
funnel={funnel}
|
||||||
isTraceDetailsPage
|
isTraceDetailsPage
|
||||||
span={span}
|
span={span}
|
||||||
disableAutoSave
|
|
||||||
triggerAutoSave={triggerAutoSave}
|
triggerAutoSave={triggerAutoSave}
|
||||||
showNotifications={showNotifications}
|
showNotifications={showNotifications}
|
||||||
/>
|
/>
|
||||||
@ -143,13 +142,19 @@ function AddSpanToFunnelModal({
|
|||||||
const handleSaveFunnel = (): void => {
|
const handleSaveFunnel = (): void => {
|
||||||
setTriggerSave(true);
|
setTriggerSave(true);
|
||||||
// Reset trigger after a brief moment to allow the save to be processed
|
// Reset trigger after a brief moment to allow the save to be processed
|
||||||
setTimeout(() => setTriggerSave(false), 100);
|
setTimeout(() => {
|
||||||
|
setTriggerSave(false);
|
||||||
|
onClose();
|
||||||
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDiscard = (): void => {
|
const handleDiscard = (): void => {
|
||||||
setTriggerDiscard(true);
|
setTriggerDiscard(true);
|
||||||
// Reset trigger after a brief moment
|
// Reset trigger after a brief moment
|
||||||
setTimeout(() => setTriggerDiscard(false), 100);
|
setTimeout(() => {
|
||||||
|
setTriggerDiscard(false);
|
||||||
|
onClose();
|
||||||
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderListView = (): JSX.Element => (
|
const renderListView = (): JSX.Element => (
|
||||||
@ -239,9 +244,6 @@ function AddSpanToFunnelModal({
|
|||||||
footer={
|
footer={
|
||||||
activeView === ModalView.DETAILS
|
activeView === ModalView.DETAILS
|
||||||
? [
|
? [
|
||||||
<Button key="close" onClick={onClose}>
|
|
||||||
Close
|
|
||||||
</Button>,
|
|
||||||
<Button
|
<Button
|
||||||
type="default"
|
type="default"
|
||||||
key="discard"
|
key="discard"
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import useDebounce from 'hooks/useDebounce';
|
import useDebounce from 'hooks/useDebounce';
|
||||||
|
import { useLocalStorage } from 'hooks/useLocalStorage';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { useFunnelContext } from 'pages/TracesFunnels/FunnelContext';
|
import { useFunnelContext } from 'pages/TracesFunnels/FunnelContext';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useQueryClient } from 'react-query';
|
import { useQueryClient } from 'react-query';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { FunnelData, FunnelStepData } from 'types/api/traceFunnels';
|
import { FunnelData, FunnelStepData } from 'types/api/traceFunnels';
|
||||||
|
|
||||||
import { useUpdateFunnelSteps } from './useFunnels';
|
import { useUpdateFunnelSteps } from './useFunnels';
|
||||||
@ -13,22 +16,30 @@ interface UseFunnelConfiguration {
|
|||||||
isPopoverOpen: boolean;
|
isPopoverOpen: boolean;
|
||||||
setIsPopoverOpen: (isPopoverOpen: boolean) => void;
|
setIsPopoverOpen: (isPopoverOpen: boolean) => void;
|
||||||
steps: FunnelStepData[];
|
steps: FunnelStepData[];
|
||||||
|
isSaving: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add this helper function
|
// Add this helper function
|
||||||
const normalizeSteps = (steps: FunnelStepData[]): FunnelStepData[] => {
|
export const normalizeSteps = (steps: FunnelStepData[]): FunnelStepData[] => {
|
||||||
if (steps.some((step) => !step.filters)) return steps;
|
if (steps.some((step) => !step.filters)) return steps;
|
||||||
|
|
||||||
return steps.map((step) => ({
|
return steps.map((step) => ({
|
||||||
...step,
|
...step,
|
||||||
filters: {
|
filters: {
|
||||||
...step.filters,
|
...step.filters,
|
||||||
items: step.filters.items.map((item) => ({
|
items: step.filters.items.map((item) => {
|
||||||
|
const {
|
||||||
|
id: unusedId,
|
||||||
|
isIndexed,
|
||||||
|
...keyObj
|
||||||
|
} = item.key as BaseAutocompleteData;
|
||||||
|
return {
|
||||||
id: '',
|
id: '',
|
||||||
key: item.key,
|
key: keyObj,
|
||||||
value: item.value,
|
value: item.value,
|
||||||
op: item.op,
|
op: item.op,
|
||||||
})),
|
};
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
@ -36,22 +47,22 @@ const normalizeSteps = (steps: FunnelStepData[]): FunnelStepData[] => {
|
|||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
export default function useFunnelConfiguration({
|
export default function useFunnelConfiguration({
|
||||||
funnel,
|
funnel,
|
||||||
disableAutoSave = false,
|
|
||||||
triggerAutoSave = false,
|
triggerAutoSave = false,
|
||||||
showNotifications = false,
|
showNotifications = false,
|
||||||
}: {
|
}: {
|
||||||
funnel: FunnelData;
|
funnel: FunnelData;
|
||||||
disableAutoSave?: boolean;
|
|
||||||
triggerAutoSave?: boolean;
|
triggerAutoSave?: boolean;
|
||||||
showNotifications?: boolean;
|
showNotifications?: boolean;
|
||||||
}): UseFunnelConfiguration {
|
}): UseFunnelConfiguration {
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const {
|
const {
|
||||||
steps,
|
steps,
|
||||||
initialSteps,
|
lastUpdatedSteps,
|
||||||
hasIncompleteStepFields,
|
setLastUpdatedSteps,
|
||||||
handleRestoreSteps,
|
handleRestoreSteps,
|
||||||
handleRunFunnel,
|
selectedTime,
|
||||||
|
setIsUpdatingFunnel,
|
||||||
} = useFunnelContext();
|
} = useFunnelContext();
|
||||||
|
|
||||||
// State management
|
// State management
|
||||||
@ -59,10 +70,6 @@ export default function useFunnelConfiguration({
|
|||||||
|
|
||||||
const debouncedSteps = useDebounce(steps, 200);
|
const debouncedSteps = useDebounce(steps, 200);
|
||||||
|
|
||||||
const [lastValidatedSteps, setLastValidatedSteps] = useState<FunnelStepData[]>(
|
|
||||||
initialSteps,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mutation hooks
|
// Mutation hooks
|
||||||
const updateStepsMutation = useUpdateFunnelSteps(
|
const updateStepsMutation = useUpdateFunnelSteps(
|
||||||
funnel.funnel_id,
|
funnel.funnel_id,
|
||||||
@ -71,6 +78,15 @@ export default function useFunnelConfiguration({
|
|||||||
|
|
||||||
// Derived state
|
// Derived state
|
||||||
const lastSavedStepsStateRef = useRef<FunnelStepData[]>(steps);
|
const lastSavedStepsStateRef = useRef<FunnelStepData[]>(steps);
|
||||||
|
const hasRestoredFromLocalStorage = useRef(false);
|
||||||
|
|
||||||
|
// localStorage hook for funnel steps
|
||||||
|
const localStorageKey = `${LOCALSTORAGE.FUNNEL_STEPS}_${funnel.funnel_id}`;
|
||||||
|
const [
|
||||||
|
localStorageSavedSteps,
|
||||||
|
setLocalStorageSavedSteps,
|
||||||
|
clearLocalStorageSavedSteps,
|
||||||
|
] = useLocalStorage<FunnelStepData[] | null>(localStorageKey, null);
|
||||||
|
|
||||||
const hasStepsChanged = useCallback(() => {
|
const hasStepsChanged = useCallback(() => {
|
||||||
const normalizedLastSavedSteps = normalizeSteps(
|
const normalizedLastSavedSteps = normalizeSteps(
|
||||||
@ -80,6 +96,34 @@ export default function useFunnelConfiguration({
|
|||||||
return !isEqual(normalizedDebouncedSteps, normalizedLastSavedSteps);
|
return !isEqual(normalizedDebouncedSteps, normalizedLastSavedSteps);
|
||||||
}, [debouncedSteps]);
|
}, [debouncedSteps]);
|
||||||
|
|
||||||
|
// Handle localStorage for funnel steps
|
||||||
|
useEffect(() => {
|
||||||
|
// Restore from localStorage on first run if
|
||||||
|
if (!hasRestoredFromLocalStorage.current) {
|
||||||
|
const savedSteps = localStorageSavedSteps;
|
||||||
|
if (savedSteps) {
|
||||||
|
handleRestoreSteps(savedSteps);
|
||||||
|
hasRestoredFromLocalStorage.current = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save steps to localStorage
|
||||||
|
if (hasStepsChanged()) {
|
||||||
|
setLocalStorageSavedSteps(debouncedSteps);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
debouncedSteps,
|
||||||
|
funnel.funnel_id,
|
||||||
|
hasStepsChanged,
|
||||||
|
handleRestoreSteps,
|
||||||
|
localStorageSavedSteps,
|
||||||
|
setLocalStorageSavedSteps,
|
||||||
|
queryClient,
|
||||||
|
selectedTime,
|
||||||
|
lastUpdatedSteps,
|
||||||
|
]);
|
||||||
|
|
||||||
const hasFunnelStepDefinitionsChanged = useCallback(
|
const hasFunnelStepDefinitionsChanged = useCallback(
|
||||||
(prevSteps: FunnelStepData[], nextSteps: FunnelStepData[]): boolean => {
|
(prevSteps: FunnelStepData[], nextSteps: FunnelStepData[]): boolean => {
|
||||||
if (prevSteps.length !== nextSteps.length) return true;
|
if (prevSteps.length !== nextSteps.length) return true;
|
||||||
@ -97,15 +141,6 @@ export default function useFunnelConfiguration({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasFunnelLatencyTypeChanged = useCallback(
|
|
||||||
(prevSteps: FunnelStepData[], nextSteps: FunnelStepData[]): boolean =>
|
|
||||||
prevSteps.some((step, index) => {
|
|
||||||
const nextStep = nextSteps[index];
|
|
||||||
return step.latency_type !== nextStep.latency_type;
|
|
||||||
}),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mutation payload preparation
|
// Mutation payload preparation
|
||||||
const getUpdatePayload = useCallback(
|
const getUpdatePayload = useCallback(
|
||||||
() => ({
|
() => ({
|
||||||
@ -116,33 +151,19 @@ export default function useFunnelConfiguration({
|
|||||||
[funnel.funnel_id, debouncedSteps],
|
[funnel.funnel_id, debouncedSteps],
|
||||||
);
|
);
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { selectedTime } = useFunnelContext();
|
|
||||||
|
|
||||||
const validateStepsQueryKey = useMemo(
|
|
||||||
() => [REACT_QUERY_KEY.VALIDATE_FUNNEL_STEPS, funnel.funnel_id, selectedTime],
|
|
||||||
[funnel.funnel_id, selectedTime],
|
|
||||||
);
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Determine if we should save based on the mode
|
if (triggerAutoSave && !isEqual(debouncedSteps, lastUpdatedSteps)) {
|
||||||
let shouldSave = false;
|
setIsUpdatingFunnel(true);
|
||||||
|
|
||||||
if (disableAutoSave) {
|
|
||||||
// Manual save mode: only save when explicitly triggered
|
|
||||||
shouldSave = triggerAutoSave;
|
|
||||||
} else {
|
|
||||||
// Auto-save mode: save when steps have changed and no incomplete fields
|
|
||||||
shouldSave = hasStepsChanged() && !hasIncompleteStepFields;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldSave && !isEqual(debouncedSteps, lastValidatedSteps)) {
|
|
||||||
updateStepsMutation.mutate(getUpdatePayload(), {
|
updateStepsMutation.mutate(getUpdatePayload(), {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
const updatedFunnelSteps = data?.payload?.steps;
|
const updatedFunnelSteps = data?.payload?.steps;
|
||||||
|
|
||||||
if (!updatedFunnelSteps) return;
|
if (!updatedFunnelSteps) return;
|
||||||
|
|
||||||
|
// Clear localStorage since steps are saved successfully
|
||||||
|
clearLocalStorageSavedSteps();
|
||||||
|
|
||||||
queryClient.setQueryData(
|
queryClient.setQueryData(
|
||||||
[REACT_QUERY_KEY.GET_FUNNEL_DETAILS, funnel.funnel_id],
|
[REACT_QUERY_KEY.GET_FUNNEL_DETAILS, funnel.funnel_id],
|
||||||
(oldData: any) => {
|
(oldData: any) => {
|
||||||
@ -163,17 +184,9 @@ export default function useFunnelConfiguration({
|
|||||||
(step) => step.service_name === '' || step.span_name === '',
|
(step) => step.service_name === '' || step.span_name === '',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasFunnelLatencyTypeChanged(lastValidatedSteps, debouncedSteps)) {
|
|
||||||
handleRunFunnel();
|
|
||||||
setLastValidatedSteps(debouncedSteps);
|
|
||||||
}
|
|
||||||
// Only validate if funnel steps definitions
|
// Only validate if funnel steps definitions
|
||||||
else if (
|
if (!hasIncompleteStepFields) {
|
||||||
!hasIncompleteStepFields &&
|
setLastUpdatedSteps(debouncedSteps);
|
||||||
hasFunnelStepDefinitionsChanged(lastValidatedSteps, debouncedSteps)
|
|
||||||
) {
|
|
||||||
queryClient.refetchQueries(validateStepsQueryKey);
|
|
||||||
setLastValidatedSteps(debouncedSteps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show success notification only when requested
|
// Show success notification only when requested
|
||||||
@ -216,17 +229,18 @@ export default function useFunnelConfiguration({
|
|||||||
getUpdatePayload,
|
getUpdatePayload,
|
||||||
hasFunnelStepDefinitionsChanged,
|
hasFunnelStepDefinitionsChanged,
|
||||||
hasStepsChanged,
|
hasStepsChanged,
|
||||||
lastValidatedSteps,
|
lastUpdatedSteps,
|
||||||
queryClient,
|
queryClient,
|
||||||
validateStepsQueryKey,
|
|
||||||
triggerAutoSave,
|
triggerAutoSave,
|
||||||
showNotifications,
|
showNotifications,
|
||||||
disableAutoSave,
|
localStorageSavedSteps,
|
||||||
|
clearLocalStorageSavedSteps,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isPopoverOpen,
|
isPopoverOpen,
|
||||||
setIsPopoverOpen,
|
setIsPopoverOpen,
|
||||||
steps,
|
steps,
|
||||||
|
isSaving: updateStepsMutation.isLoading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,10 +20,11 @@ export function useFunnelMetrics({
|
|||||||
metricsData: MetricItem[];
|
metricsData: MetricItem[];
|
||||||
conversionRate: number;
|
conversionRate: number;
|
||||||
} {
|
} {
|
||||||
const { startTime, endTime } = useFunnelContext();
|
const { startTime, endTime, steps } = useFunnelContext();
|
||||||
const payload = {
|
const payload = {
|
||||||
start_time: startTime,
|
start_time: startTime,
|
||||||
end_time: endTime,
|
end_time: endTime,
|
||||||
|
steps,
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -81,6 +82,7 @@ export function useFunnelStepsMetrics({
|
|||||||
end_time: endTime,
|
end_time: endTime,
|
||||||
step_start: stepStart,
|
step_start: stepStart,
|
||||||
step_end: stepEnd,
|
step_end: stepEnd,
|
||||||
|
steps,
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
FunnelOverviewResponse,
|
FunnelOverviewResponse,
|
||||||
FunnelStepsOverviewPayload,
|
FunnelStepsOverviewPayload,
|
||||||
FunnelStepsOverviewResponse,
|
FunnelStepsOverviewResponse,
|
||||||
|
FunnelStepsPayload,
|
||||||
FunnelStepsResponse,
|
FunnelStepsResponse,
|
||||||
getFunnelById,
|
getFunnelById,
|
||||||
getFunnelErrorTraces,
|
getFunnelErrorTraces,
|
||||||
@ -37,6 +38,7 @@ import {
|
|||||||
CreateFunnelPayload,
|
CreateFunnelPayload,
|
||||||
CreateFunnelResponse,
|
CreateFunnelResponse,
|
||||||
FunnelData,
|
FunnelData,
|
||||||
|
FunnelStepData,
|
||||||
} from 'types/api/traceFunnels';
|
} from 'types/api/traceFunnels';
|
||||||
|
|
||||||
export const useFunnelsList = (): UseQueryResult<
|
export const useFunnelsList = (): UseQueryResult<
|
||||||
@ -117,12 +119,14 @@ export const useValidateFunnelSteps = ({
|
|||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
enabled,
|
enabled,
|
||||||
|
steps,
|
||||||
}: {
|
}: {
|
||||||
funnelId: string;
|
funnelId: string;
|
||||||
selectedTime: string;
|
selectedTime: string;
|
||||||
startTime: number;
|
startTime: number;
|
||||||
endTime: number;
|
endTime: number;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
steps: FunnelStepData[];
|
||||||
}): UseQueryResult<
|
}): UseQueryResult<
|
||||||
SuccessResponse<ValidateFunnelResponse> | ErrorResponse,
|
SuccessResponse<ValidateFunnelResponse> | ErrorResponse,
|
||||||
Error
|
Error
|
||||||
@ -130,11 +134,19 @@ export const useValidateFunnelSteps = ({
|
|||||||
useQuery({
|
useQuery({
|
||||||
queryFn: ({ signal }) =>
|
queryFn: ({ signal }) =>
|
||||||
validateFunnelSteps(
|
validateFunnelSteps(
|
||||||
funnelId,
|
{ start_time: startTime, end_time: endTime, steps },
|
||||||
{ start_time: startTime, end_time: endTime },
|
|
||||||
signal,
|
signal,
|
||||||
),
|
),
|
||||||
queryKey: [REACT_QUERY_KEY.VALIDATE_FUNNEL_STEPS, funnelId, selectedTime],
|
queryKey: [
|
||||||
|
REACT_QUERY_KEY.VALIDATE_FUNNEL_STEPS,
|
||||||
|
funnelId,
|
||||||
|
selectedTime,
|
||||||
|
steps.map((step) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
const { latency_type, ...rest } = step;
|
||||||
|
return rest;
|
||||||
|
}),
|
||||||
|
],
|
||||||
enabled,
|
enabled,
|
||||||
staleTime: 0,
|
staleTime: 0,
|
||||||
});
|
});
|
||||||
@ -168,18 +180,17 @@ export const useFunnelOverview = (
|
|||||||
const {
|
const {
|
||||||
selectedTime,
|
selectedTime,
|
||||||
validTracesCount,
|
validTracesCount,
|
||||||
hasFunnelBeenExecuted,
|
isUpdatingFunnel,
|
||||||
} = useFunnelContext();
|
} = useFunnelContext();
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryFn: ({ signal }) => getFunnelOverview(funnelId, payload, signal),
|
queryFn: ({ signal }) => getFunnelOverview(payload, signal),
|
||||||
queryKey: [
|
queryKey: [
|
||||||
REACT_QUERY_KEY.GET_FUNNEL_OVERVIEW,
|
REACT_QUERY_KEY.GET_FUNNEL_OVERVIEW,
|
||||||
funnelId,
|
funnelId,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
payload.step_start ?? '',
|
payload.steps,
|
||||||
payload.step_end ?? '',
|
|
||||||
],
|
],
|
||||||
enabled: !!funnelId && validTracesCount > 0 && hasFunnelBeenExecuted,
|
enabled: !!funnelId && validTracesCount > 0 && !isUpdatingFunnel,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -190,18 +201,19 @@ export const useFunnelSlowTraces = (
|
|||||||
const {
|
const {
|
||||||
selectedTime,
|
selectedTime,
|
||||||
validTracesCount,
|
validTracesCount,
|
||||||
hasFunnelBeenExecuted,
|
isUpdatingFunnel,
|
||||||
} = useFunnelContext();
|
} = useFunnelContext();
|
||||||
return useQuery<SuccessResponse<SlowTraceData> | ErrorResponse, Error>({
|
return useQuery<SuccessResponse<SlowTraceData> | ErrorResponse, Error>({
|
||||||
queryFn: ({ signal }) => getFunnelSlowTraces(funnelId, payload, signal),
|
queryFn: ({ signal }) => getFunnelSlowTraces(payload, signal),
|
||||||
queryKey: [
|
queryKey: [
|
||||||
REACT_QUERY_KEY.GET_FUNNEL_SLOW_TRACES,
|
REACT_QUERY_KEY.GET_FUNNEL_SLOW_TRACES,
|
||||||
funnelId,
|
funnelId,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
payload.step_start ?? '',
|
payload.step_start ?? '',
|
||||||
payload.step_end ?? '',
|
payload.step_end ?? '',
|
||||||
|
payload.steps,
|
||||||
],
|
],
|
||||||
enabled: !!funnelId && validTracesCount > 0 && hasFunnelBeenExecuted,
|
enabled: !!funnelId && validTracesCount > 0 && !isUpdatingFunnel,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -212,7 +224,7 @@ export const useFunnelErrorTraces = (
|
|||||||
const {
|
const {
|
||||||
selectedTime,
|
selectedTime,
|
||||||
validTracesCount,
|
validTracesCount,
|
||||||
hasFunnelBeenExecuted,
|
isUpdatingFunnel,
|
||||||
} = useFunnelContext();
|
} = useFunnelContext();
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryFn: ({ signal }) => getFunnelErrorTraces(funnelId, payload, signal),
|
queryFn: ({ signal }) => getFunnelErrorTraces(funnelId, payload, signal),
|
||||||
@ -222,35 +234,31 @@ export const useFunnelErrorTraces = (
|
|||||||
selectedTime,
|
selectedTime,
|
||||||
payload.step_start ?? '',
|
payload.step_start ?? '',
|
||||||
payload.step_end ?? '',
|
payload.step_end ?? '',
|
||||||
|
payload.steps,
|
||||||
],
|
],
|
||||||
enabled: !!funnelId && validTracesCount > 0 && hasFunnelBeenExecuted,
|
enabled: !!funnelId && validTracesCount > 0 && !isUpdatingFunnel,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useFunnelStepsGraphData(
|
export function useFunnelStepsGraphData(
|
||||||
funnelId: string,
|
funnelId: string,
|
||||||
|
payload: FunnelStepsPayload,
|
||||||
): UseQueryResult<SuccessResponse<FunnelStepsResponse> | ErrorResponse, Error> {
|
): UseQueryResult<SuccessResponse<FunnelStepsResponse> | ErrorResponse, Error> {
|
||||||
const {
|
const {
|
||||||
startTime,
|
|
||||||
endTime,
|
|
||||||
selectedTime,
|
selectedTime,
|
||||||
validTracesCount,
|
validTracesCount,
|
||||||
hasFunnelBeenExecuted,
|
isUpdatingFunnel,
|
||||||
} = useFunnelContext();
|
} = useFunnelContext();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryFn: ({ signal }) =>
|
queryFn: ({ signal }) => getFunnelSteps(payload, signal),
|
||||||
getFunnelSteps(
|
|
||||||
funnelId,
|
|
||||||
{ start_time: startTime, end_time: endTime },
|
|
||||||
signal,
|
|
||||||
),
|
|
||||||
queryKey: [
|
queryKey: [
|
||||||
REACT_QUERY_KEY.GET_FUNNEL_STEPS_GRAPH_DATA,
|
REACT_QUERY_KEY.GET_FUNNEL_STEPS_GRAPH_DATA,
|
||||||
funnelId,
|
funnelId,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
|
payload.steps,
|
||||||
],
|
],
|
||||||
enabled: !!funnelId && validTracesCount > 0 && hasFunnelBeenExecuted,
|
enabled: !!funnelId && validTracesCount > 0 && !isUpdatingFunnel,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,17 +272,18 @@ export const useFunnelStepsOverview = (
|
|||||||
const {
|
const {
|
||||||
selectedTime,
|
selectedTime,
|
||||||
validTracesCount,
|
validTracesCount,
|
||||||
hasFunnelBeenExecuted,
|
isUpdatingFunnel,
|
||||||
} = useFunnelContext();
|
} = useFunnelContext();
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryFn: ({ signal }) => getFunnelStepsOverview(funnelId, payload, signal),
|
queryFn: ({ signal }) => getFunnelStepsOverview(payload, signal),
|
||||||
queryKey: [
|
queryKey: [
|
||||||
REACT_QUERY_KEY.GET_FUNNEL_STEPS_OVERVIEW,
|
REACT_QUERY_KEY.GET_FUNNEL_STEPS_OVERVIEW,
|
||||||
funnelId,
|
funnelId,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
payload.step_start ?? '',
|
payload.step_start ?? '',
|
||||||
payload.step_end ?? '',
|
payload.step_end ?? '',
|
||||||
|
payload.steps,
|
||||||
],
|
],
|
||||||
enabled: !!funnelId && validTracesCount > 0 && hasFunnelBeenExecuted,
|
enabled: !!funnelId && validTracesCount > 0 && !isUpdatingFunnel,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import './DeleteFunnelStep.styles.scss';
|
|||||||
|
|
||||||
import SignozModal from 'components/SignozModal/SignozModal';
|
import SignozModal from 'components/SignozModal/SignozModal';
|
||||||
import { Trash2, X } from 'lucide-react';
|
import { Trash2, X } from 'lucide-react';
|
||||||
|
import { useFunnelContext } from 'pages/TracesFunnels/FunnelContext';
|
||||||
|
|
||||||
interface DeleteFunnelStepProps {
|
interface DeleteFunnelStepProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -14,8 +15,10 @@ function DeleteFunnelStep({
|
|||||||
onClose,
|
onClose,
|
||||||
onStepRemove,
|
onStepRemove,
|
||||||
}: DeleteFunnelStepProps): JSX.Element {
|
}: DeleteFunnelStepProps): JSX.Element {
|
||||||
|
const { handleRunFunnel } = useFunnelContext();
|
||||||
const handleStepRemoval = (): void => {
|
const handleStepRemoval = (): void => {
|
||||||
onStepRemove();
|
onStepRemove();
|
||||||
|
handleRunFunnel();
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
|||||||
import useFunnelConfiguration from 'hooks/TracesFunnels/useFunnelConfiguration';
|
import useFunnelConfiguration from 'hooks/TracesFunnels/useFunnelConfiguration';
|
||||||
import { PencilLine } from 'lucide-react';
|
import { PencilLine } from 'lucide-react';
|
||||||
import FunnelItemPopover from 'pages/TracesFunnels/components/FunnelsList/FunnelItemPopover';
|
import FunnelItemPopover from 'pages/TracesFunnels/components/FunnelsList/FunnelItemPopover';
|
||||||
|
import { useFunnelContext } from 'pages/TracesFunnels/FunnelContext';
|
||||||
import CopyToClipboard from 'periscope/components/CopyToClipboard';
|
import CopyToClipboard from 'periscope/components/CopyToClipboard';
|
||||||
import { memo, useState } from 'react';
|
import { memo, useState } from 'react';
|
||||||
import { Span } from 'types/api/trace/getTraceV2';
|
import { Span } from 'types/api/trace/getTraceV2';
|
||||||
@ -21,7 +22,6 @@ interface FunnelConfigurationProps {
|
|||||||
funnel: FunnelData;
|
funnel: FunnelData;
|
||||||
isTraceDetailsPage?: boolean;
|
isTraceDetailsPage?: boolean;
|
||||||
span?: Span;
|
span?: Span;
|
||||||
disableAutoSave?: boolean;
|
|
||||||
triggerAutoSave?: boolean;
|
triggerAutoSave?: boolean;
|
||||||
showNotifications?: boolean;
|
showNotifications?: boolean;
|
||||||
}
|
}
|
||||||
@ -30,15 +30,19 @@ function FunnelConfiguration({
|
|||||||
funnel,
|
funnel,
|
||||||
isTraceDetailsPage,
|
isTraceDetailsPage,
|
||||||
span,
|
span,
|
||||||
disableAutoSave,
|
|
||||||
triggerAutoSave,
|
triggerAutoSave,
|
||||||
showNotifications,
|
showNotifications,
|
||||||
}: FunnelConfigurationProps): JSX.Element {
|
}: FunnelConfigurationProps): JSX.Element {
|
||||||
const { isPopoverOpen, setIsPopoverOpen, steps } = useFunnelConfiguration({
|
const { triggerSave } = useFunnelContext();
|
||||||
|
const {
|
||||||
|
isPopoverOpen,
|
||||||
|
setIsPopoverOpen,
|
||||||
|
steps,
|
||||||
|
isSaving,
|
||||||
|
} = useFunnelConfiguration({
|
||||||
funnel,
|
funnel,
|
||||||
disableAutoSave,
|
triggerAutoSave: triggerAutoSave || triggerSave,
|
||||||
triggerAutoSave,
|
showNotifications: showNotifications || triggerSave,
|
||||||
showNotifications,
|
|
||||||
});
|
});
|
||||||
const [isDescriptionModalOpen, setIsDescriptionModalOpen] = useState<boolean>(
|
const [isDescriptionModalOpen, setIsDescriptionModalOpen] = useState<boolean>(
|
||||||
false,
|
false,
|
||||||
@ -106,7 +110,7 @@ function FunnelConfiguration({
|
|||||||
|
|
||||||
{!isTraceDetailsPage && (
|
{!isTraceDetailsPage && (
|
||||||
<>
|
<>
|
||||||
<StepsFooter stepsCount={steps.length} />
|
<StepsFooter stepsCount={steps.length} isSaving={isSaving || false} />
|
||||||
<AddFunnelDescriptionModal
|
<AddFunnelDescriptionModal
|
||||||
isOpen={isDescriptionModalOpen}
|
isOpen={isDescriptionModalOpen}
|
||||||
onClose={handleDescriptionModalClose}
|
onClose={handleDescriptionModalClose}
|
||||||
@ -122,7 +126,6 @@ function FunnelConfiguration({
|
|||||||
FunnelConfiguration.defaultProps = {
|
FunnelConfiguration.defaultProps = {
|
||||||
isTraceDetailsPage: false,
|
isTraceDetailsPage: false,
|
||||||
span: undefined,
|
span: undefined,
|
||||||
disableAutoSave: false,
|
|
||||||
triggerAutoSave: false,
|
triggerAutoSave: false,
|
||||||
showNotifications: false,
|
showNotifications: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
color: var(--bg-vanilla-400);
|
color: var(--bg-vanilla-400);
|
||||||
border: 1px solid var(--bg-slate-500);
|
border: 1px solid var(--bg-slate-500);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
width: 100%;
|
||||||
.step-popover {
|
.step-popover {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
width: 22px;
|
width: 22px;
|
||||||
|
|||||||
@ -40,11 +40,6 @@
|
|||||||
letter-spacing: 0.12px;
|
letter-spacing: 0.12px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
|
||||||
&--sync {
|
|
||||||
border: 1px solid var(--bg-slate-400);
|
|
||||||
background: var(--bg-ink-300);
|
|
||||||
color: var(--bg-vanilla-400);
|
|
||||||
}
|
|
||||||
&--run {
|
&--run {
|
||||||
background-color: var(--bg-robin-500);
|
background-color: var(--bg-robin-500);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,53 +1,14 @@
|
|||||||
import './StepsFooter.styles.scss';
|
import './StepsFooter.styles.scss';
|
||||||
|
|
||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { Button, Skeleton } from 'antd';
|
||||||
import { Button, Skeleton, Spin } from 'antd';
|
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import { Cone, Play, RefreshCcw } from 'lucide-react';
|
import { Check, Cone } from 'lucide-react';
|
||||||
import { useFunnelContext } from 'pages/TracesFunnels/FunnelContext';
|
import { useFunnelContext } from 'pages/TracesFunnels/FunnelContext';
|
||||||
import { useMemo } from 'react';
|
import { useIsMutating } from 'react-query';
|
||||||
import { useIsFetching, useIsMutating } from 'react-query';
|
|
||||||
|
|
||||||
const useFunnelResultsLoading = (): boolean => {
|
|
||||||
const { funnelId } = useFunnelContext();
|
|
||||||
|
|
||||||
const isFetchingFunnelOverview = useIsFetching({
|
|
||||||
queryKey: [REACT_QUERY_KEY.GET_FUNNEL_OVERVIEW, funnelId],
|
|
||||||
});
|
|
||||||
|
|
||||||
const isFetchingStepsGraphData = useIsFetching({
|
|
||||||
queryKey: [REACT_QUERY_KEY.GET_FUNNEL_STEPS_GRAPH_DATA, funnelId],
|
|
||||||
});
|
|
||||||
|
|
||||||
const isFetchingErrorTraces = useIsFetching({
|
|
||||||
queryKey: [REACT_QUERY_KEY.GET_FUNNEL_ERROR_TRACES, funnelId],
|
|
||||||
});
|
|
||||||
|
|
||||||
const isFetchingSlowTraces = useIsFetching({
|
|
||||||
queryKey: [REACT_QUERY_KEY.GET_FUNNEL_SLOW_TRACES, funnelId],
|
|
||||||
});
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
if (!funnelId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
!!isFetchingFunnelOverview ||
|
|
||||||
!!isFetchingStepsGraphData ||
|
|
||||||
!!isFetchingErrorTraces ||
|
|
||||||
!!isFetchingSlowTraces
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
funnelId,
|
|
||||||
isFetchingFunnelOverview,
|
|
||||||
isFetchingStepsGraphData,
|
|
||||||
isFetchingErrorTraces,
|
|
||||||
isFetchingSlowTraces,
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface StepsFooterProps {
|
interface StepsFooterProps {
|
||||||
stepsCount: number;
|
stepsCount: number;
|
||||||
|
isSaving: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ValidTracesCount(): JSX.Element {
|
function ValidTracesCount(): JSX.Element {
|
||||||
@ -93,21 +54,13 @@ function ValidTracesCount(): JSX.Element {
|
|||||||
return <span className="steps-footer__valid-traces">Valid traces found</span>;
|
return <span className="steps-footer__valid-traces">Valid traces found</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function StepsFooter({ stepsCount }: StepsFooterProps): JSX.Element {
|
function StepsFooter({ stepsCount, isSaving }: StepsFooterProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
validTracesCount,
|
hasIncompleteStepFields,
|
||||||
handleRunFunnel,
|
handleSaveFunnel,
|
||||||
hasFunnelBeenExecuted,
|
hasUnsavedChanges,
|
||||||
funnelId,
|
|
||||||
} = useFunnelContext();
|
} = useFunnelContext();
|
||||||
|
|
||||||
const isFunnelResultsLoading = useFunnelResultsLoading();
|
|
||||||
|
|
||||||
const isFunnelUpdateMutating = useIsMutating([
|
|
||||||
REACT_QUERY_KEY.UPDATE_FUNNEL_STEPS,
|
|
||||||
funnelId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="steps-footer">
|
<div className="steps-footer">
|
||||||
<div className="steps-footer__left">
|
<div className="steps-footer__left">
|
||||||
@ -117,38 +70,16 @@ function StepsFooter({ stepsCount }: StepsFooterProps): JSX.Element {
|
|||||||
<ValidTracesCount />
|
<ValidTracesCount />
|
||||||
</div>
|
</div>
|
||||||
<div className="steps-footer__right">
|
<div className="steps-footer__right">
|
||||||
{!!isFunnelUpdateMutating && (
|
|
||||||
<div className="steps-footer__button steps-footer__button--updating">
|
|
||||||
<Spin
|
|
||||||
indicator={<LoadingOutlined style={{ color: 'grey' }} />}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
Updating
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!hasFunnelBeenExecuted ? (
|
|
||||||
<Button
|
<Button
|
||||||
disabled={validTracesCount === 0}
|
disabled={hasIncompleteStepFields || !hasUnsavedChanges}
|
||||||
onClick={handleRunFunnel}
|
onClick={handleSaveFunnel}
|
||||||
type="primary"
|
type="primary"
|
||||||
className="steps-footer__button steps-footer__button--run"
|
className="steps-footer__button steps-footer__button--run"
|
||||||
icon={<Play size={16} />}
|
icon={<Check size={14} />}
|
||||||
|
loading={isSaving}
|
||||||
>
|
>
|
||||||
Run funnel
|
Save funnel
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
className="steps-footer__button steps-footer__button--sync"
|
|
||||||
icon={<RefreshCcw size={16} />}
|
|
||||||
onClick={handleRunFunnel}
|
|
||||||
loading={isFunnelResultsLoading}
|
|
||||||
disabled={validTracesCount === 0}
|
|
||||||
>
|
|
||||||
Refresh
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -29,13 +29,20 @@ Chart.register(
|
|||||||
);
|
);
|
||||||
|
|
||||||
function FunnelGraph(): JSX.Element {
|
function FunnelGraph(): JSX.Element {
|
||||||
const { funnelId } = useFunnelContext();
|
const { funnelId, startTime, endTime, steps } = useFunnelContext();
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
start_time: startTime,
|
||||||
|
end_time: endTime,
|
||||||
|
steps,
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: stepsData,
|
data: stepsData,
|
||||||
isLoading,
|
isLoading,
|
||||||
isFetching,
|
isFetching,
|
||||||
isError,
|
isError,
|
||||||
} = useFunnelStepsGraphData(funnelId);
|
} = useFunnelStepsGraphData(funnelId, payload);
|
||||||
|
|
||||||
const data = useMemo(() => stepsData?.payload?.data?.[0]?.data, [
|
const data = useMemo(() => stepsData?.payload?.data?.[0]?.data, [
|
||||||
stepsData?.payload?.data,
|
stepsData?.payload?.data,
|
||||||
|
|||||||
@ -16,7 +16,6 @@ function FunnelResults(): JSX.Element {
|
|||||||
isValidateStepsLoading,
|
isValidateStepsLoading,
|
||||||
hasIncompleteStepFields,
|
hasIncompleteStepFields,
|
||||||
hasAllEmptyStepFields,
|
hasAllEmptyStepFields,
|
||||||
hasFunnelBeenExecuted,
|
|
||||||
funnelId,
|
funnelId,
|
||||||
} = useFunnelContext();
|
} = useFunnelContext();
|
||||||
|
|
||||||
@ -47,14 +46,6 @@ function FunnelResults(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!hasFunnelBeenExecuted) {
|
|
||||||
return (
|
|
||||||
<EmptyFunnelResults
|
|
||||||
title="Funnel has not been run yet."
|
|
||||||
description="Run the funnel to see the results"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="funnel-results">
|
<div className="funnel-results">
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { useFunnelContext } from 'pages/TracesFunnels/FunnelContext';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { UseQueryResult } from 'react-query';
|
import { UseQueryResult } from 'react-query';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { FunnelStepData } from 'types/api/traceFunnels';
|
||||||
|
|
||||||
import FunnelTable from './FunnelTable';
|
import FunnelTable from './FunnelTable';
|
||||||
import { topTracesTableColumns } from './utils';
|
import { topTracesTableColumns } from './utils';
|
||||||
@ -24,6 +25,7 @@ interface FunnelTopTracesTableProps {
|
|||||||
SuccessResponse<SlowTraceData | ErrorTraceData> | ErrorResponse,
|
SuccessResponse<SlowTraceData | ErrorTraceData> | ErrorResponse,
|
||||||
Error
|
Error
|
||||||
>;
|
>;
|
||||||
|
steps: FunnelStepData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function FunnelTopTracesTable({
|
function FunnelTopTracesTable({
|
||||||
@ -32,6 +34,7 @@ function FunnelTopTracesTable({
|
|||||||
stepBOrder,
|
stepBOrder,
|
||||||
title,
|
title,
|
||||||
tooltip,
|
tooltip,
|
||||||
|
steps,
|
||||||
useQueryHook,
|
useQueryHook,
|
||||||
}: FunnelTopTracesTableProps): JSX.Element {
|
}: FunnelTopTracesTableProps): JSX.Element {
|
||||||
const { startTime, endTime } = useFunnelContext();
|
const { startTime, endTime } = useFunnelContext();
|
||||||
@ -41,8 +44,9 @@ function FunnelTopTracesTable({
|
|||||||
end_time: endTime,
|
end_time: endTime,
|
||||||
step_start: stepAOrder,
|
step_start: stepAOrder,
|
||||||
step_end: stepBOrder,
|
step_end: stepBOrder,
|
||||||
|
steps,
|
||||||
}),
|
}),
|
||||||
[startTime, endTime, stepAOrder, stepBOrder],
|
[startTime, endTime, stepAOrder, stepBOrder, steps],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: response, isLoading, isFetching } = useQueryHook(
|
const { data: response, isLoading, isFetching } = useQueryHook(
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import FunnelMetricsTable from './FunnelMetricsTable';
|
|||||||
function OverallMetrics(): JSX.Element {
|
function OverallMetrics(): JSX.Element {
|
||||||
const { funnelId } = useParams<{ funnelId: string }>();
|
const { funnelId } = useParams<{ funnelId: string }>();
|
||||||
const { isLoading, metricsData, conversionRate, isError } = useFunnelMetrics({
|
const { isLoading, metricsData, conversionRate, isError } = useFunnelMetrics({
|
||||||
funnelId: funnelId || '',
|
funnelId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -52,11 +52,13 @@ function StepsTransitionResults(): JSX.Element {
|
|||||||
funnelId={funnelId}
|
funnelId={funnelId}
|
||||||
stepAOrder={stepAOrder}
|
stepAOrder={stepAOrder}
|
||||||
stepBOrder={stepBOrder}
|
stepBOrder={stepBOrder}
|
||||||
|
steps={steps}
|
||||||
/>
|
/>
|
||||||
<TopTracesWithErrors
|
<TopTracesWithErrors
|
||||||
funnelId={funnelId}
|
funnelId={funnelId}
|
||||||
stepAOrder={stepAOrder}
|
stepAOrder={stepAOrder}
|
||||||
stepBOrder={stepBOrder}
|
stepBOrder={stepBOrder}
|
||||||
|
steps={steps}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useFunnelSlowTraces } from 'hooks/TracesFunnels/useFunnels';
|
import { useFunnelSlowTraces } from 'hooks/TracesFunnels/useFunnels';
|
||||||
|
import { FunnelStepData } from 'types/api/traceFunnels';
|
||||||
|
|
||||||
import FunnelTopTracesTable from './FunnelTopTracesTable';
|
import FunnelTopTracesTable from './FunnelTopTracesTable';
|
||||||
|
|
||||||
@ -6,6 +7,7 @@ interface TopSlowestTracesProps {
|
|||||||
funnelId: string;
|
funnelId: string;
|
||||||
stepAOrder: number;
|
stepAOrder: number;
|
||||||
stepBOrder: number;
|
stepBOrder: number;
|
||||||
|
steps: FunnelStepData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function TopSlowestTraces(props: TopSlowestTracesProps): JSX.Element {
|
function TopSlowestTraces(props: TopSlowestTracesProps): JSX.Element {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useFunnelErrorTraces } from 'hooks/TracesFunnels/useFunnels';
|
import { useFunnelErrorTraces } from 'hooks/TracesFunnels/useFunnels';
|
||||||
|
import { FunnelStepData } from 'types/api/traceFunnels';
|
||||||
|
|
||||||
import FunnelTopTracesTable from './FunnelTopTracesTable';
|
import FunnelTopTracesTable from './FunnelTopTracesTable';
|
||||||
|
|
||||||
@ -6,6 +7,7 @@ interface TopTracesWithErrorsProps {
|
|||||||
funnelId: string;
|
funnelId: string;
|
||||||
stepAOrder: number;
|
stepAOrder: number;
|
||||||
stepBOrder: number;
|
stepBOrder: number;
|
||||||
|
steps: FunnelStepData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function TopTracesWithErrors(props: TopTracesWithErrorsProps): JSX.Element {
|
function TopTracesWithErrors(props: TopTracesWithErrorsProps): JSX.Element {
|
||||||
|
|||||||
@ -18,10 +18,4 @@ export const topTracesTableColumns = [
|
|||||||
key: 'duration_ms',
|
key: 'duration_ms',
|
||||||
render: (value: string): string => getYAxisFormattedValue(value, 'ms'),
|
render: (value: string): string => getYAxisFormattedValue(value, 'ms'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'SPAN COUNT',
|
|
||||||
dataIndex: 'span_count',
|
|
||||||
key: 'span_count',
|
|
||||||
render: (value: number): string => value.toString(),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|||||||
@ -14,8 +14,6 @@ export const initialStepsData: FunnelStepData[] = [
|
|||||||
latency_pointer: 'start',
|
latency_pointer: 'start',
|
||||||
latency_type: undefined,
|
latency_type: undefined,
|
||||||
has_errors: false,
|
has_errors: false,
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: v4(),
|
id: v4(),
|
||||||
@ -29,8 +27,6 @@ export const initialStepsData: FunnelStepData[] = [
|
|||||||
latency_pointer: 'start',
|
latency_pointer: 'start',
|
||||||
latency_type: LatencyOptions.P95,
|
latency_type: LatencyOptions.P95,
|
||||||
has_errors: false,
|
has_errors: false,
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import { ValidateFunnelResponse } from 'api/traceFunnels';
|
import { ValidateFunnelResponse } from 'api/traceFunnels';
|
||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||||
import {
|
import {
|
||||||
CustomTimeType,
|
CustomTimeType,
|
||||||
Time as TimeV2,
|
Time as TimeV2,
|
||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||||
|
import { normalizeSteps } from 'hooks/TracesFunnels/useFunnelConfiguration';
|
||||||
import { useValidateFunnelSteps } from 'hooks/TracesFunnels/useFunnels';
|
import { useValidateFunnelSteps } from 'hooks/TracesFunnels/useFunnels';
|
||||||
import { useLocalStorage } from 'hooks/useLocalStorage';
|
|
||||||
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
||||||
|
import { isEqual } from 'lodash-es';
|
||||||
import { initialStepsData } from 'pages/TracesFunnelDetails/constants';
|
import { initialStepsData } from 'pages/TracesFunnelDetails/constants';
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
@ -41,6 +41,9 @@ interface FunnelContextType {
|
|||||||
handleStepChange: (index: number, newStep: Partial<FunnelStepData>) => void;
|
handleStepChange: (index: number, newStep: Partial<FunnelStepData>) => void;
|
||||||
handleStepRemoval: (index: number) => void;
|
handleStepRemoval: (index: number) => void;
|
||||||
handleRunFunnel: () => void;
|
handleRunFunnel: () => void;
|
||||||
|
handleSaveFunnel: () => void;
|
||||||
|
triggerSave: boolean;
|
||||||
|
hasUnsavedChanges: boolean;
|
||||||
validationResponse:
|
validationResponse:
|
||||||
| SuccessResponse<ValidateFunnelResponse>
|
| SuccessResponse<ValidateFunnelResponse>
|
||||||
| ErrorResponse
|
| ErrorResponse
|
||||||
@ -54,8 +57,10 @@ interface FunnelContextType {
|
|||||||
spanName: string,
|
spanName: string,
|
||||||
) => void;
|
) => void;
|
||||||
handleRestoreSteps: (oldSteps: FunnelStepData[]) => void;
|
handleRestoreSteps: (oldSteps: FunnelStepData[]) => void;
|
||||||
hasFunnelBeenExecuted: boolean;
|
isUpdatingFunnel: boolean;
|
||||||
setHasFunnelBeenExecuted: Dispatch<SetStateAction<boolean>>;
|
setIsUpdatingFunnel: Dispatch<SetStateAction<boolean>>;
|
||||||
|
lastUpdatedSteps: FunnelStepData[];
|
||||||
|
setLastUpdatedSteps: Dispatch<SetStateAction<FunnelStepData[]>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FunnelContext = createContext<FunnelContextType | undefined>(undefined);
|
const FunnelContext = createContext<FunnelContextType | undefined>(undefined);
|
||||||
@ -86,6 +91,19 @@ export function FunnelProvider({
|
|||||||
const funnel = data?.payload;
|
const funnel = data?.payload;
|
||||||
const initialSteps = funnel?.steps?.length ? funnel.steps : initialStepsData;
|
const initialSteps = funnel?.steps?.length ? funnel.steps : initialStepsData;
|
||||||
const [steps, setSteps] = useState<FunnelStepData[]>(initialSteps);
|
const [steps, setSteps] = useState<FunnelStepData[]>(initialSteps);
|
||||||
|
const [triggerSave, setTriggerSave] = useState<boolean>(false);
|
||||||
|
const [isUpdatingFunnel, setIsUpdatingFunnel] = useState<boolean>(false);
|
||||||
|
const [lastUpdatedSteps, setLastUpdatedSteps] = useState<FunnelStepData[]>(
|
||||||
|
initialSteps,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if there are unsaved changes by comparing with initial steps from API
|
||||||
|
const hasUnsavedChanges = useMemo(() => {
|
||||||
|
const normalizedCurrentSteps = normalizeSteps(steps);
|
||||||
|
const normalizedInitialSteps = normalizeSteps(lastUpdatedSteps);
|
||||||
|
return !isEqual(normalizedCurrentSteps, normalizedInitialSteps);
|
||||||
|
}, [steps, lastUpdatedSteps]);
|
||||||
|
|
||||||
const { hasIncompleteStepFields, hasAllEmptyStepFields } = useMemo(
|
const { hasIncompleteStepFields, hasAllEmptyStepFields } = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
hasAllEmptyStepFields: steps.every(
|
hasAllEmptyStepFields: steps.every(
|
||||||
@ -98,15 +116,6 @@ export function FunnelProvider({
|
|||||||
[steps],
|
[steps],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [unexecutedFunnels, setUnexecutedFunnels] = useLocalStorage<string[]>(
|
|
||||||
LOCALSTORAGE.UNEXECUTED_FUNNELS,
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [hasFunnelBeenExecuted, setHasFunnelBeenExecuted] = useState(
|
|
||||||
!unexecutedFunnels.includes(funnelId),
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: validationResponse,
|
data: validationResponse,
|
||||||
isLoading: isValidationLoading,
|
isLoading: isValidationLoading,
|
||||||
@ -116,7 +125,13 @@ export function FunnelProvider({
|
|||||||
selectedTime,
|
selectedTime,
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
enabled: !!funnelId && !!selectedTime && !!startTime && !!endTime,
|
enabled:
|
||||||
|
!!funnelId &&
|
||||||
|
!!selectedTime &&
|
||||||
|
!!startTime &&
|
||||||
|
!!endTime &&
|
||||||
|
!hasIncompleteStepFields,
|
||||||
|
steps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const validTracesCount = useMemo(
|
const validTracesCount = useMemo(
|
||||||
@ -185,11 +200,7 @@ export function FunnelProvider({
|
|||||||
|
|
||||||
const handleRunFunnel = useCallback(async (): Promise<void> => {
|
const handleRunFunnel = useCallback(async (): Promise<void> => {
|
||||||
if (validTracesCount === 0) return;
|
if (validTracesCount === 0) return;
|
||||||
if (!hasFunnelBeenExecuted) {
|
|
||||||
setUnexecutedFunnels(unexecutedFunnels.filter((id) => id !== funnelId));
|
|
||||||
|
|
||||||
setHasFunnelBeenExecuted(true);
|
|
||||||
}
|
|
||||||
queryClient.refetchQueries([
|
queryClient.refetchQueries([
|
||||||
REACT_QUERY_KEY.GET_FUNNEL_OVERVIEW,
|
REACT_QUERY_KEY.GET_FUNNEL_OVERVIEW,
|
||||||
funnelId,
|
funnelId,
|
||||||
@ -215,15 +226,13 @@ export function FunnelProvider({
|
|||||||
funnelId,
|
funnelId,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
]);
|
]);
|
||||||
}, [
|
}, [funnelId, queryClient, selectedTime, validTracesCount]);
|
||||||
funnelId,
|
|
||||||
hasFunnelBeenExecuted,
|
const handleSaveFunnel = useCallback(() => {
|
||||||
unexecutedFunnels,
|
setTriggerSave(true);
|
||||||
queryClient,
|
// Reset the trigger after a brief moment to allow useFunnelConfiguration to pick it up
|
||||||
selectedTime,
|
setTimeout(() => setTriggerSave(false), 100);
|
||||||
setUnexecutedFunnels,
|
}, []);
|
||||||
validTracesCount,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const value = useMemo<FunnelContextType>(
|
const value = useMemo<FunnelContextType>(
|
||||||
() => ({
|
() => ({
|
||||||
@ -239,14 +248,19 @@ export function FunnelProvider({
|
|||||||
handleAddStep: addNewStep,
|
handleAddStep: addNewStep,
|
||||||
handleStepRemoval,
|
handleStepRemoval,
|
||||||
handleRunFunnel,
|
handleRunFunnel,
|
||||||
|
handleSaveFunnel,
|
||||||
|
triggerSave,
|
||||||
validationResponse,
|
validationResponse,
|
||||||
isValidateStepsLoading: isValidationLoading || isValidationFetching,
|
isValidateStepsLoading: isValidationLoading || isValidationFetching,
|
||||||
hasIncompleteStepFields,
|
hasIncompleteStepFields,
|
||||||
hasAllEmptyStepFields,
|
hasAllEmptyStepFields,
|
||||||
handleReplaceStep,
|
handleReplaceStep,
|
||||||
handleRestoreSteps,
|
handleRestoreSteps,
|
||||||
hasFunnelBeenExecuted,
|
hasUnsavedChanges,
|
||||||
setHasFunnelBeenExecuted,
|
setIsUpdatingFunnel,
|
||||||
|
isUpdatingFunnel,
|
||||||
|
lastUpdatedSteps,
|
||||||
|
setLastUpdatedSteps,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
funnelId,
|
funnelId,
|
||||||
@ -260,6 +274,8 @@ export function FunnelProvider({
|
|||||||
addNewStep,
|
addNewStep,
|
||||||
handleStepRemoval,
|
handleStepRemoval,
|
||||||
handleRunFunnel,
|
handleRunFunnel,
|
||||||
|
handleSaveFunnel,
|
||||||
|
triggerSave,
|
||||||
validationResponse,
|
validationResponse,
|
||||||
isValidationLoading,
|
isValidationLoading,
|
||||||
isValidationFetching,
|
isValidationFetching,
|
||||||
@ -267,8 +283,11 @@ export function FunnelProvider({
|
|||||||
hasAllEmptyStepFields,
|
hasAllEmptyStepFields,
|
||||||
handleReplaceStep,
|
handleReplaceStep,
|
||||||
handleRestoreSteps,
|
handleRestoreSteps,
|
||||||
hasFunnelBeenExecuted,
|
hasUnsavedChanges,
|
||||||
setHasFunnelBeenExecuted,
|
setIsUpdatingFunnel,
|
||||||
|
isUpdatingFunnel,
|
||||||
|
lastUpdatedSteps,
|
||||||
|
setLastUpdatedSteps,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -4,11 +4,9 @@ import { Input } from 'antd';
|
|||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import SignozModal from 'components/SignozModal/SignozModal';
|
import SignozModal from 'components/SignozModal/SignozModal';
|
||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { useCreateFunnel } from 'hooks/TracesFunnels/useFunnels';
|
import { useCreateFunnel } from 'hooks/TracesFunnels/useFunnels';
|
||||||
import { useLocalStorage } from 'hooks/useLocalStorage';
|
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||||
import { Check, X } from 'lucide-react';
|
import { Check, X } from 'lucide-react';
|
||||||
@ -34,11 +32,6 @@ function CreateFunnel({
|
|||||||
const { safeNavigate } = useSafeNavigate();
|
const { safeNavigate } = useSafeNavigate();
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
const [unexecutedFunnels, setUnexecutedFunnels] = useLocalStorage<string[]>(
|
|
||||||
LOCALSTORAGE.UNEXECUTED_FUNNELS,
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCreate = (): void => {
|
const handleCreate = (): void => {
|
||||||
createFunnelMutation.mutate(
|
createFunnelMutation.mutate(
|
||||||
{
|
{
|
||||||
@ -61,9 +54,6 @@ function CreateFunnel({
|
|||||||
queryClient.invalidateQueries([REACT_QUERY_KEY.GET_FUNNELS_LIST]);
|
queryClient.invalidateQueries([REACT_QUERY_KEY.GET_FUNNELS_LIST]);
|
||||||
|
|
||||||
const funnelId = data?.payload?.funnel_id;
|
const funnelId = data?.payload?.funnel_id;
|
||||||
if (funnelId) {
|
|
||||||
setUnexecutedFunnels([...unexecutedFunnels, funnelId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose(funnelId);
|
onClose(funnelId);
|
||||||
if (funnelId && redirectToDetails) {
|
if (funnelId && redirectToDetails) {
|
||||||
|
|||||||
@ -2,13 +2,16 @@ import '../RenameFunnel/RenameFunnel.styles.scss';
|
|||||||
import './DeleteFunnel.styles.scss';
|
import './DeleteFunnel.styles.scss';
|
||||||
|
|
||||||
import SignozModal from 'components/SignozModal/SignozModal';
|
import SignozModal from 'components/SignozModal/SignozModal';
|
||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { useDeleteFunnel } from 'hooks/TracesFunnels/useFunnels';
|
import { useDeleteFunnel } from 'hooks/TracesFunnels/useFunnels';
|
||||||
|
import { useLocalStorage } from 'hooks/useLocalStorage';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { Trash2, X } from 'lucide-react';
|
import { Trash2, X } from 'lucide-react';
|
||||||
import { useQueryClient } from 'react-query';
|
import { useQueryClient } from 'react-query';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { FunnelStepData } from 'types/api/traceFunnels';
|
||||||
|
|
||||||
interface DeleteFunnelProps {
|
interface DeleteFunnelProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -29,6 +32,13 @@ function DeleteFunnel({
|
|||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { pathname } = history.location;
|
const { pathname } = history.location;
|
||||||
|
|
||||||
|
// localStorage hook for funnel steps
|
||||||
|
const localStorageKey = `${LOCALSTORAGE.FUNNEL_STEPS}_${funnelId}`;
|
||||||
|
const [, , clearLocalStorageSavedSteps] = useLocalStorage<
|
||||||
|
FunnelStepData[] | null
|
||||||
|
>(localStorageKey, null);
|
||||||
|
|
||||||
const handleDelete = (): void => {
|
const handleDelete = (): void => {
|
||||||
deleteFunnelMutation.mutate(
|
deleteFunnelMutation.mutate(
|
||||||
{
|
{
|
||||||
@ -39,6 +49,7 @@ function DeleteFunnel({
|
|||||||
notifications.success({
|
notifications.success({
|
||||||
message: 'Funnel deleted successfully',
|
message: 'Funnel deleted successfully',
|
||||||
});
|
});
|
||||||
|
clearLocalStorageSavedSteps();
|
||||||
onClose();
|
onClose();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user