From db8a6397ca002a8af62bbb89be3db2e2461e9ad9 Mon Sep 17 00:00:00 2001 From: olehp Date: Thu, 29 Feb 2024 17:08:27 +0200 Subject: [PATCH 1/4] Bottlneck --- src/components/Insights/common/Info/index.tsx | 12 ++ src/components/Insights/common/Info/styles.ts | 7 ++ .../EndpointBottleneckInsight.stories.tsx | 23 ++++ .../EndpointBottleneckInsight/index.tsx | 75 +++++++++++++ .../EndpointBottleneckInsight/mockData.ts | 103 ++++++++++++++++++ .../EndpointBottleneckInsight/styles.ts | 6 + .../EndpointBottleneckInsight/types.ts | 13 +++ .../EndpointNPlusOneInsight/index.tsx | 15 +-- .../SpanEndpointBottleneckInsight.stories.tsx | 48 ++++++++ .../SpanEndpointBottleneckInsight/index.tsx | 99 +++++++++++++++++ .../SpanEndpointBottleneckInsight/styles.ts | 8 ++ .../SpanEndpointBottleneckInsight/types.ts | 10 ++ .../insights/SpanNPlusOneInsight/index.tsx | 15 +-- src/components/Insights/types.ts | 1 + 14 files changed, 415 insertions(+), 20 deletions(-) create mode 100644 src/components/Insights/common/Info/index.tsx create mode 100644 src/components/Insights/common/Info/styles.ts create mode 100644 src/components/Insights/common/insights/EndpointBottleneckInsight/EndpointBottleneckInsight.stories.tsx create mode 100644 src/components/Insights/common/insights/EndpointBottleneckInsight/index.tsx create mode 100644 src/components/Insights/common/insights/EndpointBottleneckInsight/mockData.ts create mode 100644 src/components/Insights/common/insights/EndpointBottleneckInsight/styles.ts create mode 100644 src/components/Insights/common/insights/EndpointBottleneckInsight/types.ts create mode 100644 src/components/Insights/common/insights/SpanEndpointBottleneckInsight/SpanEndpointBottleneckInsight.stories.tsx create mode 100644 src/components/Insights/common/insights/SpanEndpointBottleneckInsight/index.tsx create mode 100644 src/components/Insights/common/insights/SpanEndpointBottleneckInsight/styles.ts create mode 100644 src/components/Insights/common/insights/SpanEndpointBottleneckInsight/types.ts diff --git a/src/components/Insights/common/Info/index.tsx b/src/components/Insights/common/Info/index.tsx new file mode 100644 index 000000000..1b2a4e30b --- /dev/null +++ b/src/components/Insights/common/Info/index.tsx @@ -0,0 +1,12 @@ +import { InfoCircleIcon } from "../../../common/icons/InfoCircleIcon"; +import { Tooltip } from "../../../common/v3/Tooltip"; +import * as s from "./styles"; + +export const Info = (props: { text: string; name: string }) => ( + + +
{props.name}
+ +
+
+); diff --git a/src/components/Insights/common/Info/styles.ts b/src/components/Insights/common/Info/styles.ts new file mode 100644 index 000000000..7ba3943ef --- /dev/null +++ b/src/components/Insights/common/Info/styles.ts @@ -0,0 +1,7 @@ +import styled from "styled-components"; + +export const InfoContainer = styled.div` + display: flex; + gap: 4px; + align-items: center; +`; diff --git a/src/components/Insights/common/insights/EndpointBottleneckInsight/EndpointBottleneckInsight.stories.tsx b/src/components/Insights/common/insights/EndpointBottleneckInsight/EndpointBottleneckInsight.stories.tsx new file mode 100644 index 000000000..3c48997e8 --- /dev/null +++ b/src/components/Insights/common/insights/EndpointBottleneckInsight/EndpointBottleneckInsight.stories.tsx @@ -0,0 +1,23 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { EndpointBottleneckInsight } from "."; +import { mockedSpanBottleneckInsight } from "./mockData"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Insights/common/insights/EndpointBottleneckInsight", + component: EndpointBottleneckInsight, + 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: mockedSpanBottleneckInsight + } +}; diff --git a/src/components/Insights/common/insights/EndpointBottleneckInsight/index.tsx b/src/components/Insights/common/insights/EndpointBottleneckInsight/index.tsx new file mode 100644 index 000000000..287824eaa --- /dev/null +++ b/src/components/Insights/common/insights/EndpointBottleneckInsight/index.tsx @@ -0,0 +1,75 @@ +import { getDurationString } from "../../../../../utils/getDurationString"; +import { roundTo } from "../../../../../utils/roundTo"; +import { sendTrackingEvent } from "../../../../../utils/sendTrackingEvent"; +import { trackingEvents } from "../../../tracking"; +import { Info } from "../../Info"; +import { InsightCard } from "../../InsightCard"; +import { ColumnsContainer } from "../../InsightCard/ColumnsContainer"; +import { KeyValue } from "../../InsightCard/KeyValue"; +import { ContentContainer, Description, Details } from "../styles"; +import * as s from "./styles"; +import { EndpointBottleneckInsightProps } from "./types"; + +export const EndpointBottleneckInsight = ( + props: EndpointBottleneckInsightProps +) => { + const { insight } = props; + const { span } = insight; + + const handleSpanLinkClick = (spanCodeObjectId: string) => { + props.onAssetLinkClick(spanCodeObjectId, insight.type); + }; + + const handleTicketInfoButtonClick = ( + spanCodeObjectId: string, + event: string + ) => { + sendTrackingEvent(trackingEvents.JIRA_TICKET_INFO_BUTTON_CLICKED, { + insightType: insight.type + }); + props.onJiraTicketCreate && + props.onJiraTicketCreate(insight, spanCodeObjectId, event); + }; + + const spanName = span.spanInfo.displayName; + const spanCodeObjectId = span.spanInfo.spanCodeObjectId; + + return ( + +
+ Asset + handleSpanLinkClick(spanCodeObjectId)} + /> +
+ + + {roundTo(span.probabilityOfBeingBottleneck * 100, 2)}% + + + } + > + {span.requestPercentage}% + + + {getDurationString(span.avgDurationWhenBeingBottleneck)} + + + + } + onRecalculate={props.onRecalculate} + onRefresh={props.onRefresh} + onJiraButtonClick={handleTicketInfoButtonClick} + /> + ); +}; diff --git a/src/components/Insights/common/insights/EndpointBottleneckInsight/mockData.ts b/src/components/Insights/common/insights/EndpointBottleneckInsight/mockData.ts new file mode 100644 index 000000000..d13ee250a --- /dev/null +++ b/src/components/Insights/common/insights/EndpointBottleneckInsight/mockData.ts @@ -0,0 +1,103 @@ +import { + EndpointBottleneckInsight, + InsightCategory, + InsightScope, + InsightType +} from "../../../types"; + +export const mockedSpanBottleneckInsight: EndpointBottleneckInsight = { + sourceSpanCodeObjectInsight: "sourceSpanCodeObjectInsightId", + id: "60b55792-8262-4c5d-9628-7dce7979ad6d", + firstDetected: "2023-12-05T17:25:47.010Z", + lastDetected: "2024-01-05T13:14:47.010Z", + criticality: 0.7, + firstCommitId: "b3f7b3f", + lastCommitId: "a1b2c3d", + deactivatedCommitId: null, + reopenCount: 0, + ticketLink: null, + impact: 0, + name: "Bottleneck Detected", + type: InsightType.EndpointBottleneck, + category: InsightCategory.Performance, + specifity: 3, + importance: 2, + span: { + spanInfo: { + name: "DelayAsync", + displayName: "DelayAsync", + instrumentationLibrary: "SampleInsightsController", + spanCodeObjectId: "span:SampleInsightsController$_$DelayAsync", + methodCodeObjectId: null, + kind: "Internal", + codeObjectId: null + }, + probabilityOfBeingBottleneck: 0.6923076923076923, + avgDurationWhenBeingBottleneck: { + value: 2, + unit: "sec", + raw: 2002883447.4474475 + }, + criticality: 0, + ticketLink: "https://digma.ai/1", + requestPercentage: 90, + p50: { + fraction: 0, + maxDuration: { + value: 0, + unit: "ns", + raw: 0 + } + }, + p95: { + fraction: 0, + maxDuration: { + value: 0, + unit: "ns", + raw: 0 + } + }, + p99: { + fraction: 0, + maxDuration: { + value: 0, + unit: "ns", + raw: 0 + } + } + }, + scope: InsightScope.EntrySpan, + endpointSpan: "HTTP GET SampleInsights/lock/{milisec}", + spanCodeObjectId: + "span:OpenTelemetry.Instrumentation.AspNetCore$_$HTTP GET SampleInsights/lock/{milisec}", + route: "epHTTP:HTTP GET SampleInsights/lock/{milisec}", + serviceName: "Sample.MoneyTransfer.API", + spanInfo: { + name: "HTTP GET SampleInsights/lock/{milisec}", + displayName: "HTTP GET SampleInsights/lock/{milisec}", + instrumentationLibrary: "OpenTelemetry.Instrumentation.AspNetCore", + spanCodeObjectId: + "span:OpenTelemetry.Instrumentation.AspNetCore$_$HTTP GET SampleInsights/lock/{milisec}", + methodCodeObjectId: + "method:Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$Lock(Double)", + kind: "Server", + codeObjectId: + "Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$Lock(Double)" + }, + shortDisplayInfo: { + title: "", + targetDisplayName: "HTTP GET SampleInsights/lock/{milisec}", + subtitle: "2 spans", + description: "WaitForLock 4.58 sec" + }, + codeObjectId: + "Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$Lock(Double)", + decorators: null, + environment: "BOB-LAPTOP[LOCAL]", + severity: 0, + isRecalculateEnabled: true, + prefixedCodeObjectId: + "method:Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$Lock(Double)", + customStartTime: null, + actualStartTime: "2023-06-16T11:10:30.349Z" +}; diff --git a/src/components/Insights/common/insights/EndpointBottleneckInsight/styles.ts b/src/components/Insights/common/insights/EndpointBottleneckInsight/styles.ts new file mode 100644 index 000000000..39395a748 --- /dev/null +++ b/src/components/Insights/common/insights/EndpointBottleneckInsight/styles.ts @@ -0,0 +1,6 @@ +import styled from "styled-components"; +import { ListItem } from "../../InsightCard/ListItem"; + +export const SpanListItem = styled(ListItem)` + padding: 4px; +`; diff --git a/src/components/Insights/common/insights/EndpointBottleneckInsight/types.ts b/src/components/Insights/common/insights/EndpointBottleneckInsight/types.ts new file mode 100644 index 000000000..ae9b728ed --- /dev/null +++ b/src/components/Insights/common/insights/EndpointBottleneckInsight/types.ts @@ -0,0 +1,13 @@ +import { + EndpointBottleneckInsight, + InsightProps, + InsightType +} from "../../../types"; + +export interface EndpointBottleneckInsightProps extends InsightProps { + insight: EndpointBottleneckInsight; + onAssetLinkClick: ( + spanCodeObjectId: string, + insightType: InsightType + ) => void; +} diff --git a/src/components/Insights/common/insights/EndpointNPlusOneInsight/index.tsx b/src/components/Insights/common/insights/EndpointNPlusOneInsight/index.tsx index 1c2a605b3..15682a308 100644 --- a/src/components/Insights/common/insights/EndpointNPlusOneInsight/index.tsx +++ b/src/components/Insights/common/insights/EndpointNPlusOneInsight/index.tsx @@ -2,10 +2,9 @@ import { useContext } from "react"; import { getDurationString } from "../../../../../utils/getDurationString"; import { sendTrackingEvent } from "../../../../../utils/sendTrackingEvent"; import { ConfigContext } from "../../../../common/App/ConfigContext"; -import { InfoCircleIcon } from "../../../../common/icons/InfoCircleIcon"; -import { Tooltip } from "../../../../common/v3/Tooltip"; import { trackingEvents } from "../../../tracking"; import { InsightType, Trace } from "../../../types"; +import { Info } from "../../Info"; import { InsightCard } from "../../InsightCard"; import { ColumnsContainer } from "../../InsightCard/ColumnsContainer"; import { KeyValue } from "../../InsightCard/KeyValue"; @@ -81,14 +80,10 @@ export const EndpointNPlusOneInsight = ( {span.occurrences} - -
Requests
- -
- + } > {span.requestPercentage}% diff --git a/src/components/Insights/common/insights/SpanEndpointBottleneckInsight/SpanEndpointBottleneckInsight.stories.tsx b/src/components/Insights/common/insights/SpanEndpointBottleneckInsight/SpanEndpointBottleneckInsight.stories.tsx new file mode 100644 index 000000000..33f8aab16 --- /dev/null +++ b/src/components/Insights/common/insights/SpanEndpointBottleneckInsight/SpanEndpointBottleneckInsight.stories.tsx @@ -0,0 +1,48 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { SpanBottleneckEndpoints } from "."; +import { mockedBottleneckInsight } from "../../../BottleneckInsight/mockData"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Insights/common/insights/SpanBottleneckEndpoints", + component: SpanBottleneckEndpoints, + 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: { + ...mockedBottleneckInsight, + slowEndpoints: [ + ...mockedBottleneckInsight.slowEndpoints, + { + ...mockedBottleneckInsight.slowEndpoints[0], + requestPercentage: 100, + endpointInfo: { + ...mockedBottleneckInsight.slowEndpoints[0].endpointInfo, + route: `${mockedBottleneckInsight.slowEndpoints[0].endpointInfo.route}1`, + spanCodeObjectId: `${mockedBottleneckInsight.slowEndpoints[0].endpointInfo.spanCodeObjectId}1` + } + } + ] + } + } +}; + +export const WithoutInsight: Story = {}; + +export const WithNoSlowEndpoints: Story = { + args: { + insight: { + ...mockedBottleneckInsight, + slowEndpoints: [] + } + } +}; diff --git a/src/components/Insights/common/insights/SpanEndpointBottleneckInsight/index.tsx b/src/components/Insights/common/insights/SpanEndpointBottleneckInsight/index.tsx new file mode 100644 index 000000000..586d88f0f --- /dev/null +++ b/src/components/Insights/common/insights/SpanEndpointBottleneckInsight/index.tsx @@ -0,0 +1,99 @@ +import { useState } from "react"; +import { getDurationString } from "../../../../../utils/getDurationString"; +import { roundTo } from "../../../../../utils/roundTo"; +import { trimEndpointScheme } from "../../../../../utils/trimEndpointScheme"; +import { Info } from "../../Info"; +import { InsightCard } from "../../InsightCard"; +import { ColumnsContainer } from "../../InsightCard/ColumnsContainer"; +import { KeyValue } from "../../InsightCard/KeyValue"; +import { ListItem } from "../../InsightCard/ListItem"; +import { Select } from "../../InsightCard/Select"; +import { ContentContainer, Description, Details } from "../styles"; +import * as s from "./styles"; +import { SpanEndpointBottleneckEndpointsProps } from "./types"; + +export const SpanBottleneckEndpoints = ( + props: SpanEndpointBottleneckEndpointsProps +) => { + const [selectedEndpoint, setSelectedEndpoint] = useState( + props.insight?.slowEndpoints.length ? props.insight.slowEndpoints[0] : null + ); + + const handleSpanLinkClick = (spanCodeObjectId?: string) => { + spanCodeObjectId && + props.onAssetLinkClick(spanCodeObjectId, props?.insight.type); + }; + + const endpoints = props.insight.slowEndpoints; + + if (endpoints.length === 0) { + return null; + } + + return ( + +
+ Affected Endpoints ({endpoints.length}) +