From 2b42d5a2914db04d3f72bf0dd711416c6b16f837 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Fri, 11 Apr 2025 10:41:01 +0200 Subject: [PATCH 1/5] Add new insight type --- src/components/Insights/typeGuards.ts | 5 +++++ src/components/Insights/types.ts | 14 ++++++++++++++ src/types.ts | 3 ++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/components/Insights/typeGuards.ts b/src/components/Insights/typeGuards.ts index 654f7d141..876a67833 100644 --- a/src/components/Insights/typeGuards.ts +++ b/src/components/Insights/typeGuards.ts @@ -160,3 +160,8 @@ export const isSpanPerformanceAnomalyInsight = ( insight: CodeObjectInsight ): insight is SpanPerformanceAnomalyInsight => insight.type === InsightType.SpanPerformanceAnomaly; + +// export const isEndpointScalingInsight = ( +// insight: CodeObjectInsight +// ): insight is EndpointScalingInsight => +// insight.type === InsightType.EndpointScaling; diff --git a/src/components/Insights/types.ts b/src/components/Insights/types.ts index d33004528..696821b7c 100644 --- a/src/components/Insights/types.ts +++ b/src/components/Insights/types.ts @@ -387,6 +387,14 @@ export interface AffectedEndpoint extends SpanInfo { flowHash: string; } +// interface ScalingInsightInfo { +// turningPointConcurrency: number; +// maxConcurrency: number; +// minDuration: Duration; +// maxDuration: Duration; +// histogramInfo: histogramBarData[] | null; +// } + export interface SpanScalingInsight extends SpanInsight { name: "Scaling Issue Found"; type: InsightType.SpanScaling; @@ -654,3 +662,9 @@ export interface SpanPerformanceAnomalyInsight extends SpanInsight { p50TraceId: string | null; p95TraceId: string | null; } + +export interface EndpointScalingInsight extends EndpointInsight { + name: "Scaling Issue Found"; + type: InsightType.EndpointScaling; + category: InsightCategory.Performance; +} diff --git a/src/types.ts b/src/types.ts index 93d5f7ac1..224e90574 100644 --- a/src/types.ts +++ b/src/types.ts @@ -59,7 +59,8 @@ export enum InsightType { SpanQueryOptimization = "SpanQueryOptimization", EndpointQueryOptimizationV2 = "EndpointQueryOptimizationV2", EndpointSlowdownSource = "EndpointSlowdownSource", - SpanPerformanceAnomaly = "SpanPerformanceAnomaly" + SpanPerformanceAnomaly = "SpanPerformanceAnomaly", + EndpointScaling = "EndpointScaling" } export enum ScopeChangeEvent { From 1c0cce0b883b6d64834b3d56a3f7132e429b6b73 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Fri, 11 Apr 2025 18:45:06 +0200 Subject: [PATCH 2/5] Add EndpointScaling insight --- dependencies.json | 2 +- .../Report/Cards/DiscoveredCard/index.tsx | 2 +- .../FlowStack/index.tsx | 2 +- .../TopIssues/HighlightCardRenderer/index.tsx | 6 + .../EndpointScalingHighlightCard.stories.tsx | 25 + .../EndpointScalingHighlightCard/index.tsx | 71 +++ .../EndpointScalingHighlightCard/mockData.ts | 70 +++ .../EndpointScalingHighlightCard/types.ts | 8 + .../Insights/InsightTicketRenderer/index.tsx | 2 +- .../InsightCardRenderer/index.tsx | 21 + .../EndpointScalingInsightCard.stories.tsx | 36 ++ .../EndpointScalingInsightCard/index.tsx | 159 ++++++ .../EndpointScalingInsightCard/mockData.ts | 533 ++++++++++++++++++ .../EndpointScalingInsightCard/styles.ts | 8 + .../EndpointScalingInsightCard/types.ts | 23 + .../SpanScalingInsightCard/index.tsx | 2 +- .../SpanScalingInsightCard/styles.ts | 6 - .../InsightsCatalog/InsightsPage/index.tsx | 2 +- src/components/Insights/typeGuards.ts | 9 +- src/components/Insights/types.ts | 67 ++- src/components/Main/index.tsx | 2 +- src/components/Navigation/index.tsx | 2 +- .../CreateEnvironmentWizard/index.tsx | 2 +- src/components/RecentActivity/index.tsx | 2 +- src/components/common/Popover/hooks.ts | 5 +- src/redux/services/typeGuards.ts | 6 + src/redux/services/types.ts | 7 + src/utils/getInsightTypeInfo.ts | 6 + 28 files changed, 1045 insertions(+), 41 deletions(-) create mode 100644 src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/EndpointScalingHighlightCard.stories.tsx create mode 100644 src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/index.tsx create mode 100644 src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/mockData.ts create mode 100644 src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/types.ts create mode 100644 src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/EndpointScalingInsightCard.stories.tsx create mode 100644 src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/index.tsx create mode 100644 src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/mockData.ts create mode 100644 src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/styles.ts create mode 100644 src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/types.ts diff --git a/dependencies.json b/dependencies.json index d92f62394..165ab188d 100644 --- a/dependencies.json +++ b/dependencies.json @@ -1,6 +1,6 @@ { "jetBrainsPluginVersion": "2.0.411", "visualStudioExtensionVersion": "1.2.4", - "jaegerUIVersion": "1.29.1-digma.1.1.2", + "jaegerUIVersion": "1.29.1-digma.1.2.0", "jaegerVersion": "1.45.0" } diff --git a/src/components/Dashboard/Report/Cards/DiscoveredCard/index.tsx b/src/components/Dashboard/Report/Cards/DiscoveredCard/index.tsx index 9cca9e0e5..3d2920cc8 100644 --- a/src/components/Dashboard/Report/Cards/DiscoveredCard/index.tsx +++ b/src/components/Dashboard/Report/Cards/DiscoveredCard/index.tsx @@ -18,7 +18,7 @@ export const DiscoveredCard = ({ options, title }: DiscoveredCardProps) => { ))} diff --git a/src/components/Errors/ErrorDetails/ErrorDetailsCardContent/FlowStack/index.tsx b/src/components/Errors/ErrorDetails/ErrorDetailsCardContent/FlowStack/index.tsx index 08a1a20a8..11507f542 100644 --- a/src/components/Errors/ErrorDetails/ErrorDetailsCardContent/FlowStack/index.tsx +++ b/src/components/Errors/ErrorDetails/ErrorDetailsCardContent/FlowStack/index.tsx @@ -137,7 +137,7 @@ export const FlowStack = ({ data }: FlowStackProps) => { : frames; const spanGroups = visibleFrames.reduce((acc, frame, i) => { - if (i == 0 || frame.spanName !== frames[i - 1].spanName) { + if (i === 0 || frame.spanName !== frames[i - 1].spanName) { acc.push([frame]); } else { acc[acc.length - 1].push(frame); diff --git a/src/components/Highlights/TopIssues/HighlightCardRenderer/index.tsx b/src/components/Highlights/TopIssues/HighlightCardRenderer/index.tsx index d85b0e63d..7a8f75bec 100644 --- a/src/components/Highlights/TopIssues/HighlightCardRenderer/index.tsx +++ b/src/components/Highlights/TopIssues/HighlightCardRenderer/index.tsx @@ -3,6 +3,7 @@ import { isEndpointChattyApiV2Highlight, isEndpointHighNumberOfQueriesHighlight, isEndpointQueryOptimizationV2Highlight, + isEndpointScalingHighlight, isEndpointSessionInViewHighlight, isEndpointSlowdownSourceHighlight, isEndpointSpanNPlusOneHighlight, @@ -17,6 +18,7 @@ import { EndpointBottleneckHighlightCard } from "../highlightCards/EndpointBottl import { EndpointChattyApiV2HighlightCard } from "../highlightCards/EndpointChattyApiV2HighlightCard"; import { EndpointHighNumberOfQueriesHighlightCard } from "../highlightCards/EndpointHighNumberOfQueriesHighlightCard"; import { EndpointQueryOptimizationV2HighlightCard } from "../highlightCards/EndpointQueryOptimizationV2HighlightCard"; +import { EndpointScalingHighlightCard } from "../highlightCards/EndpointScalingHighlightCard"; import { EndpointSessionInViewHighlightCard } from "../highlightCards/EndpointSessionInViewHighlightCard"; import { EndpointSlowdownSourceHighlightCard } from "../highlightCards/EndpointSlowdownSourceHighlightCard"; import { EndpointSpanNPlusOneHighlightCard } from "../highlightCards/EndpointSpanNPlusOneHighlightCard"; @@ -82,4 +84,8 @@ export const HighlightCardRenderer = ({ if (isSpanPerformanceAnomalyHighlight(highlight)) { return ; } + + if (isEndpointScalingHighlight(highlight)) { + return ; + } }; diff --git a/src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/EndpointScalingHighlightCard.stories.tsx b/src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/EndpointScalingHighlightCard.stories.tsx new file mode 100644 index 000000000..671c35143 --- /dev/null +++ b/src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/EndpointScalingHighlightCard.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { EndpointScalingHighlightCard } from "."; +import { mockedEndpointScalingHighlightData } from "./mockData"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard", + component: EndpointScalingHighlightCard, + 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; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args + +export const Default: Story = { + args: { + data: mockedEndpointScalingHighlightData + } +}; diff --git a/src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/index.tsx b/src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/index.tsx new file mode 100644 index 000000000..3d32ca417 --- /dev/null +++ b/src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/index.tsx @@ -0,0 +1,71 @@ +import type { Row } from "@tanstack/react-table"; +import { createColumnHelper } from "@tanstack/react-table"; +import type { + EndpointScalingMetrics, + EnvironmentData +} from "../../../../../redux/services/types"; +import { useConfigSelector } from "../../../../../store/config/useConfigSelector"; +import { ScopeChangeEvent } from "../../../../../types"; +import { sendUserActionTrackingEvent } from "../../../../../utils/actions/sendUserActionTrackingEvent"; +import { Table } from "../../../common/Table"; +import { TableText } from "../../../common/TableText"; +import { handleEnvironmentTableRowClick } from "../../../handleEnvironmentTableRowClick"; +import { trackingEvents } from "../../../tracking"; +import { HighlightCard } from "../../common/HighlightCard"; +import { addEnvironmentColumns } from "../addEnvironmentColumns"; +import type { EndpointScalingHighlightCardProps } from "./types"; + +export const EndpointScalingHighlightCard = ({ + data +}: EndpointScalingHighlightCardProps) => { + const { scope, environments } = useConfigSelector(); + + const columnHelper = + createColumnHelper>(); + + const metricsColumns = [ + columnHelper.accessor( + (x) => x.metrics.find((x) => x.id === "IncreasePercentage"), + { + header: "Increased by", + cell: (info) => { + const metric = info.getValue(); + const value = metric ? `${String(metric.value)}%` : ""; + return metric ? {value} : null; + } + } + ) + ]; + + const columns = addEnvironmentColumns(columnHelper, metricsColumns); + + const handleTableRowClick = ( + row: Row> + ) => { + sendUserActionTrackingEvent( + trackingEvents.TOP_ISSUES_CARD_TABLE_ROW_CLICKED, + { + insightType: data.insightType + } + ); + handleEnvironmentTableRowClick( + scope, + environments, + row.original.environmentId, + ScopeChangeEvent.HighlightsTopIssuesCardItemClicked + ); + }; + + return ( + > + columns={columns} + data={data.environments} + onRowClick={handleTableRowClick} + /> + } + /> + ); +}; diff --git a/src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/mockData.ts b/src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/mockData.ts new file mode 100644 index 000000000..48a0f8807 --- /dev/null +++ b/src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/mockData.ts @@ -0,0 +1,70 @@ +import type { + EndpointScalingMetrics, + HighlightData +} from "../../../../../redux/services/types"; +import { InsightType } from "../../../../../types"; +import { InsightStatus } from "../../../../Insights/types"; + +export const mockedEndpointScalingMetrics: EndpointScalingMetrics = [ + { + id: "IncreasePercentage", + value: 50 + } +]; + +export const mockedEndpointScalingHighlightData: HighlightData = + { + insightType: InsightType.EndpointScaling, + asset: { + name: "spanName", + displayName: "displayName", + instrumentationLibrary: "instrumentationLibrary", + spanCodeObjectId: "spanCodeObjectId", + methodCodeObjectId: "methodCodeObjectId", + kind: "kind" + }, + environments: [ + { + environmentId: "1", + environmentName: "Dev", + insightStatus: InsightStatus.Active, + insightCriticality: 0.8, + metrics: mockedEndpointScalingMetrics + }, + { + environmentId: "2", + environmentName: "Staging", + insightStatus: InsightStatus.Active, + insightCriticality: 0.8, + metrics: mockedEndpointScalingMetrics + }, + { + environmentId: "3", + environmentName: "Production", + insightStatus: InsightStatus.Active, + insightCriticality: 0.8, + metrics: mockedEndpointScalingMetrics + }, + { + environmentId: "4", + environmentName: "Env1", + insightStatus: InsightStatus.Active, + insightCriticality: 0.8, + metrics: mockedEndpointScalingMetrics + }, + { + environmentId: "5", + environmentName: "Env2", + insightStatus: InsightStatus.Active, + insightCriticality: 0.8, + metrics: mockedEndpointScalingMetrics + }, + { + environmentId: "6", + environmentName: "Env3", + insightStatus: InsightStatus.Active, + insightCriticality: 0.8, + metrics: mockedEndpointScalingMetrics + } + ] + }; diff --git a/src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/types.ts b/src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/types.ts new file mode 100644 index 000000000..16f6b75b7 --- /dev/null +++ b/src/components/Highlights/TopIssues/highlightCards/EndpointScalingHighlightCard/types.ts @@ -0,0 +1,8 @@ +import type { + EndpointScalingMetrics, + HighlightData +} from "../../../../../redux/services/types"; + +export interface EndpointScalingHighlightCardProps { + data: HighlightData; +} diff --git a/src/components/Insights/InsightTicketRenderer/index.tsx b/src/components/Insights/InsightTicketRenderer/index.tsx index 3883181cd..b03832a22 100644 --- a/src/components/Insights/InsightTicketRenderer/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/index.tsx @@ -121,7 +121,7 @@ export const InsightTicketRenderer = ({ if (isSpanScalingBadlyInsight(data.insight)) { const ticketData = data as InsightTicketInfo; const selectedRootCause = data.insight.rootCauseSpans.find( - (r) => r.spanCodeObjectId == data.spanCodeObjectId + (r) => r.spanCodeObjectId === data.spanCodeObjectId ); if (selectedRootCause) { return ( diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/index.tsx index 2038750c8..8992a9a49 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/index.tsx @@ -22,6 +22,7 @@ import { isEndpointLowUsageInsight, isEndpointNormalUsageInsight, isEndpointQueryOptimizationV2Insight, + isEndpointScalingInsight, isEndpointSlowdownSourceInsight, isEndpointSpanNPlusOneInsight, isSessionInViewEndpointInsight, @@ -43,6 +44,7 @@ import { EndpointBreakdownInsightCard } from "./insightCards/EndpointBreakdownIn import { EndpointChattyApiV2InsightCard } from "./insightCards/EndpointChattyApiV2InsightCard"; import { EndpointHighNumberOfQueriesInsightCard } from "./insightCards/EndpointHighNumberOfQueriesInsightCard"; import { EndpointQueryOptimizationV2InsightCard } from "./insightCards/EndpointQueryOptimizationV2InsightCard"; +import { EndpointScalingInsightCard } from "./insightCards/EndpointScalingInsightCard"; import { EndpointSessionInViewInsightCard } from "./insightCards/EndpointSessionInViewInsightCard"; import { EndpointSlowdownSourceInsightCard } from "./insightCards/EndpointSlowdownSourceInsightCard"; import { EndpointSpanNPlusOneInsightCard } from "./insightCards/EndpointSpanNPlusOneInsightInsightCard"; @@ -518,5 +520,24 @@ export const InsightCardRenderer = ({ ); } + if (isEndpointScalingInsight(insight)) { + return ( + + ); + } + return null; }; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/EndpointScalingInsightCard.stories.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/EndpointScalingInsightCard.stories.tsx new file mode 100644 index 000000000..cf07df4c6 --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/EndpointScalingInsightCard.stories.tsx @@ -0,0 +1,36 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { EndpointScalingInsightCard } from "."; +import { mockedEndpointScalingInsight } 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/EndpointScalingInsightCard", + component: EndpointScalingInsightCard, + 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: mockedEndpointScalingInsight + } +}; + +export const WithSpan: Story = { + args: { + insight: { ...mockedEndpointScalingInsight, issueLocation: "Span" } + } +}; + +export const WithRootCause: Story = { + args: { + insight: { ...mockedEndpointScalingInsight, issueLocation: "SpanRootCause" } + } +}; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/index.tsx new file mode 100644 index 000000000..61fa0f132 --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/index.tsx @@ -0,0 +1,159 @@ +import { useConfigSelector } from "../../../../../../../store/config/useConfigSelector"; +import type { InsightType } from "../../../../../../../types"; +import { getDurationString } from "../../../../../../../utils/getDurationString"; +import { TraceIcon } from "../../../../../../common/icons/12px/TraceIcon"; +import { Button } from "../../../../../../common/v3/Button"; +import { JiraButton } from "../../../../../../common/v3/JiraButton"; +import { Tooltip } from "../../../../../../common/v3/Tooltip"; +import type { SourceSpanInfo, Trace } from "../../../../../types"; +import { InsightCard } from "../common/InsightCard"; +import { ColumnsContainer } from "../common/InsightCard/ColumnsContainer"; +import { KeyValue } from "../common/InsightCard/KeyValue"; +import { ListItem } from "../common/InsightCard/ListItem"; +import { AssetLink, ContentContainer, Description, Details } from "../styles"; +import * as s from "./styles"; +import type { EndpointScalingInsightCardProps } from "./types"; + +export const EndpointScalingInsightCard = ({ + insight, + onAssetLinkClick, + onTraceButtonClick, + onJiraTicketCreate, + isJiraHintEnabled, + onHistogramButtonClick, + onGoToSpan, + isMarkAsReadButtonEnabled, + viewMode, + onDismissalChange, + tooltipBoundaryRef +}: EndpointScalingInsightCardProps) => { + const { isJaegerEnabled } = useConfigSelector(); + + const handleLinkClick = (spanCodeObjectId: string) => () => { + onAssetLinkClick(spanCodeObjectId, insight.type); + }; + + const handleTraceButtonClick = + (trace: Trace, insightType: InsightType, spanCodeObjectId: string) => + () => { + onTraceButtonClick(trace, insightType, spanCodeObjectId); + }; + + const handleTicketInfoButtonClick = ( + spanCodeObjectId: string | undefined, + event: string + ) => { + if (onJiraTicketCreate) { + onJiraTicketCreate(insight, spanCodeObjectId, event); + } + }; + + const renderRootCause = (sourceSpanInfo: SourceSpanInfo) => { + const spanName = sourceSpanInfo.displayName; + const traceId = sourceSpanInfo.sampleTraceId; + const spanCodeObjectId = sourceSpanInfo.spanCodeObjectId; + + const buttons = [ + + ]; + + if (isJaegerEnabled && traceId) { + buttons.push( +