diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpaNPlusOneInsightCard/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpaNPlusOneInsightCard/index.tsx index 2e59a81fe..e2869ff2d 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpaNPlusOneInsightCard/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpaNPlusOneInsightCard/index.tsx @@ -24,7 +24,8 @@ import type { SpaNPlusOneInsightCardProps } from "./types"; const getSelectorOption = (endpoint: NPlusOneEndpointInfo): Option => ({ route: endpoint.endpointInfo.route, serviceName: endpoint.endpointInfo.serviceName, - spanCodeObjectId: endpoint.endpointInfo.entrySpanCodeObjectId + spanCodeObjectId: endpoint.endpointInfo.entrySpanCodeObjectId, + duration: endpoint.duration }); export const SpaNPlusOneInsightCard = ({ @@ -42,7 +43,12 @@ export const SpaNPlusOneInsightCard = ({ }: SpaNPlusOneInsightCardProps) => { const endpoints = useMemo(() => insight.endpoints ?? [], [insight.endpoints]); const selectorOptions = useMemo( - () => endpoints.map(getSelectorOption), + () => + endpoints + .map(getSelectorOption) + .sort((a, b) => + a.duration && b.duration ? b.duration.raw - a.duration.raw : 0 + ), [endpoints] ); const endpointWithMaxDuration = endpoints.reduce( diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpaNPlusOneInsightCard/mockData.ts b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpaNPlusOneInsightCard/mockData.ts index ee655a83e..eace8685f 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpaNPlusOneInsightCard/mockData.ts +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpaNPlusOneInsightCard/mockData.ts @@ -74,7 +74,7 @@ export const mockedSpaNPlusOneInsight: SpaNPlusOneInsight = { duration: { value: 3.64, unit: "sec", - raw: 1636050588.0 + raw: 3636050588.0 } } ], diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanDurationBreakdownInsightCard/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanDurationBreakdownInsightCard/index.tsx index 1ab983487..fbcf91187 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanDurationBreakdownInsightCard/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanDurationBreakdownInsightCard/index.tsx @@ -13,6 +13,7 @@ import { isNumber } from "../../../../../../../typeGuards/isNumber"; import { FeatureFlag } from "../../../../../../../types"; import { getDurationString } from "../../../../../../../utils/getDurationString"; import { getPercentileLabel } from "../../../../../../../utils/getPercentileLabel"; +import { roundTo } from "../../../../../../../utils/roundTo"; import { Info } from "../../../../../../common/v3/Info"; import { Pagination } from "../../../../../../common/v3/Pagination"; import { Tag } from "../../../../../../common/v3/Tag"; @@ -82,6 +83,10 @@ export const SpanDurationBreakdownInsightCard = ({ backendInfo, FeatureFlag.IS_DURATION_BREAKDOWN_QUANTITY_ENABLED ); + const isPercentageOfCallsSupported = getFeatureFlagValue( + backendInfo, + FeatureFlag.IS_DURATION_BREAKDOWN_QUANTITY_ENABLED + ); const columnHelper = createColumnHelper(); @@ -90,7 +95,7 @@ export const SpanDurationBreakdownInsightCard = ({ header: "Asset", id: "asset", meta: { - width: "60%", + width: "55%", minWidth: 100 }, cell: (info) => { @@ -109,7 +114,7 @@ export const SpanDurationBreakdownInsightCard = ({ id: "quantity", meta: { minWidth: 30, - width: "20%", + width: "15%", info: "The number of times this asset repeats within the trace. The duration shown next to this column represents the overall duration of all asset instances." }, cell: (info) => { @@ -131,12 +136,42 @@ export const SpanDurationBreakdownInsightCard = ({ ); } }), + columnHelper.accessor((row) => row, { + header: "% Calls", + id: "percentageOfCalls", + meta: { + minWidth: 30, + width: "15%", + info: "The percentage of calls triggering this child asset" + }, + cell: (info) => { + const entry = info.getValue(); + + const percentageOfCalls = entry.percentageOfCalls; + if (!percentageOfCalls) { + return null; + } + + const percentageString = `${ + percentageOfCalls < 1 ? "<1" : roundTo(percentageOfCalls, 2) + }%`; + + return ( + + ); + } + }), columnHelper.accessor((row) => row, { header: "Duration", id: "duration", meta: { minWidth: 30, - width: "20%", + width: "15%", info: "The execution time of the asset" }, cell: (info) => { @@ -161,9 +196,12 @@ export const SpanDurationBreakdownInsightCard = ({ }) ]; - let columnVisibility = { quantity: false }; + let columnVisibility = { quantity: false, percentageOfCalls: false }; if (isQuantitySupported) { - columnVisibility = { quantity: true }; + columnVisibility = { ...columnVisibility, quantity: true }; + } + if (isPercentageOfCallsSupported) { + columnVisibility = { ...columnVisibility, percentageOfCalls: true }; } const filteredItems = useMemo( diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanEndpointBottleneckInsightCard/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanEndpointBottleneckInsightCard/index.tsx index a0aae0aa4..0bf734510 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanEndpointBottleneckInsightCard/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanEndpointBottleneckInsightCard/index.tsx @@ -26,7 +26,8 @@ import type { SpanEndpointBottleneckInsightCardProps } from "./types"; const getSelectorOption = (endpoint: BottleneckEndpointInfo): Option => ({ route: trimEndpointScheme(endpoint.endpointInfo.route), serviceName: endpoint.endpointInfo.serviceName, - spanCodeObjectId: endpoint.endpointInfo.spanCodeObjectId + spanCodeObjectId: endpoint.endpointInfo.spanCodeObjectId, + duration: endpoint.avgDurationWhenBeingBottleneck }); export const SpanEndpointBottleneckInsightCard = ({ @@ -48,7 +49,12 @@ export const SpanEndpointBottleneckInsightCard = ({ [insight.slowEndpoints] ); const selectorOptions: Option[] = useMemo( - () => slowEndpoints.map(getSelectorOption), + () => + slowEndpoints + .map(getSelectorOption) + .sort((a, b) => + a.duration && b.duration ? b.duration.raw - a.duration.raw : 0 + ), [slowEndpoints] ); diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanQueryOptimizationInsightCard/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanQueryOptimizationInsightCard/index.tsx index 5bb088935..1bf3a6570 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanQueryOptimizationInsightCard/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpanQueryOptimizationInsightCard/index.tsx @@ -76,7 +76,12 @@ export const SpanQueryOptimizationInsightCard = ({ > {durationString} - + {getDurationString(insight.typicalDuration)} {insight.dbName} diff --git a/src/components/Insights/types.ts b/src/components/Insights/types.ts index 01a17775b..af587cbd1 100644 --- a/src/components/Insights/types.ts +++ b/src/components/Insights/types.ts @@ -317,6 +317,7 @@ export interface SpanDurationBreakdownEntry { spanCodeObjectId: string; percentiles: DurationPercentile[]; codeObjectId: string | null; + percentageOfCalls?: number; } export interface SpanDurationBreakdownInsight extends SpanInsight { diff --git a/src/components/common/AffectedEndpointsSelector/EndpointOption/index.tsx b/src/components/common/AffectedEndpointsSelector/EndpointOption/index.tsx index 9c208d615..1500d2cf9 100644 --- a/src/components/common/AffectedEndpointsSelector/EndpointOption/index.tsx +++ b/src/components/common/AffectedEndpointsSelector/EndpointOption/index.tsx @@ -1,4 +1,3 @@ -import { Link } from "../../v3/Link"; import { Tooltip } from "../../v3/Tooltip"; import * as s from "./styles"; import type { EndpointOptionProps } from "./types"; @@ -10,7 +9,9 @@ export const EndpointOption = ({ onSpanLinkClick, selected, hideCopyIcon, - onClick + onClick, + duration, + hideDuration }: EndpointOptionProps) => { const title = `${serviceName} ${route}`; @@ -23,16 +24,21 @@ export const EndpointOption = ({ onClick={() => onClick && onClick()} > {serviceName} - {spanCodeObjectId && onSpanLinkClick ? ( - onSpanLinkClick(spanCodeObjectId)}> - {route} - - ) : ( - {route} + + {spanCodeObjectId && onSpanLinkClick ? ( + onSpanLinkClick(spanCodeObjectId)}> + {route} + + ) : ( + {route} + )} + + {!hideCopyIcon && } + {!selected && !hideDuration && duration && ( + {duration} )} - {!hideCopyIcon && } ); }; diff --git a/src/components/common/AffectedEndpointsSelector/EndpointOption/styles.ts b/src/components/common/AffectedEndpointsSelector/EndpointOption/styles.ts index a1465822b..f450fe41d 100644 --- a/src/components/common/AffectedEndpointsSelector/EndpointOption/styles.ts +++ b/src/components/common/AffectedEndpointsSelector/EndpointOption/styles.ts @@ -1,6 +1,7 @@ import styled, { css } from "styled-components"; import { footnoteRegularTypography } from "../../App/typographies"; import { CopyButton } from "../../v3/CopyButton"; +import { Link as CommonLink } from "../../v3/Link"; import type { EndpointNameProps } from "./types"; export const StyledCopyButton = styled(CopyButton)` @@ -34,9 +35,7 @@ export const EndpointName = styled.div` width: 100%; padding: 4px 8px; ${ServiceName} { - flex-shrink: 0; - width: 151px; - max-width: none; + width: 30%; } `; } @@ -54,9 +53,30 @@ export const EndpointName = styled.div` `; export const ServiceName = styled.span` - max-width: fit-content; + max-width: 100px; + min-width: 30px; + flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: ${({ theme }) => theme.colors.v3.text.tertiary}; `; + +export const Route = styled.span` + flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +export const Link = styled(CommonLink)` + width: 100%; +`; + +export const Duration = styled.span` + flex-shrink: 0; + width: 80px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; diff --git a/src/components/common/AffectedEndpointsSelector/EndpointOption/types.ts b/src/components/common/AffectedEndpointsSelector/EndpointOption/types.ts index 01a7c2f3f..78f01617c 100644 --- a/src/components/common/AffectedEndpointsSelector/EndpointOption/types.ts +++ b/src/components/common/AffectedEndpointsSelector/EndpointOption/types.ts @@ -6,6 +6,8 @@ export interface EndpointOptionProps { onSpanLinkClick?: (spanCodeObjectId: string) => void; hideCopyIcon?: boolean; onClick?: ((spanCodeObjectId?: string) => void) | null; + duration?: string; + hideDuration?: boolean; } export interface EndpointNameProps { diff --git a/src/components/common/AffectedEndpointsSelector/index.tsx b/src/components/common/AffectedEndpointsSelector/index.tsx index e1ffc6313..a9860d11d 100644 --- a/src/components/common/AffectedEndpointsSelector/index.tsx +++ b/src/components/common/AffectedEndpointsSelector/index.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from "react"; import { DELIMITER } from "../../../constants"; +import { getDurationString } from "../../../utils/getDurationString"; import { trimEndpointScheme } from "../../../utils/trimEndpointScheme"; import { Select } from "../../Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/Select"; import type { CustomContentProps } from "../../Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/InsightCard/Select/types"; @@ -21,6 +22,7 @@ const renderOptions = ( endpoints.map((x) => { const spanCodeObjectId = x.spanCodeObjectId; const route = trimEndpointScheme(x.route); + const durationString = x.duration && getDurationString(x.duration); return { label: route, customContent: ({ isSelected, onClick }) => ( @@ -32,6 +34,7 @@ const renderOptions = ( onSpanLinkClick={handleLinkClick} hideCopyIcon={!isSelected} onClick={onClick} + duration={durationString} /> ), value: getEndpointKey(x) @@ -66,6 +69,7 @@ export const AffectedEndpointsSelector = ({ diff --git a/src/components/common/AffectedEndpointsSelector/types.ts b/src/components/common/AffectedEndpointsSelector/types.ts index 904384459..8305200b5 100644 --- a/src/components/common/AffectedEndpointsSelector/types.ts +++ b/src/components/common/AffectedEndpointsSelector/types.ts @@ -1,3 +1,5 @@ +import type { Duration } from "../../../globals"; + export interface FilterButtonProps { isActive: boolean; onClick: () => void; @@ -20,4 +22,5 @@ export interface Option { serviceName: string; route: string; spanCodeObjectId: string; + duration?: Duration; } diff --git a/src/featureFlags.ts b/src/featureFlags.ts index f023d76d2..a0a054c15 100644 --- a/src/featureFlags.ts +++ b/src/featureFlags.ts @@ -28,7 +28,8 @@ export const featureFlagMinBackendVersions: Record = { "0.3.145", [FeatureFlag.IS_GLOBAL_ERROR_PIN_ENABLED]: "0.3.147", [FeatureFlag.IS_GLOBAL_ERROR_DISMISS_ENABLED]: "0.3.148", - [FeatureFlag.IS_GLOBAL_ERROR_LAST_DAYS_FILTER_ENABLED]: "0.3.149" + [FeatureFlag.IS_GLOBAL_ERROR_LAST_DAYS_FILTER_ENABLED]: "0.3.149", + [FeatureFlag.IS_DURATION_BREAKDOWN_PERCENTAGE_OF_CALLS_ENABLED]: "0.3.193" }; export const getFeatureFlagValue = ( diff --git a/src/types.ts b/src/types.ts index 458e41783..c861118cd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -28,7 +28,8 @@ export enum FeatureFlag { ARE_GLOBAL_ERRORS_CRITICALITY_AND_UNHANDLED_FILTERS_ENABLED, IS_GLOBAL_ERROR_PIN_ENABLED, IS_GLOBAL_ERROR_DISMISS_ENABLED, - IS_GLOBAL_ERROR_LAST_DAYS_FILTER_ENABLED + IS_GLOBAL_ERROR_LAST_DAYS_FILTER_ENABLED, + IS_DURATION_BREAKDOWN_PERCENTAGE_OF_CALLS_ENABLED } export enum InsightType {