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/Admin/common/MainSidebarOverlay/MainSidebar/Issues/index.tsx b/src/components/Admin/common/MainSidebarOverlay/MainSidebar/Issues/index.tsx index 676e9b65a..bd07bdb39 100644 --- a/src/components/Admin/common/MainSidebarOverlay/MainSidebar/Issues/index.tsx +++ b/src/components/Admin/common/MainSidebarOverlay/MainSidebar/Issues/index.tsx @@ -19,22 +19,19 @@ import { setIssuesInsightInfoToOpenTicket } from "../../../../../../redux/slices/repositorySlice"; import { isUndefined } from "../../../../../../typeGuards/isUndefined"; -import { InsightType } from "../../../../../../types"; import type { ChangeScopePayload } from "../../../../../../utils/actions/changeScope"; import { sendUserActionTrackingEvent } from "../../../../../../utils/actions/sendUserActionTrackingEvent"; import { EyeIcon } from "../../../../../common/icons/16px/EyeIcon"; import { Pagination } from "../../../../../common/Pagination"; import { NewButton } from "../../../../../common/v3/NewButton"; import { EmptyState } from "../../../../../Insights/EmptyState"; +import { getInsightToShowJiraHint } from "../../../../../Insights/InsightsCatalog/InsightsPage"; import { EmptyState as InsightsPageEmptyState } from "../../../../../Insights/InsightsCatalog/InsightsPage/EmptyState"; import { InsightCardRenderer } from "../../../../../Insights/InsightsCatalog/InsightsPage/InsightCardRenderer"; import { actions } from "../../../../../Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/hooks/useDismissal"; import { ViewMode } from "../../../../../Insights/InsightsCatalog/types"; import { InsightTicketRenderer } from "../../../../../Insights/InsightTicketRenderer"; -import { - type CodeObjectInsight, - type GenericCodeObjectInsight -} from "../../../../../Insights/types"; +import { type GenericCodeObjectInsight } from "../../../../../Insights/types"; import { trackingEvents } from "../../../../tracking"; import { SuggestionBar } from "../SuggestionBar"; import * as s from "./styles"; @@ -42,23 +39,6 @@ import type { IssuesProps } from "./types"; const PAGE_SIZE = 10; -const getInsightToShowJiraHint = (insights: CodeObjectInsight[]): number => { - const insightsWithJiraButton = [ - InsightType.EndpointSpanNPlusOne, - InsightType.SpaNPlusOne, - InsightType.SpanEndpointBottleneck, - InsightType.EndpointBottleneck, - InsightType.SpanQueryOptimization, - InsightType.EndpointHighNumberOfQueries, - InsightType.EndpointQueryOptimizationV2, - InsightType.SpanScaling - ]; - - return insights.findIndex((insight) => - insightsWithJiraButton.includes(insight.type) - ); -}; - export const Issues = ({ isTransitioning, query, 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..5eaf17319 100644 --- a/src/components/Insights/InsightTicketRenderer/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/index.tsx @@ -2,6 +2,7 @@ import { isEndpointBottleneckInsight, isEndpointHighNumberOfQueriesInsight, isEndpointQueryOptimizationV2Insight, + isEndpointScalingInsight, isEndpointSpanNPlusOneInsight, isSpanEndpointBottleneckInsight, isSpanNPlusOneInsight, @@ -13,6 +14,7 @@ import type { EndpointBottleneckInsight, EndpointHighNumberOfQueriesInsight, EndpointQueryOptimizationV2Insight, + EndpointScalingInsight, EndpointSpanNPlusOneInsight, InsightTicketInfo, SpanEndpointBottleneckInsight, @@ -24,6 +26,8 @@ import type { import { EndpointBottleneckInsightTicket } from "./insightTickets/EndpointBottleneckInsightTicket"; import { EndpointHighNumberOfQueriesInsightTicket } from "./insightTickets/EndpointHighNumberOfQueriesInsightTicket"; import { EndpointQueryOptimizationV2InsightTicket } from "./insightTickets/EndpointQueryOptimizationV2InsightTicket"; +import { EndpointScalingInsightTicket } from "./insightTickets/EndpointScalingInsightTicket"; +import { EndpointScalingWithSpanInsightTicket } from "./insightTickets/EndpointScalingWithSpanInsightTicket"; import { EndpointSpanNPlusOneInsightTicket } from "./insightTickets/EndpointSpanNPlusOneInsightTicket"; import { SpanEndpointBottleneckInsightTicket } from "./insightTickets/SpanEndpointBottleneckInsightTicket"; import { SpanPerformanceAnomalyInsightTicket } from "./insightTickets/SpanPerformanceAnomalyInsightTicket"; @@ -121,7 +125,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 ( @@ -143,6 +147,30 @@ export const InsightTicketRenderer = ({ } } + if (isEndpointScalingInsight(data.insight)) { + const ticketData = data as InsightTicketInfo; + + switch (data.insight.issueLocation) { + case "SpanRootCause": + case "Span": + return ( + + ); + case "Endpoint": + return ( + + ); + } + } + if (isSpanPerformanceAnomalyInsight(data.insight)) { const ticketData = data as InsightTicketInfo; return ( diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointScalingInsightTicket/EndpointScalingInsightTicket.stories.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointScalingInsightTicket/EndpointScalingInsightTicket.stories.tsx new file mode 100644 index 000000000..ce9fc5747 --- /dev/null +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointScalingInsightTicket/EndpointScalingInsightTicket.stories.tsx @@ -0,0 +1,26 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { EndpointScalingInsightTicket } from "."; +import { mockedEndpointScalingInsight } from "../../../InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/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/EndpointScalingInsightTicket", + component: EndpointScalingInsightTicket, + 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: mockedEndpointScalingInsight + } + } +}; diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointScalingInsightTicket/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointScalingInsightTicket/index.tsx new file mode 100644 index 000000000..b7f085d04 --- /dev/null +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointScalingInsightTicket/index.tsx @@ -0,0 +1,93 @@ +import type { ReactElement } from "react"; +import { useConfigSelector } from "../../../../../store/config/useConfigSelector"; +import { intersperse } from "../../../../../utils/intersperse"; +import { DigmaSignature } from "../../../../common/DigmaSignature"; +import type { Attachment } from "../../../../common/JiraTicket/types"; +import type { EndpointScalingInsight } 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 { getScalingSummary } from "../common/Scaling"; +import { ScalingDuration } from "../common/Scaling/ScalingDuration"; +import { ScalingMessage } from "../common/Scaling/ScalingMessage"; +import { ScalingTestedConcurrency } from "../common/Scaling/ScalingTestedConcurrency"; +import type { InsightTicketProps } from "../types"; + +export const EndpointScalingInsightTicket = ({ + data, + onClose, + backendInfo +}: InsightTicketProps) => { + const { jaegerApiPath, digmaApiProxyPrefix } = useConfigSelector(); + + const { commitInfos, codeLocations, isLoading } = + useSpanDataSource( + data.insight.spanInfo, + data.insight, + data.insight.environment + ); + + const renderDescription = () => { + return ( + <> + {intersperse( + [ + , + , + , + , + , + + ], + (i: number) => ( +
+ ) + )} + + ); + }; + + const summary = getScalingSummary(data.insight); + + const traceId = data.insight.sourceSpanInfo?.sampleTraceId; + const attachmentTrace = getTraceAttachment( + `${window.location.origin}${jaegerApiPath ?? ""}`, + traceId + ); + const attachmentHistogram = getHistogramAttachment( + `${window.location.origin}${digmaApiProxyPrefix ?? "/api"}`, + data.insight, + backendInfo + ); + const attachments: Attachment[] = [ + ...(attachmentTrace ? [attachmentTrace] : []), + ...(attachmentHistogram ? [attachmentHistogram] : []) + ]; + + return ( + + ); +}; diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointScalingWithSpanInsightTicket/EndpointScalingWithSpanInsightTicket.stories.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointScalingWithSpanInsightTicket/EndpointScalingWithSpanInsightTicket.stories.tsx new file mode 100644 index 000000000..3a3aa2d8e --- /dev/null +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointScalingWithSpanInsightTicket/EndpointScalingWithSpanInsightTicket.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { EndpointScalingWithSpanInsightTicket } from "."; +import { + mockedEndpointScalingWithRootCauseInsight, + mockedEndpointScalingWithSpanInsight +} from "../../../InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/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/EndpointScalingWithSpanInsightTicket", + component: EndpointScalingWithSpanInsightTicket, + 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: mockedEndpointScalingWithSpanInsight + } + } +}; + +export const WithRootCause: Story = { + args: { + data: { + insight: mockedEndpointScalingWithRootCauseInsight + } + } +}; diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointScalingWithSpanInsightTicket/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointScalingWithSpanInsightTicket/index.tsx new file mode 100644 index 000000000..f62dc0c71 --- /dev/null +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointScalingWithSpanInsightTicket/index.tsx @@ -0,0 +1,106 @@ +import type { ReactElement } from "react"; +import { useConfigSelector } from "../../../../../store/config/useConfigSelector"; +import { InsightType } from "../../../../../types"; +import { intersperse } from "../../../../../utils/intersperse"; +import { DigmaSignature } from "../../../../common/DigmaSignature"; +import type { Attachment } from "../../../../common/JiraTicket/types"; +import type { + EndpointScalingInsight, + 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 { getScalingSummary } from "../common/Scaling"; +import { ScalingDuration } from "../common/Scaling/ScalingDuration"; +import { ScalingMessage } from "../common/Scaling/ScalingMessage"; +import { ScalingRootCauses } from "../common/Scaling/ScalingRootCauses"; +import { ScalingTestedConcurrency } from "../common/Scaling/ScalingTestedConcurrency"; +import type { InsightTicketProps } from "../types"; + +export const EndpointScalingWithSpanInsightTicket = ({ + data, + onClose, + backendInfo +}: InsightTicketProps) => { + const { jaegerApiPath, digmaApiProxyPrefix } = useConfigSelector(); + + const { commitInfos, spanInsight, isLoading, codeLocations } = + useEndpointDataSource( + data.insight.sourceSpanInfo, + InsightType.SpanScaling, + data.insight.environment + ); + + const renderDescription = () => { + return ( + <> + {intersperse( + [ + , + , + , + ...(data.insight.issueLocation === "SpanRootCause" && + data.insight.sourceSpanInfo + ? [ + + ] + : []), + , + , + + ], + (i: number) => ( +
+ ) + )} + + ); + }; + + const summary = getScalingSummary(data.insight); + + const traceId = data.insight.sourceSpanInfo?.sampleTraceId; + const attachmentTrace = getTraceAttachment( + `${window.location.origin}${jaegerApiPath ?? ""}`, + traceId + ); + const attachmentHistogram = getHistogramAttachment( + `${window.location.origin}${digmaApiProxyPrefix ?? "/api"}`, + data.insight, + backendInfo + ); + const attachments: Attachment[] = [ + ...(attachmentTrace ? [attachmentTrace] : []), + ...(attachmentHistogram ? [attachmentHistogram] : []) + ]; + + return ( + + ); +}; diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/SpanScalingByRootCauseInsightTicket/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/SpanScalingByRootCauseInsightTicket/index.tsx index 7fe8b2c10..73602406e 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/SpanScalingByRootCauseInsightTicket/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/SpanScalingByRootCauseInsightTicket/index.tsx @@ -11,14 +11,12 @@ import { CommitInfos } from "../common/CommitInfos"; import { getHistogramAttachment } from "../common/getHistogramAttachment"; import { getTraceAttachment } from "../common/getTraceAttachment"; import { InsightJiraTicket } from "../common/InsightJiraTicket"; -import { - ScalingIssueAffectedEndpoints, - ScalingIssueDuration, - ScalingIssueMessage, - ScalingIssueRootCauses, - ScalingIssueTestedConcurrency, - getScalingIssueSummary -} from "../common/SpanScaling"; +import { getScalingSummary } from "../common/Scaling"; +import { ScalingDuration } from "../common/Scaling/ScalingDuration"; +import { ScalingMessage } from "../common/Scaling/ScalingMessage"; +import { ScalingRootCauses } from "../common/Scaling/ScalingRootCauses"; +import { ScalingTestedConcurrency } from "../common/Scaling/ScalingTestedConcurrency"; +import { SpanScalingAffectedEndpoints } from "../common/Scaling/SpanScalingEndpoints"; import type { InsightTicketProps } from "../types"; export const SpanScalingByRootCauseInsightTicket = ({ @@ -45,21 +43,18 @@ export const SpanScalingByRootCauseInsightTicket = ({ <> {intersperse( [ - , - , + , - , - , + , - , {intersperse( [ - , - , + , - , - , + , - , ep.sampleTraceId) diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingDuration/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingDuration/index.tsx similarity index 82% rename from src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingDuration/index.tsx rename to src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingDuration/index.tsx index b4f290ae8..0f55a229e 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingDuration/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingDuration/index.tsx @@ -1,7 +1,7 @@ import { getDurationString } from "../../../../../../../utils/getDurationString"; import type { ScalingIssueCommonProps } from "../types"; -export const ScalingIssueDuration = ({ insight }: ScalingIssueCommonProps) => { +export const ScalingDuration = ({ insight }: ScalingIssueCommonProps) => { if (!insight) { return null; } diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingMessage/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingMessage/index.tsx similarity index 67% rename from src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingMessage/index.tsx rename to src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingMessage/index.tsx index e0bd3355b..3d02868ef 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingMessage/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingMessage/index.tsx @@ -1,6 +1,6 @@ import type { ScalingIssueCommonProps } from "../types"; -export const ScalingIssueMessage = ({ insight }: ScalingIssueCommonProps) => { +export const ScalingMessage = ({ insight }: ScalingIssueCommonProps) => { if (!insight) { return null; } diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingRootCauses/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingRootCauses/index.tsx new file mode 100644 index 000000000..1fbdb8639 --- /dev/null +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingRootCauses/index.tsx @@ -0,0 +1,19 @@ +import * as s from "../styles"; +import type { SpanScalingRootCausesProps as ScalingRootCausesProps } from "./types"; + +export const ScalingRootCauses = ({ spanInfos }: ScalingRootCausesProps) => { + if (!spanInfos || spanInfos.length === 0) { + return null; + } + + return ( +
+
Root causes:
+ + {spanInfos.map((x) => ( +
  • {x.displayName}
  • + ))} +
    +
    + ); +}; diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingRootCauses/types.ts b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingRootCauses/types.ts new file mode 100644 index 000000000..04f498b94 --- /dev/null +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingRootCauses/types.ts @@ -0,0 +1,5 @@ +import type { SpanInfo } from "../../../../../../../redux/services/types"; + +export interface SpanScalingRootCausesProps { + spanInfos?: SpanInfo[]; +} diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingTestedConcurrency/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingTestedConcurrency/index.tsx similarity index 81% rename from src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingTestedConcurrency/index.tsx rename to src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingTestedConcurrency/index.tsx index 168790430..087eb3572 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingTestedConcurrency/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/ScalingTestedConcurrency/index.tsx @@ -1,6 +1,6 @@ import type { ScalingIssueCommonProps } from "../types"; -export const ScalingIssueTestedConcurrency = ({ +export const ScalingTestedConcurrency = ({ insight }: ScalingIssueCommonProps) => { if (!insight) { diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingEndpoints/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/SpanScalingEndpoints/index.tsx similarity index 82% rename from src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingEndpoints/index.tsx rename to src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/SpanScalingEndpoints/index.tsx index 401ead4af..23694d527 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingEndpoints/index.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/SpanScalingEndpoints/index.tsx @@ -1,11 +1,11 @@ import { DELIMITER } from "../../../../../../../constants"; import { trimEndpointScheme } from "../../../../../../../utils/trimEndpointScheme"; import * as s from "../styles"; -import type { ScalingIssueCommonProps } from "../types"; +import type { SpanScalingAffectedEndpointsProps } from "./types"; -export const ScalingIssueAffectedEndpoints = ({ +export const SpanScalingAffectedEndpoints = ({ insight -}: ScalingIssueCommonProps) => { +}: SpanScalingAffectedEndpointsProps) => { if (!insight) { return null; } diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/SpanScalingEndpoints/types.ts b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/SpanScalingEndpoints/types.ts new file mode 100644 index 000000000..d3232880c --- /dev/null +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/SpanScalingEndpoints/types.ts @@ -0,0 +1,5 @@ +import type { SpanScalingInsight } from "../../../../../types"; + +export interface SpanScalingAffectedEndpointsProps { + insight: SpanScalingInsight | null; +} diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/index.tsx new file mode 100644 index 000000000..6ed9676ac --- /dev/null +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/index.tsx @@ -0,0 +1,19 @@ +import { getCriticalityLabel } from "../../../../../../utils/getCriticalityLabel"; +import type { + EndpointScalingInsight, + SpanScalingInsight +} from "../../../../types"; + +export const getScalingSummary = ( + insight: SpanScalingInsight | EndpointScalingInsight | null +) => { + const criticalityString = + insight && insight.criticality > 0 + ? `Criticality: ${getCriticalityLabel(insight.criticality)}` + : ""; + const summary = ["Scaling Issue", criticalityString] + .filter(Boolean) + .join(" - "); + + return summary; +}; diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/styles.ts b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/styles.ts similarity index 100% rename from src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/styles.ts rename to src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/styles.ts diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/types.ts b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/types.ts new file mode 100644 index 000000000..ee094b3db --- /dev/null +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/Scaling/types.ts @@ -0,0 +1,8 @@ +import type { + EndpointScalingInsight, + SpanScalingInsight +} from "../../../../types"; + +export interface ScalingIssueCommonProps { + insight: SpanScalingInsight | EndpointScalingInsight | null; +} diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingRootCauses/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingRootCauses/index.tsx deleted file mode 100644 index a41495211..000000000 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/SpanScalingRootCauses/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as s from "../styles"; -import type { ScalingIssueCommonProps } from "../types"; - -export const ScalingIssueRootCauses = ({ - insight -}: ScalingIssueCommonProps) => { - if (!insight) { - return null; - } - - const rootCauses = insight.rootCauseSpans; - - if (rootCauses.length === 0) { - return null; - } - - return ( -
    -
    Root causes:
    - - {rootCauses.map((x) => ( -
  • {x.displayName}
  • - ))} -
    -
    - ); -}; diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/index.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/index.tsx deleted file mode 100644 index 85dfdb8f1..000000000 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { getCriticalityLabel } from "../../../../../../utils/getCriticalityLabel"; -import type { SpanScalingInsight } from "../../../../types"; -import { ScalingIssueDuration as ScalingIssueDuration_ } from "./SpanScalingDuration"; -import { ScalingIssueAffectedEndpoints as ScalingIssueAffectedEndpoints_ } from "./SpanScalingEndpoints"; -import { ScalingIssueMessage as ScalingIssueMessage_ } from "./SpanScalingMessage"; -import { ScalingIssueRootCauses as ScalingIssueRootCauses_ } from "./SpanScalingRootCauses"; -import { ScalingIssueTestedConcurrency as ScalingIssueTestedConcurrency_ } from "./SpanScalingTestedConcurrency"; - -export const getScalingIssueSummary = (insight: SpanScalingInsight | null) => { - const criticalityString = - insight && insight.criticality > 0 - ? `Criticality: ${getCriticalityLabel(insight.criticality)}` - : ""; - const summary = ["Scaling Issue", criticalityString] - .filter(Boolean) - .join(" - "); - - return summary; -}; - -export const ScalingIssueMessage = ScalingIssueMessage_; - -export const ScalingIssueTestedConcurrency = ScalingIssueTestedConcurrency_; - -export const ScalingIssueAffectedEndpoints = ScalingIssueAffectedEndpoints_; - -export const ScalingIssueRootCauses = ScalingIssueRootCauses_; - -export const ScalingIssueDuration = ScalingIssueDuration_; diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/types.ts b/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/types.ts deleted file mode 100644 index c4d069119..000000000 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/common/SpanScaling/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { SpanScalingInsight } from "../../../../types"; - -export interface ScalingIssueCommonProps { - insight: SpanScalingInsight | null; -} diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/getHistogramAttachment.ts b/src/components/Insights/InsightTicketRenderer/insightTickets/common/getHistogramAttachment.ts index 5988dd06b..a4ea9fddd 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/common/getHistogramAttachment.ts +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/getHistogramAttachment.ts @@ -1,9 +1,9 @@ import type { GetAboutResponse } from "../../../../../redux/services/types"; import type { BackendInfo } from "../../../../common/App/types"; import { getInsightHistogramUrl } from "../../../getInsightHistogramUrl"; -import type { SpanInsight } from "../../../types"; +import type { EndpointInsight, SpanInsight } from "../../../types"; -export const getHistogramAttachment = ( +export const getHistogramAttachment = ( baseURL: string | null, insight: T | null, backendInfo: BackendInfo | GetAboutResponse | null 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..7d37369ba --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/EndpointScalingInsightCard.stories.tsx @@ -0,0 +1,40 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { EndpointScalingInsightCard } from "."; +import { + mockedEndpointScalingInsight, + mockedEndpointScalingWithRootCauseInsight, + mockedEndpointScalingWithSpanInsight +} 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: mockedEndpointScalingWithSpanInsight + } +}; + +export const WithRootCause: Story = { + args: { + insight: mockedEndpointScalingWithRootCauseInsight + } +}; 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..a5b92d743 --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/index.tsx @@ -0,0 +1,160 @@ +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( +