import React, { useEffect } from 'react'; import 'react-dom'; import { useAppDispatch, useAppSelector } from '@webapp/redux/hooks'; import Box from '@webapp/ui/Box'; import { FlamegraphRenderer } from '@pyroscope/flamegraph/src/FlamegraphRenderer'; import { fetchSingleView, setQuery, selectQueries, setDateRange, selectAnnotationsOrDefault, addAnnotation, actions, fetchTagValues, } from '@webapp/redux/reducers/continuous'; import useColorMode from '@webapp/hooks/colorMode.hook'; import TimelineChartWrapper from '@webapp/components/TimelineChart/TimelineChartWrapper'; import Toolbar from '@webapp/components/Toolbar'; import ExportData from '@webapp/components/ExportData'; import ChartTitle from '@webapp/components/ChartTitle'; import useExportToFlamegraphDotCom from '@webapp/components/exportToFlamegraphDotCom.hook'; import TagsBar from '@webapp/components/TagsBar'; import useTimeZone from '@webapp/hooks/timeZone.hook'; import PageTitle from '@webapp/components/PageTitle'; import { ContextMenuProps } from '@webapp/components/TimelineChart/ContextMenu.plugin'; import { getFormatter } from '@pyroscope/flamegraph/src/format/format'; import { LoadingOverlay } from '@webapp/ui/LoadingOverlay'; import { TooltipCallbackProps } from '@webapp/components/TimelineChart/Tooltip.plugin'; import { Profile } from '@pyroscope/models/src'; import { isExportToFlamegraphDotComEnabled, isAnnotationsEnabled, } from '@webapp/util/features'; import useTags from '@webapp/hooks/tags.hook'; import { TimelineTooltip, TimelineTooltipProps, } from '@webapp/components/TimelineTooltip'; import { formatTitle } from './formatTitle'; import ContextMenu from './continuous/contextMenu/ContextMenu'; import AddAnnotationMenuItem from './continuous/contextMenu/AddAnnotation.menuitem'; import { isLoadingOrReloading } from './loading'; function ContinuousSingleView() { const dispatch = useAppDispatch(); const { offset } = useTimeZone(); const { colorMode } = useColorMode(); const { query } = useAppSelector(selectQueries); const tags = useTags().regularTags; const { from, until, refreshToken, maxNodes } = useAppSelector( (state) => state.continuous ); const { singleView } = useAppSelector((state) => state.continuous); const annotations = useAppSelector(selectAnnotationsOrDefault('singleView')); useEffect(() => { if (from && until && query && maxNodes) { const fetchData = dispatch(fetchSingleView(null)); return () => fetchData.abort('cancel'); } return undefined; }, [from, until, query, refreshToken, maxNodes]); const getRaw = () => { switch (singleView.type) { case 'loaded': case 'reloading': { return singleView.profile; } default: { return undefined; } } }; const exportToFlamegraphDotComFn = useExportToFlamegraphDotCom(getRaw()); const flamegraphRenderer = (() => { switch (singleView.type) { case 'loaded': case 'reloading': { return ( } /> ); } default: { return 'Loading'; } } })(); const getTimeline = () => { switch (singleView.type) { case 'loaded': case 'reloading': { return { data: singleView.timeline, color: colorMode === 'light' ? '#3b78e7' : undefined, }; } default: { return { data: undefined, }; } } }; const contextMenu = (props: ContextMenuProps) => { if (!isAnnotationsEnabled) { return null; } return ( { dispatch( addAnnotation({ appName: query, timestamp: props.timestamp, content, }) ); }} /> ); }; return (
{ dispatch(setQuery(query)); }} /> dispatch(actions.refresh())} onSetQuery={(q) => dispatch(actions.setQuery(q))} onSelectedLabel={(label, query) => { dispatch(fetchTagValues({ query, label })); }} /> dispatch(setDateRange({ from, until })) } height="125px" title={ } annotations={annotations} selectionType="single" ContextMenu={contextMenu} onHoverDisplayTooltip={(data) => createTooltip(query, data, singleView.profile) } /> {flamegraphRenderer}
); } function createTooltip( query: string, data: TooltipCallbackProps, profile?: Profile ) { if (!profile) { return null; } const values = prepareTimelineTooltipContent(profile, query, data); if (values.length <= 0) { return null; } return ; } // Converts data from TimelineChartWrapper into TimelineTooltip function prepareTimelineTooltipContent( profile: Profile, query: string, data: TooltipCallbackProps ): TimelineTooltipProps['items'] { const formatter = getFormatter( profile.flamebearer.numTicks, profile.metadata.sampleRate, profile.metadata.units ); // Filter non empty values return ( data.values .map((a) => { return { label: query, // TODO: horrible API value: a?.closest?.[1], }; }) // Sometimes closest is null .filter((a) => { return a.value; }) .map((a) => { return { ...a, value: formatter.format(a.value, profile.metadata.sampleRate, true), }; }) ); } export default ContinuousSingleView;