diff --git a/src/components/Admin/Home/Overview/TopIssuesWidget/index.tsx b/src/components/Admin/Home/Overview/TopIssuesWidget/index.tsx index 594128a7f..4e84e1d34 100644 --- a/src/components/Admin/Home/Overview/TopIssuesWidget/index.tsx +++ b/src/components/Admin/Home/Overview/TopIssuesWidget/index.tsx @@ -24,7 +24,8 @@ export const TopIssuesWidget = ({ onGetIssues }: TopIssuesWidgetProps) => { query: { environment: environmentId ?? undefined }, - limit: ISSUES_LIMIT + limit: ISSUES_LIMIT, + title: "Top issues by criticality" }); }; @@ -34,7 +35,8 @@ export const TopIssuesWidget = ({ onGetIssues }: TopIssuesWidgetProps) => { environment: environmentId ?? undefined, sortBy: "severity" }, - limit: ISSUES_LIMIT + limit: ISSUES_LIMIT, + title: "Top issues by severity" }); }; diff --git a/src/components/Admin/common/IssuesSidebarOverlay/IssuesSidebar/index.tsx b/src/components/Admin/common/IssuesSidebarOverlay/IssuesSidebar/index.tsx index 6107fb2f5..0e80c77ce 100644 --- a/src/components/Admin/common/IssuesSidebarOverlay/IssuesSidebar/index.tsx +++ b/src/components/Admin/common/IssuesSidebarOverlay/IssuesSidebar/index.tsx @@ -5,8 +5,12 @@ import { useAdminDispatch, useAdminSelector } from "../../../../../containers/Admin/hooks"; -import { useGetIssuesQuery } from "../../../../../redux/services/digma"; +import { + useGetAboutQuery, + useGetIssuesQuery +} from "../../../../../redux/services/digma"; import { setIsInsightJiraTicketHintShown } from "../../../../../redux/slices/persistSlice"; +import { useConfigSelector } from "../../../../../store/config/useConfigSelector"; import { isUndefined } from "../../../../../typeGuards/isUndefined"; import type { Scope } from "../../../../common/App/types"; import { CrossIcon } from "../../../../common/icons/16px/CrossIcon"; @@ -58,8 +62,10 @@ export const IssuesSidebar = ({ onResizeHandleMouseDown, query, scopeDisplayName, - isPaginationEnabled = true + isPaginationEnabled = true, + title = "Issues" }: IssuesSidebarProps) => { + const { jaegerURL } = useConfigSelector(); const [infoToOpenJiraTicket, setInfoToOpenJiraTicket] = useState>(); const [viewMode, setViewMode] = useState(ViewMode.All); @@ -74,8 +80,8 @@ export const IssuesSidebar = ({ ); const isDrawerOpen = Boolean(insightIdToOpenSuggestion); const issuesListRef = useRef(null); - const theme = useTheme(); + const { data: about } = useGetAboutQuery(); const { data, isFetching, refetch } = useGetIssuesQuery( { showDismissed: viewMode === ViewMode.OnlyDismissed, @@ -195,7 +201,7 @@ export const IssuesSidebar = ({ - Issues + {title} ))} diff --git a/src/components/Admin/common/IssuesSidebarOverlay/IssuesSidebar/types.ts b/src/components/Admin/common/IssuesSidebarOverlay/IssuesSidebar/types.ts index 5df782c58..388b5dcd6 100644 --- a/src/components/Admin/common/IssuesSidebarOverlay/IssuesSidebar/types.ts +++ b/src/components/Admin/common/IssuesSidebarOverlay/IssuesSidebar/types.ts @@ -9,6 +9,7 @@ export interface IssuesSidebarProps { onResizeHandleMouseDown: (e: MouseEvent) => void; query?: GetIssuesPayload; isPaginationEnabled?: boolean; + title?: string; } export interface DrawerContainerProps { diff --git a/src/components/Admin/common/IssuesSidebarOverlay/index.tsx b/src/components/Admin/common/IssuesSidebarOverlay/index.tsx index 8e4267680..7dad39fa2 100644 --- a/src/components/Admin/common/IssuesSidebarOverlay/index.tsx +++ b/src/components/Admin/common/IssuesSidebarOverlay/index.tsx @@ -147,6 +147,7 @@ export const IssuesSidebarOverlay = ({ onResizeHandleMouseDown={handleResizeHandleMouseDown} scopeDisplayName={scopeDisplayName} isPaginationEnabled={isPaginationEnabled} + title={issuesSidebarQuery?.title} /> diff --git a/src/components/Admin/common/IssuesSidebarOverlay/types.ts b/src/components/Admin/common/IssuesSidebarOverlay/types.ts index 0fdeaef49..e7fa2b63b 100644 --- a/src/components/Admin/common/IssuesSidebarOverlay/types.ts +++ b/src/components/Admin/common/IssuesSidebarOverlay/types.ts @@ -3,6 +3,7 @@ import type { GetIssuesPayload } from "../../../../redux/services/types"; export interface IssuesSidebarQuery { query?: GetIssuesPayload; limit?: number; + title?: string; } export interface IssuesSidebarOverlayProps { diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointQueryOptimizationV2InsightTicket/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointQueryOptimizationV2InsightTicket/index.tsx index 32489c9db..12a3fd4b5 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointQueryOptimizationV2InsightTicket/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointQueryOptimizationV2InsightTicket/index.tsx @@ -13,9 +13,9 @@ import type { import { useEndpointDataSource } from "../common"; import { CodeLocations } from "../common/CodeLocations"; import { CommitInfos } from "../common/CommitInfos"; +import { getTraceAttachment } from "../common/getTraceAttachment"; import { InsightJiraTicket } from "../common/InsightJiraTicket"; import { QueryOptimizationEndpoints } from "../common/QueryOptimizationEndpoints"; -import { getTraceAttachment } from "../common/SpanScaling"; import type { InsightTicketProps } from "../types"; export const EndpointQueryOptimizationV2InsightTicket = ({ diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointSpanNPlusOneInsightTicket/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointSpanNPlusOneInsightTicket/index.tsx index dc47e5160..9032a1520 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointSpanNPlusOneInsightTicket/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointSpanNPlusOneInsightTicket/index.tsx @@ -12,9 +12,9 @@ import type { import { useEndpointDataSource } from "../common"; import { CodeLocations } from "../common/CodeLocations"; import { CommitInfos } from "../common/CommitInfos"; +import { getTraceAttachment } from "../common/getTraceAttachment"; import { InsightJiraTicket } from "../common/InsightJiraTicket"; import { NPlusOneEndpoints } from "../common/NPlusOneEndpoints"; -import { getTraceAttachment } from "../common/SpanScaling"; import type { InsightTicketProps } from "../types"; export const EndpointSpanNPlusOneInsightTicket = ({ diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/SpaNPlusOneInsightTicket/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/SpaNPlusOneInsightTicket/index.tsx index b333049ed..3554288f0 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/SpaNPlusOneInsightTicket/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/SpaNPlusOneInsightTicket/index.tsx @@ -8,9 +8,9 @@ import type { SpaNPlusOneInsight } from "../../../types"; import { useSpanDataSource } from "../common"; import { CodeLocations } from "../common/CodeLocations"; import { CommitInfos } from "../common/CommitInfos"; +import { getTraceAttachment } from "../common/getTraceAttachment"; import { InsightJiraTicket } from "../common/InsightJiraTicket"; import { NPlusOneEndpoints } from "../common/NPlusOneEndpoints"; -import { getTraceAttachment } from "../common/SpanScaling"; import type { InsightTicketProps } from "../types"; export const SpaNPlusOneInsightTicket = ({ diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/SpanPerformanceAnomalyInsightTicket/SpanPerformanceAnomalyInsightTicket.stories.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/SpanPerformanceAnomalyInsightTicket/SpanPerformanceAnomalyInsightTicket.stories.tsx new file mode 100644 index 000000000..b573b8f7b --- /dev/null +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/SpanPerformanceAnomalyInsightTicket/SpanPerformanceAnomalyInsightTicket.stories.tsx @@ -0,0 +1,26 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { SpanPerformanceAnomalyInsightTicket } from "."; +import { mockedSpanPerformanceAnomalyInsight } from "../../../InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/mockData"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: + "Insights/InsightTicketRenderer/insightTickets/SpanPerformanceAnomalyInsightTicket", + component: SpanPerformanceAnomalyInsightTicket, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen" + } +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + data: { + insight: mockedSpanPerformanceAnomalyInsight + } + } +}; diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/SpanPerformanceAnomalyInsightTicket/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/SpanPerformanceAnomalyInsightTicket/index.tsx new file mode 100644 index 000000000..1f806dc09 --- /dev/null +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/SpanPerformanceAnomalyInsightTicket/index.tsx @@ -0,0 +1,113 @@ +import type { ReactElement } from "react"; +import { useConfigSelector } from "../../../../../store/config/useConfigSelector"; +import { getCriticalityLabel } from "../../../../../utils/getCriticalityLabel"; +import { getDurationString } from "../../../../../utils/getDurationString"; +import { intersperse } from "../../../../../utils/intersperse"; +import { roundTo } from "../../../../../utils/roundTo"; +import { DigmaSignature } from "../../../../common/DigmaSignature"; +import type { Attachment } from "../../../../common/JiraTicket/types"; +import type { SpanPerformanceAnomalyInsight } from "../../../types"; +import { useSpanDataSource } from "../common"; +import { CodeLocations } from "../common/CodeLocations"; +import { CommitInfos } from "../common/CommitInfos"; +import { getHistogramAttachment } from "../common/getHistogramAttachment"; +import { getTraceAttachment } from "../common/getTraceAttachment"; +import { InsightJiraTicket } from "../common/InsightJiraTicket"; +import type { InsightTicketProps } from "../types"; + +export const SpanPerformanceAnomalyInsightTicket = ({ + data, + refreshInsights, + onClose, + environmentId +}: InsightTicketProps) => { + const { jaegerURL, digmaApiProxyPrefix, backendInfo } = useConfigSelector(); + + const insight = data.insight; + + const { commitInfos, isLoading, codeLocations } = + useSpanDataSource( + data.insight.spanInfo, + data.insight, + environmentId + ); + + const renderDescription = () => { + return ( + <> + {intersperse( + [ +
+ The slowest 5% of this asset is{" "} + {roundTo(insight.slowerByPercentage, 2)}% slower than the median +
, + + Median duration: {getDurationString(insight.p50)} + , + + 5% duration: {getDurationString(insight.p95)} + , + , + , + + ], + (i: number) => ( +
+ ) + )} + + ); + }; + + const criticalityString = + insight && insight.criticality > 0 + ? `Criticality: ${getCriticalityLabel(insight.criticality)}` + : ""; + + const summary = ["Performance anomaly found", criticalityString] + .filter(Boolean) + .join(" - "); + + const attachmentP50Trace = getTraceAttachment( + jaegerURL, + data.insight.p50TraceId + ); + const attachmentP95Trace = getTraceAttachment( + jaegerURL, + data.insight.p95TraceId + ); + const attachmentHistogram = getHistogramAttachment( + digmaApiProxyPrefix, + insight, + "spanPercentiles", + backendInfo + ); + const attachments: Attachment[] = [ + ...([attachmentP50Trace, attachmentP95Trace].filter( + Boolean + ) as Attachment[]), + ...(attachmentHistogram ? [attachmentHistogram] : []) + ]; + + return ( + + ); +}; diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/SpanQueryOptimizationInsightTicket/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/SpanQueryOptimizationInsightTicket/index.tsx index 0e09f67c7..fe8ce7adf 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/SpanQueryOptimizationInsightTicket/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/SpanQueryOptimizationInsightTicket/index.tsx @@ -8,9 +8,9 @@ import type { Attachment } from "../../../../common/JiraTicket/types"; import type { SpanQueryOptimizationInsight } from "../../../types"; import { useCommitInfos } from "../common"; import { CommitInfos } from "../common/CommitInfos"; +import { getTraceAttachment } from "../common/getTraceAttachment"; import { InsightJiraTicket } from "../common/InsightJiraTicket"; import { QueryOptimizationEndpoints } from "../common/QueryOptimizationEndpoints"; -import { getTraceAttachment } from "../common/SpanScaling"; import type { InsightTicketProps } from "../types"; export const SpanQueryOptimizationInsightTicket = ({ diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/SpanScalingByRootCauseInsightTicket/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/SpanScalingByRootCauseInsightTicket/index.tsx index 70d0ff225..db59b4b2d 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/SpanScalingByRootCauseInsightTicket/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/SpanScalingByRootCauseInsightTicket/index.tsx @@ -8,6 +8,8 @@ import type { RootCauseSpanInfo, SpanScalingInsight } from "../../../types"; import { useEndpointDataSource } from "../common"; import { CodeLocations } from "../common/CodeLocations"; import { CommitInfos } from "../common/CommitInfos"; +import { getHistogramAttachment } from "../common/getHistogramAttachment"; +import { getTraceAttachment } from "../common/getTraceAttachment"; import { InsightJiraTicket } from "../common/InsightJiraTicket"; import { ScalingIssueAffectedEndpoints, @@ -15,9 +17,7 @@ import { ScalingIssueMessage, ScalingIssueRootCauses, ScalingIssueTestedConcurrency, - getHistogramAttachment, - getScalingIssueSummary, - getTraceAttachment + getScalingIssueSummary } from "../common/SpanScaling"; import type { InsightTicketProps } from "../types"; @@ -30,7 +30,7 @@ export const SpanScalingByRootCauseInsightTicket = ({ }: InsightTicketProps & { rootCauseSpanInfo: RootCauseSpanInfo; }) => { - const { jaegerURL, digmaApiProxyPrefix } = useConfigSelector(); + const { jaegerURL, digmaApiProxyPrefix, backendInfo } = useConfigSelector(); const spanInfo = rootCauseSpanInfo; @@ -95,7 +95,9 @@ export const SpanScalingByRootCauseInsightTicket = ({ ); const attachmentHistogram = getHistogramAttachment( digmaApiProxyPrefix, - spanInsight + spanInsight, + "spanScaling", + backendInfo ); const attachments: Attachment[] = [ ...(attachmentTrace ? [attachmentTrace] : []), diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/SpanScalingInsightTicket/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/SpanScalingInsightTicket/index.tsx index 2b00e078f..7f62c273e 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/SpanScalingInsightTicket/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/SpanScalingInsightTicket/index.tsx @@ -7,6 +7,8 @@ import type { SpanScalingInsight } from "../../../types"; import { useSpanDataSource } from "../common"; import { CodeLocations } from "../common/CodeLocations"; import { CommitInfos } from "../common/CommitInfos"; +import { getHistogramAttachment } from "../common/getHistogramAttachment"; +import { getTraceAttachment } from "../common/getTraceAttachment"; import { InsightJiraTicket } from "../common/InsightJiraTicket"; import { ScalingIssueAffectedEndpoints, @@ -14,9 +16,7 @@ import { ScalingIssueMessage, ScalingIssueRootCauses, ScalingIssueTestedConcurrency, - getHistogramAttachment, - getScalingIssueSummary, - getTraceAttachment + getScalingIssueSummary } from "../common/SpanScaling"; import type { InsightTicketProps } from "../types"; @@ -26,7 +26,7 @@ export const SpanScalingInsightTicket = ({ onClose, environmentId }: InsightTicketProps) => { - const { jaegerURL, digmaApiProxyPrefix } = useConfigSelector(); + const { jaegerURL, digmaApiProxyPrefix, backendInfo } = useConfigSelector(); const insight = data.insight; @@ -86,7 +86,9 @@ export const SpanScalingInsightTicket = ({ const attachmentTrace = getTraceAttachment(jaegerURL, traceId); const attachmentHistogram = getHistogramAttachment( digmaApiProxyPrefix, - insight + insight, + "spanScaling", + backendInfo ); const attachments: Attachment[] = [ ...(attachmentTrace ? [attachmentTrace] : []), diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/index.tsx index e12003018..85dfdb8f1 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/index.tsx @@ -6,41 +6,6 @@ import { ScalingIssueMessage as ScalingIssueMessage_ } from "./SpanScalingMessag import { ScalingIssueRootCauses as ScalingIssueRootCauses_ } from "./SpanScalingRootCauses"; import { ScalingIssueTestedConcurrency as ScalingIssueTestedConcurrency_ } from "./SpanScalingTestedConcurrency"; -export const getHistogramAttachment = ( - baseURL: string | null, - insight: SpanScalingInsight | null -) => { - if (!insight) { - return undefined; - } - - const histogramUrlParams = new URLSearchParams({ - env: insight.environment, - scoid: insight.spanInfo?.spanCodeObjectId ?? "" - }); - - return { - url: `${ - baseURL ?? "" - }/Graphs/graphForSpanScaling?${histogramUrlParams.toString()}`, - fileName: `histogram.html` - }; -}; - -export const getTraceAttachment = ( - baseURL: string | null, - traceId: string | null | undefined -) => { - if (!traceId) { - return undefined; - } - - return { - url: `${baseURL ?? ""}/api/traces/${traceId}?prettyPrint=true`, - fileName: `trace-${traceId}.json` - }; -}; - export const getScalingIssueSummary = (insight: SpanScalingInsight | null) => { const criticalityString = insight && insight.criticality > 0 diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/getHistogramAttachment.ts b/src/components/Insights/InsightTicketRenderer/insightTickets/common/getHistogramAttachment.ts new file mode 100644 index 000000000..583815172 --- /dev/null +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/getHistogramAttachment.ts @@ -0,0 +1,56 @@ +import type { BackendInfo } from "../../../../common/App/types"; +import { + getInsightHistogramUrl, + type HistogramType +} from "../../../getInsightHistogramUrl"; +import type { SpanInsight } from "../../../types"; + +const getApiUrl = ( + baseURL: string | null, + histogramType: HistogramType, + insight: T, + backendInfo: BackendInfo | null +) => { + switch (histogramType) { + case "spanScaling": { + return getInsightHistogramUrl( + baseURL, + histogramType, + insight.environment, + insight.spanInfo?.spanCodeObjectId ?? "", + backendInfo + ); + } + case "spanPercentiles": { + return getInsightHistogramUrl( + baseURL, + histogramType, + insight.environment, + insight.spanInfo?.spanCodeObjectId ?? "", + backendInfo + ); + } + } +}; + +export const getHistogramAttachment = ( + baseURL: string | null, + insight: T | null, + histogramType: "spanScaling" | "spanPercentiles", + backendInfo: BackendInfo | null +) => { + if (!insight) { + return undefined; + } + + const url = getApiUrl(baseURL, histogramType, insight, backendInfo); + + if (!url) { + return undefined; + } + + return { + url, + fileName: `histogram.html` + }; +}; diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/getTraceAttachment.ts b/src/components/Insights/InsightTicketRenderer/insightTickets/common/getTraceAttachment.ts new file mode 100644 index 000000000..75e8e596f --- /dev/null +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/getTraceAttachment.ts @@ -0,0 +1,13 @@ +export const getTraceAttachment = ( + baseURL: string | null, + traceId: string | null | undefined +) => { + if (!traceId) { + return undefined; + } + + return { + url: `${baseURL ?? ""}/api/traces/${traceId}?prettyPrint=true`, + fileName: `trace-${traceId}.json` + }; +}; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/index.tsx index 798af1b76..4ea908885 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/index.tsx @@ -7,6 +7,7 @@ import { openURLInDefaultBrowser } from "../../../../../utils/actions/openURLInD import { sendUserActionTrackingEvent } from "../../../../../utils/actions/sendUserActionTrackingEvent"; import { openBrowserTabWithContent } from "../../../../../utils/openBrowserTabWithContent"; import { actions } from "../../../actions"; +import { getInsightHistogramUrl } from "../../../getInsightHistogramUrl"; import { trackingEvents } from "../../../tracking"; import { isEndpointBottleneckInsight, @@ -26,6 +27,7 @@ import { isSpanEndpointBottleneckInsight, isSpanNexusInsight, isSpanNPlusOneInsight, + isSpanPerformanceAnomalyInsight, isSpanQueryOptimizationInsight, isSpanScalingBadlyInsight, isSpanUsagesInsight @@ -50,6 +52,7 @@ import { SpanDurationBreakdownInsightCard } from "./insightCards/SpanDurationBre import { SpanDurationsInsightCard } from "./insightCards/SpanDurationsInsightCard"; import { SpanEndpointBottleneckInsightCard } from "./insightCards/SpanEndpointBottleneckInsightCard"; import { SpanNexusInsightCard } from "./insightCards/SpanNexusInsightCard"; +import { SpanPerformanceAnomalyInsightCard } from "./insightCards/SpanPerformanceAnomalyInsightCard"; import { SpaNPlusOneInsightCard } from "./insightCards/SpaNPlusOneInsightCard"; import { SpanQueryOptimizationInsightCard } from "./insightCards/SpanQueryOptimizationInsightCard"; import { SpanScalingInsightCard } from "./insightCards/SpanScalingInsightCard"; @@ -66,7 +69,9 @@ export const InsightCardRenderer = ({ viewMode, onDismissalChange, onOpenSuggestion, - tooltipBoundaryRef + tooltipBoundaryRef, + jaegerURL, + backendInfo }: InsightCardRendererProps) => { const [triggerSpanPercentilesHistogramFetch] = useLazyGetSpanPercentilesHistogramQuery(); @@ -80,27 +85,43 @@ export const InsightCardRenderer = ({ if (platform === "Web") { switch (insightType) { case InsightType.SpanScaling: { - const baseURL = `${window.location.origin}/api/Graphs/graphForSpanScaling`; - const url = new URL(baseURL); - - url.searchParams.append("env", environmentId); - url.searchParams.append("scoid", spanCodeObjectId); - - openURLInDefaultBrowser(url.toString()); + const url = getInsightHistogramUrl( + jaegerURL, + "spanScaling", + environmentId, + spanCodeObjectId, + backendInfo + ); + + if (url) { + openURLInDefaultBrowser(url.toString()); + } break; } case InsightType.SpanDurations: - // TODO: use links instead of opening the new tab - void triggerSpanPercentilesHistogramFetch({ - environment: environmentId, - spanCodeObjectId - }) - .unwrap() - .then((data) => { - openBrowserTabWithContent(data); - }); + case InsightType.SpanPerformanceAnomaly: { + const url = getInsightHistogramUrl( + jaegerURL, + "spanPercentiles", + environmentId, + spanCodeObjectId, + backendInfo + ); + + if (!url) { + void triggerSpanPercentilesHistogramFetch({ + environment: environmentId, + spanCodeObjectId + }) + .unwrap() + .then((data) => { + openBrowserTabWithContent(data); + }); + } + break; + } } return; @@ -110,7 +131,11 @@ export const InsightCardRenderer = ({ action: actions.OPEN_HISTOGRAM, payload: { spanCodeObjectId, - insightType, + // TODO: fix after the plugin support of SpanPerformanceAnomaly insight is added + insightType: + insightType === InsightType.SpanPerformanceAnomaly + ? InsightType.SpanDurations + : insightType, displayName } }); @@ -527,5 +552,25 @@ export const InsightCardRenderer = ({ ); } + if (isSpanPerformanceAnomalyInsight(insight)) { + return ( + + ); + } + return null; }; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/SpanPerformanceAnomalyInsightCard.stories.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/SpanPerformanceAnomalyInsightCard.stories.tsx new file mode 100644 index 000000000..966de888b --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/SpanPerformanceAnomalyInsightCard.stories.tsx @@ -0,0 +1,24 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { SpanPerformanceAnomalyInsightCard } from "."; +import { mockedSpanPerformanceAnomalyInsight } from "./mockData"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: + "Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard", + component: SpanPerformanceAnomalyInsightCard, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen" + } +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + insight: mockedSpanPerformanceAnomalyInsight + } +}; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/index.tsx new file mode 100644 index 000000000..78ddd17a0 --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/index.tsx @@ -0,0 +1,76 @@ +import { getDurationString } from "../../../../../../../utils/getDurationString"; +import { roundTo } from "../../../../../../../utils/roundTo"; +import { Tag } from "../../../../../../common/v3/Tag"; +import { InsightCard } from "../common/InsightCard"; +import { ColumnsContainer } from "../common/InsightCard/ColumnsContainer"; +import { KeyValue } from "../common/InsightCard/KeyValue"; +import { ContentContainer } from "../styles"; +import * as s from "./styles"; +import type { SpanPerformanceAnomalyInsightCardProps } from "./types"; + +export const SpanPerformanceAnomalyInsightCard = ({ + insight, + onRecalculate, + onRefresh, + onGoToSpan, + isMarkAsReadButtonEnabled, + viewMode, + onDismissalChange, + tooltipBoundaryRef, + onTraceButtonClick, + onHistogramButtonClick +}: SpanPerformanceAnomalyInsightCardProps) => { + const p50DurationString = getDurationString(insight.p50); + const p95DurationString = getDurationString(insight.p95); + + const goToTrace = (traceId: string) => { + onTraceButtonClick( + { + name: insight.spanInfo?.displayName, + id: traceId + }, + insight.type, + insight.spanInfo?.spanCodeObjectId + ); + }; + + const handleGoToP50Trace = () => { + goToTrace(insight.p50TraceId); + }; + + const handleGoToP95Trace = () => { + goToTrace(insight.p95TraceId); + }; + + return ( + + + The slowest 5% of this asset is{" "} + {roundTo(insight.slowerByPercentage, 2)}% slower than the median + + + + + + + + + + + } + onRecalculate={onRecalculate} + onRefresh={onRefresh} + onGoToSpan={onGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} + viewMode={viewMode} + onDismissalChange={onDismissalChange} + tooltipBoundaryRef={tooltipBoundaryRef} + onGoToP50Trace={handleGoToP50Trace} + onGoToP95Trace={handleGoToP95Trace} + onOpenHistogram={onHistogramButtonClick} + /> + ); +}; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/mockData.ts b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/mockData.ts new file mode 100644 index 000000000..728e2adaf --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/mockData.ts @@ -0,0 +1,70 @@ +import { InsightType } from "../../../../../../../types"; +import type { SpanPerformanceAnomalyInsight } from "../../../../../types"; +import { InsightCategory, InsightScope } from "../../../../../types"; + +// TODO: check +export const mockedSpanPerformanceAnomalyInsight: SpanPerformanceAnomalyInsight = + { + sourceSpanCodeObjectInsight: "sourceSpanCodeObjectInsightId", + id: "60b54792-8262-4c5d-9628-7cce7979ad6d", + firstDetected: "2023-12-05T17:25:47.010Z", + lastDetected: "2024-01-05T13:14:47.010Z", + criticality: 0, + impact: 0, + firstCommitId: "b3f7b3f", + lastCommitId: "a1b2c3d", + deactivatedCommitId: null, + reopenCount: 0, + ticketLink: null, + name: "Performance Anomaly", + category: InsightCategory.Performance, + specifity: 4, + importance: 2, + scope: InsightScope.Span, + spanInfo: { + name: "HTTP POST /owners/{ownerId}/pets/new", + displayName: "HTTP POST /owners/{ownerId}/pets/new", + instrumentationLibrary: "io.opentelemetry.tomcat-10.0", + spanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP POST /owners/{ownerId}/pets/new", + methodCodeObjectId: + "method:org.springframework.samples.petclinic.owner.PetController$_$processCreationForm", + kind: "Server" + }, + shortDisplayInfo: { + title: "", + targetDisplayName: "", + subtitle: "", + description: "" + }, + codeObjectId: + "org.springframework.samples.petclinic.owner.PetController$_$processCreationForm", + decorators: [ + { + title: "Performance Anomaly", + description: "Performance Anomaly detected" + } + ], + environment: "BOB-LAPTOP[LOCAL]", + severity: 0.0, + isRecalculateEnabled: false, + customStartTime: null, + actualStartTime: "2023-08-10T08:04:00Z", + type: InsightType.SpanPerformanceAnomaly, + isDismissed: false, + isDismissible: true, + isRecalculatedEnabled: true, + p50: { + value: 3.89, + unit: "sec", + raw: 3887134558.2822084 + }, + p95: { + value: 3.89, + unit: "sec", + raw: 3887134558.2822084 + }, + p50TraceId: "00E4D714D4FAD0A00F9D8A39C8A49E8A", + p95TraceId: "00E4D714D4FAD0A00F9D8A39C8A49E8A", + slowerByPercentage: 0.5 + }; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/styles.ts b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/styles.ts new file mode 100644 index 000000000..751297819 --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/styles.ts @@ -0,0 +1,9 @@ +import styled from "styled-components"; +import { subscriptRegularTypography } from "../../../../../../common/App/typographies"; + +export const Description = styled.div` + display: flex; + gap: 8px; + ${subscriptRegularTypography} + color: ${({ theme }) => theme.colors.v3.text.primary}; +`; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/types.ts b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/types.ts new file mode 100644 index 000000000..858488902 --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanPerformanceAnomalyInsightCard/types.ts @@ -0,0 +1,22 @@ +import type { + InsightType, + SpanPerformanceAnomalyInsight, + Trace +} from "../../../../../types"; +import type { InsightCardCommonProps } from "../common/InsightCard/types"; + +export interface SpanPerformanceAnomalyInsightCardProps + extends InsightCardCommonProps { + insight: SpanPerformanceAnomalyInsight; + onTraceButtonClick: ( + trace: Trace, + insightType: InsightType, + spanCodeObjectId?: string + ) => void; + onHistogramButtonClick: ( + spanCodeObjectId: string, + insightType: InsightType, + displayName: string, + environmentId: string + ) => void; +} diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/InsightCard.stories.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/InsightCard.stories.tsx index aca3ee7dc..4a29c6ef1 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/InsightCard.stories.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/InsightCard.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { fn } from "@storybook/test"; import { InsightCard } from "."; import { ConfigContext, @@ -23,12 +24,27 @@ export default meta; type Story = StoryObj; export const Default: Story = { + decorators: [ + (Story) => ( + + + + ) + ], args: { isAsync: true, insight: { ...mockedEndpointSpanNPlusOneInsight, isRead: true - } + }, + onGoToTrace: fn(), + onGoToP50Trace: fn(), + onGoToP95Trace: fn() } }; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/InsightHeader/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/InsightHeader/index.tsx index 226255c26..7c383cbe0 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/InsightHeader/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/InsightHeader/index.tsx @@ -1,8 +1,6 @@ -import { platform } from "../../../../../../../../../platform"; import { useConfigSelector } from "../../../../../../../../../store/config/useConfigSelector"; import { isString } from "../../../../../../../../../typeGuards/isString"; import { formatTimeDistance } from "../../../../../../../../../utils/formatTimeDistance"; -import { getIdeLauncherLinkForSpan } from "../../../../../../../../../utils/getIdeLauncherLinkForSpan"; import { getInsightTypeInfo } from "../../../../../../../../../utils/getInsightTypeInfo"; import { Link } from "../../../../../../../../common/v3/Link"; import { NewTag } from "../../../../../../../../common/v3/NewTag"; @@ -59,11 +57,6 @@ export const InsightHeader = ({ } }; - const spanIdeLauncherLink = - platform === "Web" && spanInfo - ? getIdeLauncherLinkForSpan(spanInfo) - : undefined; - return ( @@ -102,14 +95,7 @@ export const InsightHeader = ({ {!scope?.span && spanInfo && ( - - {spanInfo.displayName} - + {spanInfo.displayName} diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/index.tsx index 9f9ed919f..893290077 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/index.tsx @@ -4,11 +4,14 @@ import { usePrevious } from "../../../../../../../../hooks/usePrevious"; import { platform } from "../../../../../../../../platform"; import { useConfigSelector } from "../../../../../../../../store/config/useConfigSelector"; import { isString } from "../../../../../../../../typeGuards/isString"; +import { openURLInDefaultBrowser } from "../../../../../../../../utils/actions/openURLInDefaultBrowser"; import { sendUserActionTrackingEvent } from "../../../../../../../../utils/actions/sendUserActionTrackingEvent"; +import { getIdeLauncherLinkForSpan } from "../../../../../../../../utils/getIdeLauncherLinkForSpan"; import { getInsightTypeInfo } from "../../../../../../../../utils/getInsightTypeInfo"; import { DismissPanel } from "../../../../../../../common/DismissPanel"; import { CheckmarkCircleIcon } from "../../../../../../../common/icons/12px/CheckmarkCircleIcon"; import { TraceIcon } from "../../../../../../../common/icons/12px/TraceIcon"; +import { CodeIcon } from "../../../../../../../common/icons/16px/CodeIcon"; import { DoubleCircleIcon } from "../../../../../../../common/icons/16px/DoubleCircleIcon"; import { HistogramIcon } from "../../../../../../../common/icons/16px/HistogramIcon"; import { LightBulbWithScrewIcon } from "../../../../../../../common/icons/16px/LightBulbWithScrewIcon"; @@ -45,6 +48,8 @@ export const InsightCard = ({ jiraTicketInfo, isMarkAsReadButtonEnabled, onGoToTrace, + onGoToP50Trace, + onGoToP95Trace, onGoToLive, onPin, content, @@ -128,6 +133,20 @@ export const InsightCard = ({ } }; + const handleIdeButtonClick = () => { + if ( + (isSpanInsight(insight) || isEndpointInsight(insight)) && + insight.spanInfo + ) { + const spanInfo = insight.spanInfo; + const spanIdeLauncherLink = getIdeLauncherLinkForSpan(spanInfo); + + if (spanIdeLauncherLink) { + openURLInDefaultBrowser(spanIdeLauncherLink); + } + } + }; + const handleHistogramButtonClick = () => { if (isSpanInsight(insight) && insight.spanInfo && onOpenHistogram) { onOpenHistogram( @@ -291,6 +310,16 @@ export const InsightCard = ({ onClick={handleRecheckButtonClick} /> ); + case "ide": + return ( + + ); case "viewTicketInfo": return ( @@ -306,6 +335,36 @@ export const InsightCard = ({ /> ); + case "openP50Trace": + return ( + ( + + + Median + + )} + label={"Trace"} + title={"Open Median Trace"} + onClick={() => onGoToP50Trace && onGoToP50Trace()} + /> + ); + case "openP95Trace": + return ( + ( + + + 5% + + )} + label={"Trace"} + title={"Open %5 Trace"} + onClick={() => onGoToP95Trace && onGoToP95Trace()} + /> + ); case "openTrace": return ( void; onGoToLive?: () => void; onGoToTrace?: () => void; + onGoToP50Trace?: () => void; + onGoToP95Trace?: () => void; jiraTicketInfo?: { ticketLink?: string | null; isHintEnabled?: boolean; @@ -97,12 +99,15 @@ export interface InsightCardCommonProps { } export type Action = + | "info" | "markAsRead" | "openHistogram" | "recheck" + | "ide" | "viewTicketInfo" + | "openP50Trace" + | "openP95Trace" | "openTrace" | "openLiveView" | "pin" - | "info" | "openSuggestion"; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/types.ts b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/types.ts index 74a2cac56..9520e93cb 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/types.ts +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/types.ts @@ -1,4 +1,6 @@ import type { RefObject } from "react"; +import type { GetAboutResponse } from "../../../../../redux/services/types"; +import type { BackendInfo } from "../../../../common/App/types"; import type { GenericCodeObjectInsight } from "../../../types"; import type { InsightCardViewMode } from "./insightCards/common/InsightCard/types"; @@ -16,4 +18,6 @@ export interface InsightCardRendererProps { onDismissalChange: (action: string, insightId: string) => void; onOpenSuggestion?: (insightId: string) => void; tooltipBoundaryRef?: RefObject; + jaegerURL: string | null; + backendInfo: BackendInfo | GetAboutResponse | null; } diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx index 21430fbbd..4a6ff3820 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx @@ -153,7 +153,7 @@ export const InsightsPage = ({ isMarkAsReadButtonEnabled, insightsViewType }: InsightsPageProps) => { - const { scope, environment } = useConfigSelector(); + const { scope, environment, backendInfo, jaegerURL } = useConfigSelector(); const { viewMode, search, filters, filteredInsightTypes } = useInsightsSelector(); const { setInsightsViewMode: setMode } = useStore.getState(); @@ -229,6 +229,8 @@ export const InsightsPage = ({ viewMode={isAtSpan ? "full" : "compact"} onDismissalChange={handleDismissalChange} tooltipBoundaryRef={listRef} + backendInfo={backendInfo} + jaegerURL={jaegerURL} /> )) : renderEmptyState( diff --git a/src/components/Insights/getInsightHistogramUrl.ts b/src/components/Insights/getInsightHistogramUrl.ts new file mode 100644 index 000000000..db58d1aac --- /dev/null +++ b/src/components/Insights/getInsightHistogramUrl.ts @@ -0,0 +1,47 @@ +import { getFeatureFlagValue } from "../../featureFlags"; +import type { GetAboutResponse } from "../../redux/services/types"; +import { FeatureFlag } from "../../types"; +import type { BackendInfo } from "../common/App/types"; + +export type HistogramType = "spanScaling" | "spanPercentiles"; + +export const getInsightHistogramUrl = ( + baseURL: string | null, + histogramType: HistogramType, + environmentId: string, + spanCodeObjectId: string, + backendInfo: BackendInfo | GetAboutResponse | null +) => { + switch (histogramType) { + case "spanScaling": { + const histogramUrlParams = new URLSearchParams({ + env: environmentId, + scoid: spanCodeObjectId + }); + + const path = "/Graphs/graphForSpanScaling"; + return `${baseURL ?? ""}${path}?${histogramUrlParams.toString()}`; + } + case "spanPercentiles": { + const isSpanPercentilesHistogramIsEnabled = Boolean( + backendInfo && + getFeatureFlagValue( + backendInfo, + FeatureFlag.IS_HTTP_GET_METHOD_SPAN_PERCENTILES_HISTOGRAM_ENABLED + ) + ); + + if (!isSpanPercentilesHistogramIsEnabled) { + return undefined; + } + + const histogramUrlParams = new URLSearchParams({ + environment: environmentId, + spanCodeObjectId: spanCodeObjectId + }); + + const path = "/Graphs/graphForSpanPercentiles"; + return `${baseURL ?? ""}${path}?${histogramUrlParams.toString()}`; + } + } +}; diff --git a/src/components/Insights/typeGuards.ts b/src/components/Insights/typeGuards.ts index bd9376553..654f7d141 100644 --- a/src/components/Insights/typeGuards.ts +++ b/src/components/Insights/typeGuards.ts @@ -23,6 +23,7 @@ import type { SpanEndpointBottleneckInsight, SpanInsight, SpanNexusInsight, + SpanPerformanceAnomalyInsight, SpanQueryOptimizationInsight, SpanScalingInsight, SpanScalingInsufficientDataInsight, @@ -154,3 +155,8 @@ export const isSpanQueryOptimizationInsight = ( insight: CodeObjectInsight ): insight is SpanQueryOptimizationInsight => insight.type === InsightType.SpanQueryOptimization; + +export const isSpanPerformanceAnomalyInsight = ( + insight: CodeObjectInsight +): insight is SpanPerformanceAnomalyInsight => + insight.type === InsightType.SpanPerformanceAnomaly; diff --git a/src/components/Insights/types.ts b/src/components/Insights/types.ts index af587cbd1..669c3e2af 100644 --- a/src/components/Insights/types.ts +++ b/src/components/Insights/types.ts @@ -54,7 +54,8 @@ export type GenericSpanInsight = | SpanScalingWellInsight | SpanScalingInsufficientDataInsight | SpanNexusInsight - | SpanQueryOptimizationInsight; + | SpanQueryOptimizationInsight + | SpanPerformanceAnomalyInsight; export interface MethodSpan { spanCodeObjectId: string; @@ -685,3 +686,17 @@ export interface DismissUndismissInsightPayload { insightId: string; id: string; } + +export interface SpanPerformanceAnomalyInsight extends SpanInsight { + name: "Performance Anomaly"; + type: InsightType.SpanPerformanceAnomaly; + category: InsightCategory.Performance; + specifity: InsightSpecificity.OwnInsight; + isRecalculatedEnabled: true; + importance: InsightImportance.Critical; + p50: Duration; + p95: Duration; + slowerByPercentage: number; + p50TraceId: string; + p95TraceId: string; +} diff --git a/src/components/Tests/TestTicket/index.tsx b/src/components/Tests/TestTicket/index.tsx index 44ee85d6f..fc4bd1a16 100644 --- a/src/components/Tests/TestTicket/index.tsx +++ b/src/components/Tests/TestTicket/index.tsx @@ -6,7 +6,7 @@ import { intersperse } from "../../../utils/intersperse"; import { DigmaSignature } from "../../common/DigmaSignature"; import { JiraTicket } from "../../common/JiraTicket"; import type { Attachment } from "../../common/JiraTicket/types"; -import { getTraceAttachment } from "../../Insights/InsightTicketRenderer/insightTickets/common/SpanScaling"; +import { getTraceAttachment } from "../../Insights/InsightTicketRenderer/insightTickets/common/getTraceAttachment"; import type { TestTicketProps } from "./types"; export const TestTicket = ({ diff --git a/src/components/common/icons/16px/PulseIcon.tsx b/src/components/common/icons/16px/PulseIcon.tsx new file mode 100644 index 000000000..dea2fbb7e --- /dev/null +++ b/src/components/common/icons/16px/PulseIcon.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import type { IconProps } from "../types"; + +const PulseIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const PulseIcon = React.memo(PulseIconComponent); diff --git a/src/featureFlags.ts b/src/featureFlags.ts index 51224956f..2def58da5 100644 --- a/src/featureFlags.ts +++ b/src/featureFlags.ts @@ -30,6 +30,8 @@ export const featureFlagMinBackendVersions: Record = { [FeatureFlag.IS_GLOBAL_ERROR_DISMISS_ENABLED]: "0.3.148", [FeatureFlag.IS_GLOBAL_ERROR_LAST_DAYS_FILTER_ENABLED]: "0.3.149", [FeatureFlag.IS_DURATION_BREAKDOWN_PERCENTAGE_OF_CALLS_ENABLED]: "0.3.193", + [FeatureFlag.IS_HTTP_GET_METHOD_SPAN_PERCENTILES_HISTOGRAM_ENABLED]: + "0.3.199", [FeatureFlag.IS_INSIGHT_SEVERITY_SORTING_ENABLED]: "0.3.204" }; diff --git a/src/types.ts b/src/types.ts index db7696fc2..96192eda3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -30,7 +30,8 @@ export enum FeatureFlag { IS_GLOBAL_ERROR_DISMISS_ENABLED, IS_GLOBAL_ERROR_LAST_DAYS_FILTER_ENABLED, IS_DURATION_BREAKDOWN_PERCENTAGE_OF_CALLS_ENABLED, - IS_INSIGHT_SEVERITY_SORTING_ENABLED + IS_INSIGHT_SEVERITY_SORTING_ENABLED, + IS_HTTP_GET_METHOD_SPAN_PERCENTILES_HISTOGRAM_ENABLED } export enum InsightType { @@ -58,7 +59,8 @@ export enum InsightType { SpanNexus = "SpanNexus", SpanQueryOptimization = "SpanQueryOptimization", EndpointQueryOptimizationV2 = "EndpointQueryOptimizationV2", - EndpointSlowdownSource = "EndpointSlowdownSource" + EndpointSlowdownSource = "EndpointSlowdownSource", + SpanPerformanceAnomaly = "SpanPerformanceAnomaly" } export enum SCOPE_CHANGE_EVENTS { diff --git a/src/utils/getIdeLauncherLinkForSpan.ts b/src/utils/getIdeLauncherLinkForSpan.ts index 4c89c0035..d800560bd 100644 --- a/src/utils/getIdeLauncherLinkForSpan.ts +++ b/src/utils/getIdeLauncherLinkForSpan.ts @@ -1,6 +1,8 @@ import type { SpanInfo } from "../types"; -export const getIdeLauncherLinkForSpan = (spanInfo: SpanInfo) => { +export const getIdeLauncherLinkForSpan = ( + spanInfo: SpanInfo +): string | undefined => { if (!spanInfo.uid) { return; } diff --git a/src/utils/getInsightTypeInfo.ts b/src/utils/getInsightTypeInfo.ts index 0ec07627e..3c92f71a9 100644 --- a/src/utils/getInsightTypeInfo.ts +++ b/src/utils/getInsightTypeInfo.ts @@ -1,5 +1,6 @@ import type { MemoExoticComponent } from "react"; import * as descriptionProvider from "../components/common/InsightsDescription"; +import { PulseIcon } from "../components/common/icons/16px/PulseIcon"; import { TwoHorizontalEndpointsIcon } from "../components/common/icons/16px/TwoHorizontalEndpointsIcon"; import { AlarmClockIcon } from "../components/common/icons/AlarmClockIcon"; import { BottleneckIcon } from "../components/common/icons/BottleneckIcon"; @@ -180,6 +181,10 @@ export const getInsightTypeInfo = ( label: "Inefficient Query", description: descriptionProvider.QueryOptimizationDescription, documentationLink: QUERY_OPTIMIZATION_ISSUES_DOCUMENTATION_URL + }, + [InsightType.SpanPerformanceAnomaly]: { + icon: PulseIcon, + label: "Performance Anomaly" } };