diff --git a/src/components/Errors/ErrorDetails/ErrorDetailsCardContent/FlowStack/index.tsx b/src/components/Errors/ErrorDetails/ErrorDetailsCardContent/FlowStack/index.tsx index 2b58b7c49..f75a86084 100644 --- a/src/components/Errors/ErrorDetails/ErrorDetailsCardContent/FlowStack/index.tsx +++ b/src/components/Errors/ErrorDetails/ErrorDetailsCardContent/FlowStack/index.tsx @@ -4,7 +4,8 @@ import { DataFetcherConfiguration, useFetchData } from "../../../../../hooks/useFetchData"; -import { usePersistence } from "../../../../../hooks/usePersistence"; +import { useErrorsSelector } from "../../../../../store/errors/useErrorsSelector"; +import { useStore } from "../../../../../store/useStore"; import { isNull } from "../../../../../typeGuards/isNull"; import { isString } from "../../../../../typeGuards/isString"; import { sendUserActionTrackingEvent } from "../../../../../utils/actions/sendUserActionTrackingEvent"; @@ -27,13 +28,7 @@ import { import { SpanFrameGroup } from "./SpanFrameGroup"; import { FrameItemCodeLocation } from "./SpanFrameGroup/types"; import * as s from "./styles"; -import { - FlowProps as FlowStackProps, - ShowOnlyWorkspaceErrorStackTraceItemsPayload -} from "./types"; - -const SHOW_ONLY_WORKSPACE_ERROR_STACK_TRACE_ITEMS_PERSISTENCE_KEY = - "showOnlyWorkspaceErrorStackTraceItems"; +import { FlowProps as FlowStackProps } from "./types"; const filesURIsDataFetcherConfiguration: DataFetcherConfiguration = { requestAction: actions.GET_FILES_URIS, @@ -42,15 +37,9 @@ const filesURIsDataFetcherConfiguration: DataFetcherConfiguration = { }; export const FlowStack = ({ data }: FlowStackProps) => { - const [persistedShowWorkspaceItemsOnly, setPersistedShowWorkspaceItemsOnly] = - usePersistence( - SHOW_ONLY_WORKSPACE_ERROR_STACK_TRACE_ITEMS_PERSISTENCE_KEY, - "application" - ); - const showWorkspaceOnly = useMemo( - () => Boolean(persistedShowWorkspaceItemsOnly?.value), - [persistedShowWorkspaceItemsOnly] - ); + const { errorDetailsWorkspaceItemsOnly: showWorkspaceOnly } = + useErrorsSelector(); + const { setErrorDetailsWorkspaceItemsOnly } = useStore.getState(); const stacksContainerRef = useRef(null); const frameStacks = useMemo( @@ -123,10 +112,7 @@ export const FlowStack = ({ data }: FlowStackProps) => { sendUserActionTrackingEvent(trackingEvents.WORKSPACE_ONLY_TOGGLE_SWITCHED, { value }); - - setPersistedShowWorkspaceItemsOnly({ - value - }); + setErrorDetailsWorkspaceItemsOnly(value); }; const handleFrameItemLinkClick = ({ diff --git a/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx b/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx index 0e94ed7e6..e2c829eed 100644 --- a/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx +++ b/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx @@ -95,74 +95,83 @@ export const OccurrenceChart = ({ Occurrence over time - {chartData?.dailyOccurrence && ( - - - { - if (!offset.height || !isNumber(offset.top)) { - return []; - } - let linesCount = 4; - const lines = []; - const maxTickTopOffset = offset.height + offset.top; - const interval = Math.floor(offset.height / linesCount); - let top = maxTickTopOffset - interval; - - while (linesCount) { - lines.push(top); - linesCount--; - top -= interval; - } - - return lines; - }} - /> - format(new Date(x), "MM/dd")} - /> - - - { - const payload = x.payload; - - if (!payload || payload.length === 0) { - return; - } - - const { date, value } = payload[0] - .payload as ErrorOccurrenceRecord; - - return ( - - Occurrences: {value} - {format(new Date(date), "MM/dd/yyyy")} - - ); - }} - isAnimationActive={false} - /> - - + {chartData?.dailyOccurrence ? ( + chartData.dailyOccurrence.length > 0 ? ( + + + { + if (!offset.height || !isNumber(offset.top)) { + return []; + } + let linesCount = 4; + const lines = []; + const maxTickTopOffset = offset.height + offset.top; + const interval = Math.floor(offset.height / linesCount); + let top = maxTickTopOffset - interval; + + while (linesCount) { + lines.push(top); + linesCount--; + top -= interval; + } + + return lines; + }} + /> + format(new Date(x), "MM/dd")} + /> + + + { + const payload = x.payload; + + if (!payload || payload.length === 0) { + return; + } + + const { date, value } = payload[0] + .payload as ErrorOccurrenceRecord; + + return ( + + Occurrences: {value} + {format(new Date(date), "MM/dd/yyyy")} + + ); + }} + isAnimationActive={false} + /> + + + ) : ( + No data + ) + ) : ( + Loading... )} ); diff --git a/src/components/Errors/NewErrorCard/OccurrenceChart/styles.ts b/src/components/Errors/NewErrorCard/OccurrenceChart/styles.ts index 1655a5a96..5720b75b5 100644 --- a/src/components/Errors/NewErrorCard/OccurrenceChart/styles.ts +++ b/src/components/Errors/NewErrorCard/OccurrenceChart/styles.ts @@ -32,3 +32,11 @@ export const TooltipContainer = styled(Tooltip)` flex-direction: column; gap: 8px; `; + +export const EmptyStateContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + text-align: center; + flex-grow: 1; +`; diff --git a/src/components/Errors/index.tsx b/src/components/Errors/index.tsx index 919911953..be0f16b70 100644 --- a/src/components/Errors/index.tsx +++ b/src/components/Errors/index.tsx @@ -1,7 +1,13 @@ +import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import { getFeatureFlagValue } from "../../featureFlags"; +import { usePersistence } from "../../hooks/usePersistence"; +import { usePrevious } from "../../hooks/usePrevious"; import { useConfigSelector } from "../../store/config/useConfigSelector"; +import { useErrorsSelector } from "../../store/errors/useErrorsSelector"; +import { useStore } from "../../store/useStore"; import { trackingEvents as globalEvents } from "../../trackingEvents"; +import { isUndefined } from "../../typeGuards/isUndefined"; import { FeatureFlag } from "../../types"; import { sendUserActionTrackingEvent } from "../../utils/actions/sendUserActionTrackingEvent"; import { ErrorIcon } from "../common/icons/16px/ErrorIcon"; @@ -10,12 +16,34 @@ import { NewEmptyState } from "../common/v3/NewEmptyState"; import { useHistory } from "../Main/useHistory"; import { TAB_IDS } from "../Navigation/Tabs/types"; import { ErrorDetails } from "./ErrorDetails"; +import { ShowOnlyWorkspaceErrorStackTraceItemsPayload } from "./ErrorDetails/ErrorDetailsCardContent/FlowStack/types"; import { ErrorsList } from "./ErrorsList"; import { GlobalErrorsList } from "./GlobalErrorsList"; import * as s from "./styles"; +const SHOW_ONLY_WORKSPACE_ERROR_STACK_TRACE_ITEMS_PERSISTENCE_KEY = + "showOnlyWorkspaceErrorStackTraceItems"; + export const Errors = () => { + const [persistedShowWorkspaceItemsOnly, setPersistedShowWorkspaceItemsOnly] = + usePersistence( + SHOW_ONLY_WORKSPACE_ERROR_STACK_TRACE_ITEMS_PERSISTENCE_KEY, + "application" + ); + const [ + isErrorDetailsWorkspaceItemsOnlyRehydrated, + setIsErrorDetailsWorkspaceItemsOnlyRehydrated + ] = useState(false); + + const previousPersistedShowWorkspaceItemsOnly = usePrevious( + persistedShowWorkspaceItemsOnly + ); const { scope, backendInfo } = useConfigSelector(); + const { errorDetailsWorkspaceItemsOnly } = useErrorsSelector(); + const previousErrorDetailsWorkspaceItemsOnly = usePrevious( + errorDetailsWorkspaceItemsOnly + ); + const { setErrorDetailsWorkspaceItemsOnly } = useStore.getState(); const spanCodeObjectId = scope?.span?.spanCodeObjectId; const methodId = scope?.span?.methodId ?? undefined; const { goTo } = useHistory(); @@ -26,6 +54,41 @@ export const Errors = () => { FeatureFlag.ARE_GLOBAL_ERRORS_ENABLED ); + // Rehydrate "Workspace only" toggle value from persistence + useEffect(() => { + if ( + isUndefined(previousPersistedShowWorkspaceItemsOnly) && + !isUndefined(persistedShowWorkspaceItemsOnly) + ) { + setErrorDetailsWorkspaceItemsOnly( + Boolean(persistedShowWorkspaceItemsOnly?.value) + ); + setIsErrorDetailsWorkspaceItemsOnlyRehydrated(true); + } + }, [ + persistedShowWorkspaceItemsOnly, + previousPersistedShowWorkspaceItemsOnly, + setErrorDetailsWorkspaceItemsOnly + ]); + + // Persist "Workspace only" toggle value on its change + useEffect(() => { + if ( + previousErrorDetailsWorkspaceItemsOnly !== + errorDetailsWorkspaceItemsOnly && + isErrorDetailsWorkspaceItemsOnlyRehydrated + ) { + setPersistedShowWorkspaceItemsOnly({ + value: errorDetailsWorkspaceItemsOnly + }); + } + }, [ + previousErrorDetailsWorkspaceItemsOnly, + errorDetailsWorkspaceItemsOnly, + setPersistedShowWorkspaceItemsOnly, + isErrorDetailsWorkspaceItemsOnlyRehydrated + ]); + const handleErrorSelect = (errorId: string) => { goTo(errorId); }; diff --git a/src/store/errors/errorsSlice.ts b/src/store/errors/errorsSlice.ts index baf7cb304..24bcb60ab 100644 --- a/src/store/errors/errorsSlice.ts +++ b/src/store/errors/errorsSlice.ts @@ -40,6 +40,7 @@ export interface ErrorsState { globalErrorsSorting: GLOBAL_ERROR_SORTING_CRITERION; globalErrorsFilters: GlobalErrorsFiltersState; globalErrorsSelectedFilters: GlobalErrorsSelectedFiltersState; + errorDetailsWorkspaceItemsOnly: boolean; } const selectedFiltersInitialState = { @@ -50,7 +51,7 @@ const selectedFiltersInitialState = { handlingTypes: [] }; -export const initialState: ErrorsState = { +export const globalErrorsInitialState = { globalErrorsList: null, globalErrorsTotalCount: 0, areGlobalErrorsLoading: false, @@ -66,6 +67,11 @@ export const initialState: ErrorsState = { globalErrorsSelectedFilters: selectedFiltersInitialState }; +export const initialState: ErrorsState = { + ...globalErrorsInitialState, + errorDetailsWorkspaceItemsOnly: false +}; + const set = (update: Partial) => (state: ErrorsState) => ({ ...state, ...update @@ -96,6 +102,9 @@ export const errorsSlice = createSlice({ ) => set({ globalErrorsSelectedFilters: filters }), resetGlobalErrorsSelectedFilters: () => set({ globalErrorsSelectedFilters: selectedFiltersInitialState }), - resetGlobalErrors: () => set(initialState) + setErrorDetailsWorkspaceItemsOnly: ( + errorDetailsWorkspaceItemsOnly: boolean + ) => set({ errorDetailsWorkspaceItemsOnly }), + resetGlobalErrors: () => set({ ...globalErrorsInitialState }) } });