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(
+
+ );
+ }
+
+ return (
+
+ Caused by
+
+
+ );
+ };
+
+ const durationRangeString = `${getDurationString(
+ insight.minDuration
+ )} - ${getDurationString(insight.maxDuration)}`;
+
+ return (
+
+
+ {insight.shortDisplayInfo.description}
+
+ {insight.issueLocation === "Span" && insight.sourceSpanInfo && (
+
+ Asset
+
+
+ )}
+
+
+ {insight.maxConcurrency}
+
+
+ {durationRangeString}
+
+
+ {insight.issueLocation === "SpanRootCause" &&
+ insight.sourceSpanInfo &&
+ renderRootCause(insight.sourceSpanInfo)}
+
+ }
+ jiraTicketInfo={{
+ ticketLink: insight.ticketLink,
+ isHintEnabled: isJiraHintEnabled
+ }}
+ onJiraButtonClick={
+ insight.issueLocation !== "SpanRootCause"
+ ? handleTicketInfoButtonClick
+ : undefined
+ }
+ onOpenHistogram={insight.spanInfo ? onHistogramButtonClick : undefined}
+ onGoToSpan={onGoToSpan}
+ isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled}
+ viewMode={viewMode}
+ mainMetric={
+
+ {durationRangeString}
+
+ }
+ onDismissalChange={onDismissalChange}
+ tooltipBoundaryRef={tooltipBoundaryRef}
+ />
+ );
+};
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/mockData.ts b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/mockData.ts
new file mode 100644
index 000000000..400589310
--- /dev/null
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/mockData.ts
@@ -0,0 +1,436 @@
+import { InsightType } from "../../../../../../../types";
+import {
+ InsightCategory,
+ InsightScope,
+ InsightStatus,
+ type EndpointScalingInsight
+} from "../../../../../types";
+
+export const mockedEndpointScalingInsight: EndpointScalingInsight = {
+ name: "Scaling Issue Found",
+ category: InsightCategory.Performance,
+ specifity: 2,
+ importance: 2,
+ turningPointConcurrency: 5,
+ maxConcurrency: 9,
+ minDuration: {
+ value: 9.1,
+ unit: "ms",
+ raw: 9098884.61833865
+ },
+ maxDuration: {
+ value: 34.28,
+ unit: "ms",
+ raw: 34278875.03671072
+ },
+ flowHash: "",
+ issueLocation: "Span",
+ histogram: {
+ lineFunction: {
+ A: 1556615.0413785747,
+ B: 0.29089712035014054,
+ C: 9098883.61833865,
+ KneeX: 5.743450145936153,
+ $type: "exponent"
+ },
+ points: [
+ {
+ x: 1,
+ y: 15968366.170570357,
+ occurrences: 139825,
+ negligible: false
+ },
+ {
+ x: 2,
+ y: 12940692.905096898,
+ occurrences: 59134,
+ negligible: false
+ },
+ {
+ x: 3,
+ y: 9151696.144007443,
+ occurrences: 22934,
+ negligible: false
+ },
+ {
+ x: 4,
+ y: 9098884.61833865,
+ occurrences: 8463,
+ negligible: false
+ },
+ {
+ x: 5,
+ y: 10240765.271654764,
+ occurrences: 3221,
+ negligible: false
+ },
+ {
+ x: 6,
+ y: 13616007.83293301,
+ occurrences: 1249,
+ negligible: false
+ },
+ {
+ x: 7,
+ y: 25946974.649618886,
+ occurrences: 581,
+ negligible: false
+ },
+ {
+ x: 8,
+ y: 30054854.44606414,
+ occurrences: 343,
+ negligible: false
+ },
+ {
+ x: 9,
+ y: 34278875.03671072,
+ occurrences: 227,
+ negligible: false
+ },
+ {
+ x: 10,
+ y: 34937979.086021505,
+ occurrences: 186,
+ negligible: true
+ },
+ {
+ x: 11,
+ y: 29936882.636363633,
+ occurrences: 200,
+ negligible: true
+ },
+ {
+ x: 12,
+ y: 28419015.78726968,
+ occurrences: 199,
+ negligible: true
+ },
+ {
+ x: 13,
+ y: 23508810.331274565,
+ occurrences: 137,
+ negligible: true
+ },
+ {
+ x: 14,
+ y: 20265642.228571426,
+ occurrences: 125,
+ negligible: true
+ },
+ {
+ x: 15,
+ y: 13226994.824561406,
+ occurrences: 152,
+ negligible: true
+ },
+ {
+ x: 16,
+ y: 13032020.495495494,
+ occurrences: 111,
+ negligible: true
+ },
+ {
+ x: 17,
+ y: 13696774.660633486,
+ occurrences: 91,
+ negligible: true
+ },
+ {
+ x: 18,
+ y: 24421427.30829421,
+ occurrences: 71,
+ negligible: true
+ },
+ {
+ x: 19,
+ y: 15494992.174515236,
+ occurrences: 76,
+ negligible: true
+ },
+ {
+ x: 20,
+ y: 14215335.51020408,
+ occurrences: 49,
+ negligible: true
+ },
+ {
+ x: 21,
+ y: 15688993.130366901,
+ occurrences: 61,
+ negligible: true
+ },
+ {
+ x: 22,
+ y: 15562939.108061751,
+ occurrences: 53,
+ negligible: true
+ },
+ {
+ x: 23,
+ y: 10903889.829192549,
+ occurrences: 56,
+ negligible: true
+ },
+ {
+ x: 24,
+ y: 13321730.053191489,
+ occurrences: 47,
+ negligible: true
+ },
+ {
+ x: 25,
+ y: 11680250.27027027,
+ occurrences: 37,
+ negligible: true
+ },
+ {
+ x: 26,
+ y: 10011658.653846154,
+ occurrences: 36,
+ negligible: true
+ },
+ {
+ x: 27,
+ y: 6934111.030595815,
+ occurrences: 46,
+ negligible: true
+ },
+ {
+ x: 28,
+ y: 8205554.336734696,
+ occurrences: 42,
+ negligible: true
+ },
+ {
+ x: 29,
+ y: 5742216.632860041,
+ occurrences: 51,
+ negligible: true
+ },
+ {
+ x: 30,
+ y: 4502219.294871796,
+ occurrences: 52,
+ negligible: true
+ },
+ {
+ x: 31,
+ y: 4079368.4210526315,
+ occurrences: 38,
+ negligible: true
+ },
+ {
+ x: 32,
+ y: 3001028.3203125,
+ occurrences: 48,
+ negligible: true
+ },
+ {
+ x: 33,
+ y: 7576430.303030304,
+ occurrences: 23,
+ negligible: true
+ },
+ {
+ x: 34,
+ y: 8593071.671826623,
+ occurrences: 19,
+ negligible: true
+ },
+ {
+ x: 35,
+ y: 4232771.948051948,
+ occurrences: 11,
+ negligible: true
+ },
+ {
+ x: 36,
+ y: 4337564.087301587,
+ occurrences: 28,
+ negligible: true
+ },
+ {
+ x: 37,
+ y: 5692613.153153154,
+ occurrences: 15,
+ negligible: true
+ },
+ {
+ x: 38,
+ y: 2489640.7268170426,
+ occurrences: 21,
+ negligible: true
+ },
+ {
+ x: 39,
+ y: 1562211.2489660878,
+ occurrences: 31,
+ negligible: true
+ },
+ {
+ x: 40,
+ y: 2299987.0312499995,
+ occurrences: 16,
+ negligible: true
+ },
+ {
+ x: 41,
+ y: 2999119.7936210134,
+ occurrences: 26,
+ negligible: true
+ },
+ {
+ x: 42,
+ y: 1072403.8515406162,
+ occurrences: 34,
+ negligible: true
+ },
+ {
+ x: 43,
+ y: 1160312.0639534884,
+ occurrences: 16,
+ negligible: true
+ },
+ {
+ x: 44,
+ y: 1119005.1136363638,
+ occurrences: 8,
+ negligible: true
+ },
+ {
+ x: 45,
+ y: 1092412.7777777778,
+ occurrences: 4,
+ negligible: true
+ },
+ {
+ x: 46,
+ y: 13479380.43478261,
+ occurrences: 1,
+ negligible: true
+ },
+ {
+ x: 47,
+ y: 1229638.2978723403,
+ occurrences: 1,
+ negligible: true
+ },
+ {
+ x: 50,
+ y: 14254112,
+ occurrences: 1,
+ negligible: true
+ },
+ {
+ x: 54,
+ y: 4611370.987654321,
+ occurrences: 3,
+ negligible: true
+ },
+ {
+ x: 57,
+ y: 4830350.8771929825,
+ occurrences: 1,
+ negligible: true
+ },
+ {
+ x: 60,
+ y: 3911891.666666667,
+ occurrences: 1,
+ negligible: true
+ },
+ {
+ x: 67,
+ y: 3778249.2537313434,
+ occurrences: 1,
+ negligible: true
+ },
+ {
+ x: 74,
+ y: 990583.7837837839,
+ occurrences: 1,
+ negligible: true
+ }
+ ]
+ },
+ extraHistograms: [],
+ sourceSpanInfo: null,
+ scope: InsightScope.EntrySpan,
+ route: "epSpan:Execute ServicePipelineExecutionMessage",
+ serviceName: "Digma.Pipeline.Worker",
+ spanInfo: {
+ uid: "04bf283c-f820-11ef-b7b8-7215f134c660",
+ name: "Execute ServicePipelineExecutionMessage",
+ displayName: "Execute ServicePipelineExecutionMessage",
+ instrumentationLibrary: "PipelineConnector",
+ spanCodeObjectId:
+ "span:PipelineConnector$_$Execute ServicePipelineExecutionMessage",
+ methodCodeObjectId: null,
+ kind: "Internal"
+ },
+ codeObjectId:
+ "span:PipelineConnector$_$Execute ServicePipelineExecutionMessage",
+ id: "64687cde-148b-11f0-b94a-3a87d9c352a4",
+ shortDisplayInfo: {
+ title: "Scaling Issue Found",
+ targetDisplayName: "",
+ subtitle: "",
+ description: "Significant performance degradation at 5 executions/second"
+ },
+ decorators: [
+ {
+ title: "Scaling badly",
+ description:
+ "This code experiences exponential grows in duration after 5 concurrent executions"
+ }
+ ],
+ environment: "ENV1#ID#3140161A-52C6-4994-AF81-51D2F4572E1A",
+ severity: 0.9364017241514355,
+ impact: 0,
+ criticality: 0.9192292131858377,
+ reopenCount: 0,
+ ticketLink: null,
+ isRecalculateEnabled: false,
+ isDismissible: true,
+ isReadable: true,
+ isDismissed: false,
+ customStartTime: null,
+ actualStartTime: "0001-01-01T00:00:00",
+ firstCommitId: "",
+ lastCommitId: null,
+ deactivatedCommitId: null,
+ sourceSpanCodeObjectInsight: "span:Npgsql$_$94E7B3FFF8FCB2D57C85140445C053",
+ firstDetected: "2025-04-08T15:08:56.605051Z",
+ lastDetected: "2025-04-14T13:10:47.127954Z",
+ status: InsightStatus.Active,
+ flags: [],
+ isRead: false,
+ firstFixed: null,
+ lastReopen: null,
+ lastDeactivated: null,
+ ignoreCriticalityOnInsert: false,
+ type: InsightType.EndpointScaling
+};
+
+export const mockedEndpointScalingWithSpanInsight: EndpointScalingInsight = {
+ ...mockedEndpointScalingInsight,
+ issueLocation: "Span",
+ sourceSpanInfo: {
+ sampleTraceId: "02C0295F83E81BAC2048125275936F74",
+ spanCodeObjectId: "span:Npgsql$_$94E7B3FFF8FCB2D57C85140445C053",
+ uid: "e02b14d6-f81f-11ef-909d-7215f134c660",
+ name: "94E7B3FFF8FCB2D57C85140445C053",
+ displayName:
+ "INSERT INTO code_object_insight_related_services (id, code_object_insight_uid, service)\nVALUES (gen_random_uuid(), @InsightUid, @Service_0), (gen_random_uuid(), @InsightUid, @Service_1), (gen_random_uuid(), @InsightUid, @Service_2), (gen_random_uuid(), @InsightUid, @Service_3), (gen_random_uuid(), @InsightUid, @Service_4), (gen_random_uuid(), @InsightUid, @Service_5), (gen_random_uuid(), @InsightUid, @Service_6), (gen_random_uuid(), @InsightUid, @Service_7)\nON CONFLICT (code_object_insight_uid, service) DO NOTHING",
+ instrumentationLibrary: "Npgsql",
+ methodCodeObjectId: null,
+ kind: "Client"
+ }
+};
+
+export const mockedEndpointScalingWithRootCauseInsight: EndpointScalingInsight =
+ {
+ ...mockedEndpointScalingWithSpanInsight,
+ issueLocation: "SpanRootCause"
+ };
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/styles.ts b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/styles.ts
new file mode 100644
index 000000000..926ea8161
--- /dev/null
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/styles.ts
@@ -0,0 +1,8 @@
+import styled from "styled-components";
+import { subscriptRegularTypography } from "../../../../../../common/App/typographies";
+
+export const InsightDescription = styled.span`
+ ${subscriptRegularTypography}
+
+ color: ${({ theme }) => theme.colors.v3.text.primary};
+`;
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/types.ts b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/types.ts
new file mode 100644
index 000000000..d9d9967bf
--- /dev/null
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointScalingInsightCard/types.ts
@@ -0,0 +1,23 @@
+import type { InsightType } from "../../../../../../../types";
+import type { EndpointScalingInsight, Trace } from "../../../../../types";
+import type { InsightCardCommonProps } from "../types";
+
+export interface EndpointScalingInsightCardProps
+ extends InsightCardCommonProps {
+ insight: EndpointScalingInsight;
+ onAssetLinkClick: (
+ spanCodeObjectId: string,
+ insightType: InsightType
+ ) => void;
+ 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/SpanScalingInsightCard/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanScalingInsightCard/index.tsx
index 8a947714d..ec1ae58ad 100644
--- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanScalingInsightCard/index.tsx
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanScalingInsightCard/index.tsx
@@ -165,7 +165,7 @@ export const SpanScalingInsightCard = ({
isHintEnabled: isJiraHintEnabled
}}
onJiraButtonClick={
- insight.rootCauseSpans.length == 0
+ insight.rootCauseSpans.length === 0
? handleTicketInfoButtonClick
: undefined
}
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanScalingInsightCard/styles.ts b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanScalingInsightCard/styles.ts
index eb32c8bc5..d5bdb3457 100644
--- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanScalingInsightCard/styles.ts
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanScalingInsightCard/styles.ts
@@ -12,9 +12,3 @@ export const List = styled.div`
flex-direction: column;
gap: 4px;
`;
-
-export const RootCause = styled.div`
- align-items: center;
- justify-content: space-between;
- gap: 4px;
-`;
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 5658891f8..41712a003 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
@@ -119,7 +119,11 @@ export const InsightCard = ({
};
const handleHistogramButtonClick = () => {
- if (isSpanInsight(insight) && insight.spanInfo && onOpenHistogram) {
+ if (
+ (isSpanInsight(insight) || isEndpointInsight(insight)) &&
+ insight.spanInfo &&
+ onOpenHistogram
+ ) {
onOpenHistogram(
insight.spanInfo.spanCodeObjectId,
insight.type,
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx
index ae3072bc6..f5295906b 100644
--- a/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx
@@ -33,7 +33,9 @@ import type {
isInsightJiraTicketHintShownPayload
} from "./types";
-const getInsightToShowJiraHint = (insights: CodeObjectInsight[]): number => {
+export const getInsightToShowJiraHint = (
+ insights: CodeObjectInsight[]
+): number => {
const insightsWithJiraButton = [
InsightType.EndpointSpanNPlusOne,
InsightType.SpaNPlusOne,
@@ -42,7 +44,8 @@ const getInsightToShowJiraHint = (insights: CodeObjectInsight[]): number => {
InsightType.SpanQueryOptimization,
InsightType.EndpointHighNumberOfQueries,
InsightType.EndpointQueryOptimizationV2,
- InsightType.SpanScaling
+ InsightType.SpanScaling,
+ InsightType.EndpointScaling
];
return insights.findIndex((insight) =>
@@ -145,7 +148,7 @@ const renderEmptyState = (
return ;
}
- if (!scope?.span?.spanCodeObjectId && insightsViewType == "Analytics") {
+ if (!scope?.span?.spanCodeObjectId && insightsViewType === "Analytics") {
return (
{
switch (insightType) {
+ case InsightType.EndpointScaling:
case InsightType.SpanScaling: {
const histogramUrlParams = new URLSearchParams({
env: environmentId,
diff --git a/src/components/Insights/typeGuards.ts b/src/components/Insights/typeGuards.ts
index 654f7d141..a701246d8 100644
--- a/src/components/Insights/typeGuards.ts
+++ b/src/components/Insights/typeGuards.ts
@@ -12,6 +12,7 @@ import type {
EndpointLowUsageInsight,
EndpointNormalUsageInsight,
EndpointQueryOptimizationV2Insight,
+ EndpointScalingInsight,
EndpointSessionInViewInsight,
EndpointSlowdownSourceInsight,
EndpointSpanNPlusOneInsight,
@@ -160,3 +161,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..2b9070f10 100644
--- a/src/components/Insights/types.ts
+++ b/src/components/Insights/types.ts
@@ -40,7 +40,8 @@ export type GenericEndpointInsight =
| EndpointSessionInViewInsight
| EndpointChattyApiV2Insight
| EndpointHighNumberOfQueriesInsight
- | EndpointQueryOptimizationV2Insight;
+ | EndpointQueryOptimizationV2Insight
+ | EndpointScalingInsight;
export type GenericSpanInsight =
| SpanDurationsInsight
@@ -387,21 +388,63 @@ export interface AffectedEndpoint extends SpanInfo {
flowHash: string;
}
-export interface SpanScalingInsight extends SpanInsight {
+interface HistogramInfo {
+ lineFunction: {
+ A: number;
+ B: number;
+ C?: number;
+ KneeX?: number;
+ $type: "linear" | "exponent";
+ };
+ points: {
+ x: number;
+ y: number;
+ occurrences: number;
+ negligible: boolean;
+ }[];
+}
+
+interface HistogramInfoWithLegend extends HistogramInfo {
+ legend: string;
+}
+
+interface ScalingInsightInfo {
+ turningPointConcurrency: number;
+ maxConcurrency: number;
+ minDuration: Duration;
+ maxDuration: Duration;
+ histogram?: HistogramInfo;
+ extraHistograms?: HistogramInfoWithLegend[];
+}
+
+export interface SpanScalingInsight extends SpanInsight, ScalingInsightInfo {
name: "Scaling Issue Found";
type: InsightType.SpanScaling;
category: InsightCategory.Performance;
specifity: InsightSpecificity.OwnInsight;
importance: InsightImportance.Critical;
- turningPointConcurrency: number;
- maxConcurrency: number;
- minDuration: Duration;
- maxDuration: Duration;
rootCauseSpans: RootCauseSpanInfo[];
affectedEndpoints: AffectedEndpoint[] | null;
flowHash: string | null;
}
+export interface SourceSpanInfo extends SpanInfo {
+ sampleTraceId?: string;
+}
+
+export interface EndpointScalingInsight
+ extends EndpointInsight,
+ ScalingInsightInfo {
+ name: "Scaling Issue Found";
+ category: InsightCategory.Performance;
+ specifity: InsightSpecificity.TargetAndReasonFound;
+ importance: InsightImportance.Critical;
+ type: InsightType.EndpointScaling;
+ flowHash: string;
+ issueLocation: "Span" | "Endpoint" | "SpanRootCause";
+ sourceSpanInfo: SourceSpanInfo | null;
+}
+
export interface NPlusOneEndpointInfo {
endpointInfo: {
route: string;
diff --git a/src/components/Main/index.tsx b/src/components/Main/index.tsx
index 9ef645713..a6d33aa21 100644
--- a/src/components/Main/index.tsx
+++ b/src/components/Main/index.tsx
@@ -102,7 +102,7 @@ export const Main = () => {
const isSelectedEnvironmentExist = useMemo(
() =>
Boolean(
- environment && environments?.find((x) => x.id == environment?.id)
+ environment && environments?.find((x) => x.id === environment.id)
),
[environment, environments]
);
diff --git a/src/components/Navigation/index.tsx b/src/components/Navigation/index.tsx
index 3ef3ad757..4b10cea84 100644
--- a/src/components/Navigation/index.tsx
+++ b/src/components/Navigation/index.tsx
@@ -197,7 +197,7 @@ export const Navigation = () => {
if (
environments &&
environments.length > 0 &&
- (!environment || !environments.find((x) => x.id == environment?.id))
+ (!environment || !environments.find((x) => x.id === environment.id))
) {
changeScope({
span: scope?.span
diff --git a/src/components/RecentActivity/CreateEnvironmentWizard/index.tsx b/src/components/RecentActivity/CreateEnvironmentWizard/index.tsx
index 4d3bcbdbc..d026847bb 100644
--- a/src/components/RecentActivity/CreateEnvironmentWizard/index.tsx
+++ b/src/components/RecentActivity/CreateEnvironmentWizard/index.tsx
@@ -191,7 +191,7 @@ export const CreateEnvironmentWizard = ({
};
const getStepVisibility = (key: string) => {
- const stepIndex = getSteps().findIndex((x) => x.key == key);
+ const stepIndex = getSteps().findIndex((x) => x.key === key);
return stepIndex === currentStep;
};
diff --git a/src/components/RecentActivity/index.tsx b/src/components/RecentActivity/index.tsx
index ed468c766..86d90d819 100644
--- a/src/components/RecentActivity/index.tsx
+++ b/src/components/RecentActivity/index.tsx
@@ -489,7 +489,7 @@ export const RecentActivity = () => {
{
if (newEnvId) {
- const newEnv = environments.find((x) => x.id == newEnvId);
+ const newEnv = environments.find((x) => x.id === newEnvId);
if (newEnv) {
changeSelectedEnvironment(
config.scope,
diff --git a/src/components/common/Popover/hooks.ts b/src/components/common/Popover/hooks.ts
index 6da361a68..28355b1ab 100644
--- a/src/components/common/Popover/hooks.ts
+++ b/src/components/common/Popover/hooks.ts
@@ -12,6 +12,7 @@ import {
useRole
} from "@floating-ui/react";
import { createContext, useContext, useMemo, useState } from "react";
+import { isUndefined } from "../../../typeGuards/isUndefined";
import type { ContextType, PopoverProps } from "./types";
export const usePopover = ({
@@ -45,7 +46,7 @@ export const usePopover = ({
const context = data.context;
const click = useClick(context, {
- enabled: controlledOpen == null
+ enabled: isUndefined(controlledOpen)
});
const dismiss = useDismiss(context);
const role = useRole(context);
@@ -73,7 +74,7 @@ export const PopoverContext = createContext(null);
export const usePopoverContext = () => {
const context = useContext(PopoverContext);
- if (context == null) {
+ if (context === null) {
throw new Error("Popover components must be wrapped in ");
}
diff --git a/src/redux/services/typeGuards.ts b/src/redux/services/typeGuards.ts
index bca86d040..47fa3a0be 100644
--- a/src/redux/services/typeGuards.ts
+++ b/src/redux/services/typeGuards.ts
@@ -4,6 +4,7 @@ import type {
EndpointChattyApiV2Metrics,
EndpointHighNumberOfQueriesMetrics,
EndpointQueryOptimizationV2Metrics,
+ EndpointScalingMetrics,
EndpointSessionInViewMetrics,
EndpointSlowdownSourceMetrics,
EndpointSpanNPlusOneMetrics,
@@ -81,3 +82,8 @@ export const isSpanPerformanceAnomalyHighlight = (
highlight: HighlightData
): highlight is HighlightData =>
highlight.insightType === InsightType.SpanPerformanceAnomaly;
+
+export const isEndpointScalingHighlight = (
+ highlight: HighlightData
+): highlight is HighlightData =>
+ highlight.insightType === InsightType.EndpointScaling;
diff --git a/src/redux/services/types.ts b/src/redux/services/types.ts
index 1577acafa..aaadfd46c 100644
--- a/src/redux/services/types.ts
+++ b/src/redux/services/types.ts
@@ -413,6 +413,13 @@ export type SpanScalingMetrics = [
}
];
+export type EndpointScalingMetrics = [
+ {
+ id: "IncreasePercentage";
+ value: number;
+ }
+];
+
export type SpanPerformanceAnomalyMetrics = [
{
id: "P50";
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 {
diff --git a/src/utils/getInsightTypeInfo.ts b/src/utils/getInsightTypeInfo.ts
index 37bf28fa7..9660f7f00 100644
--- a/src/utils/getInsightTypeInfo.ts
+++ b/src/utils/getInsightTypeInfo.ts
@@ -118,6 +118,12 @@ export const getInsightTypeInfo = (
description: descriptionProvider.SpanScalingDescription,
documentationLink: SCALING_ISSUE_DOCUMENTATION_URL
},
+ [InsightType.EndpointScaling]: {
+ icon: ScalesIcon,
+ label: "Scaling Issue Found",
+ description: descriptionProvider.SpanScalingDescription,
+ documentationLink: SCALING_ISSUE_DOCUMENTATION_URL
+ },
[InsightType.SpanUsages]: {
icon: SineIcon,
label: "Top Usage"
diff --git a/src/utils/getInsightTypeOrderPriority.ts b/src/utils/getInsightTypeOrderPriority.ts
index 0f091f98d..8affedee6 100644
--- a/src/utils/getInsightTypeOrderPriority.ts
+++ b/src/utils/getInsightTypeOrderPriority.ts
@@ -14,11 +14,12 @@ export const getInsightTypeOrderPriority = (type: string): number => {
[InsightType.LowUsage]: 30,
[InsightType.EndpointBottleneck]: 40,
[InsightType.NormalUsage]: 50,
- [InsightType.EndpointSpanNPlusOne]: 55,
- [InsightType.EndpointSessionInView]: 56,
- [InsightType.EndpointChattyApiV2]: 57,
- [InsightType.EndpointHighNumberOfQueries]: 58,
- [InsightType.EndpointQueryOptimizationV2]: 59,
+ [InsightType.EndpointSpanNPlusOne]: 51,
+ [InsightType.EndpointSessionInView]: 52,
+ [InsightType.EndpointChattyApiV2]: 53,
+ [InsightType.EndpointHighNumberOfQueries]: 54,
+ [InsightType.EndpointQueryOptimizationV2]: 55,
+ [InsightType.EndpointScaling]: 56,
// Span insights
[InsightType.SpanDurations]: 60,
@@ -28,7 +29,8 @@ export const getInsightTypeOrderPriority = (type: string): number => {
[InsightType.SpanEndpointBottleneck]: 67,
[InsightType.SpanDurationBreakdown]: 68,
[InsightType.SpanNexus]: 69,
- [InsightType.SpanQueryOptimization]: 70
+ [InsightType.SpanQueryOptimization]: 70,
+ [InsightType.SpanPerformanceAnomaly]: 71
};
return insightOrderPriorityMap[type] || Infinity;