diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/AffectedEndpointsSelector.stories.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/AffectedEndpointsSelector.stories.tsx new file mode 100644 index 000000000..d9dcaa287 --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/AffectedEndpointsSelector.stories.tsx @@ -0,0 +1,39 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { AffectedEndpointsSelector } from "."; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Insights/InsightsCatalog/InsightPage/AffectedEndpointsSelector", + component: AffectedEndpointsSelector, + 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: { + value: "spanCodeObjectId1", + options: [ + { + route: "test", + serviceName: "test1", + spanCodeObjectId: "spanCodeObjectId1" + }, + { + route: "test", + serviceName: "test1", + spanCodeObjectId: "spanCodeObjectId2" + }, + { + route: "test", + serviceName: "test1", + spanCodeObjectId: "spanCodeObjectId2" + } + ] + } +}; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/EndpointOption/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/EndpointOption/index.tsx new file mode 100644 index 000000000..4861abf9c --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/EndpointOption/index.tsx @@ -0,0 +1,39 @@ +import { Link } from "../../../../../common/v3/Link"; +import { Tooltip } from "../../../../../common/v3/Tooltip"; +import * as s from "./styles"; +import { EndpointOptionProps } from "./types"; + +export const EndpointOption = ({ + serviceName, + route, + spanCodeObjectId, + onSpanLinkClick, + selected, + hideCopyIcon, + onClick +}: EndpointOptionProps) => { + const title = `${serviceName} ${route}`; + + return ( + + + onClick && onClick()} + > + {serviceName} + + {spanCodeObjectId && onSpanLinkClick ? ( + onSpanLinkClick(spanCodeObjectId)}> + {route} + + ) : ( + {route} + )} + + + {!hideCopyIcon && } + + ); +}; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/EndpointOption/styles.ts b/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/EndpointOption/styles.ts new file mode 100644 index 000000000..d9570dc6e --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/EndpointOption/styles.ts @@ -0,0 +1,60 @@ +import styled, { css } from "styled-components"; +import { footnoteRegularTypography } from "../../../../../common/App/typographies"; +import { CopyButton } from "../../../../../common/v3/CopyButton"; +import { EndpointNameProps } from "./types"; + +export const StyledCopyButton = styled(CopyButton)` + display: none; + padding: 0; +`; + +export const Container = styled.div` + ${footnoteRegularTypography} + + display: flex; + gap: 4px; + align-items: center; + width: 100%; + + &:hover { + ${StyledCopyButton} { + display: flex; + } + } +`; + +export const EndpointName = styled.div` + display: flex; + gap: 4px; + overflow: hidden; + border-radius: 4px; + ${({ $selected }) => { + if (!$selected) { + return css` + width: 100%; + padding: 4px 8px; + ${ServiceName} { + width: 151px; + } + `; + } + return null; + }} + + ${({ $clickable }) => + $clickable + ? css` + &:hover { + background: ${({ theme }) => theme.colors.v3.surface.highlight}; + } + ` + : ""} +`; + +export const ServiceName = styled.span` + max-width: 50%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: ${({ theme }) => theme.colors.v3.text.tertiary}; +`; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/EndpointOption/types.ts b/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/EndpointOption/types.ts new file mode 100644 index 000000000..01a7c2f3f --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/EndpointOption/types.ts @@ -0,0 +1,14 @@ +export interface EndpointOptionProps { + serviceName: string; + route: string; + spanCodeObjectId?: string; + selected?: boolean; + onSpanLinkClick?: (spanCodeObjectId: string) => void; + hideCopyIcon?: boolean; + onClick?: ((spanCodeObjectId?: string) => void) | null; +} + +export interface EndpointNameProps { + $selected?: boolean; + $clickable?: boolean; +} diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/index.tsx new file mode 100644 index 000000000..a2b0024e2 --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/index.tsx @@ -0,0 +1,69 @@ +import { ReactNode } from "react"; +import { trimEndpointScheme } from "../../../../../utils/trimEndpointScheme"; +import { Select } from "../insightCards/common/InsightCard/Select"; +import { CustomContentProps } from "../insightCards/common/InsightCard/Select/types"; +import { EndpointOption } from "./EndpointOption"; +import * as s from "./styles"; +import { AffectedEndpointsSelectorProps, Option } from "./types"; + +const renderOptions = ( + endpoints: Option[], + handleLinkClick: (spanCodeObjectId?: string) => void +): { + label: string; + customContent: (props: CustomContentProps) => ReactNode; + value: string; +}[] => + endpoints.map((x) => { + const spanCodeObjectId = x.spanCodeObjectId; + const route = trimEndpointScheme(x.route); + return { + label: route, + customContent: ({ isSelected, onClick }) => ( + + ), + value: spanCodeObjectId + }; + }); + +export const AffectedEndpointsSelector = ({ + onAssetLinkClick, + insightType, + value, + options, + onChange +}: AffectedEndpointsSelectorProps) => { + const handleSpanLinkClick = (spanCodeObjectId?: string) => { + spanCodeObjectId && onAssetLinkClick(spanCodeObjectId, insightType); + }; + + return ( + { const selected = endpoints.find( (x) => - x.endpointInfo.entrySpanCodeObjectId === selectedOption + x.endpointInfo.entrySpanCodeObjectId === + selectedOption?.spanCodeObjectId ) ?? null; setSelectedEndpoint(selected); }} - options={renderOptions(endpoints, handleSpanLinkClick)} + options={endpoints.map((x) => ({ + route: x.endpointInfo.route, + serviceName: x.endpointInfo.serviceName, + spanCodeObjectId: x.endpointInfo.entrySpanCodeObjectId + }))} + insightType={insight.type} + onAssetLinkClick={handleSpanLinkClick} /> {isJaegerEnabled && selectedEndpoint && ( diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/SpanEndpointBottleneckInsightCard/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/SpanEndpointBottleneckInsightCard/index.tsx index 79efc8b0f..b8e8fa3a8 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/SpanEndpointBottleneckInsightCard/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/SpanEndpointBottleneckInsightCard/index.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useState } from "react"; +import { useState } from "react"; import { useGlobalStore } from "../../../../../../containers/Main/stores/useGlobalStore"; import { isNull } from "../../../../../../typeGuards/isNull"; import { getDurationString } from "../../../../../../utils/getDurationString"; @@ -6,37 +6,15 @@ import { trimEndpointScheme } from "../../../../../../utils/trimEndpointScheme"; import { TraceIcon } from "../../../../../common/icons/12px/TraceIcon"; import { Button } from "../../../../../common/v3/Button"; import { Tooltip } from "../../../../../common/v3/Tooltip"; -import { BottleneckEndpointInfo, InsightType, Trace } from "../../../../types"; +import { InsightType, Trace } from "../../../../types"; +import { AffectedEndpointsSelector } from "../../AffectedEndpointsSelector"; import { InsightCard } from "../common/InsightCard"; import { ColumnsContainer } from "../common/InsightCard/ColumnsContainer"; -import { EndpointSelectedOption } from "../common/InsightCard/EndpointSelectedOption"; import { KeyValue } from "../common/InsightCard/KeyValue"; -import { Select } from "../common/InsightCard/Select"; import { ContentContainer, Description, Details } from "../styles"; import * as s from "./styles"; import { SpanEndpointBottleneckInsightCardProps } from "./types"; -const renderOptions = ( - endpoints: BottleneckEndpointInfo[], - handleLinkClick: (spanCodeObjectId?: string) => void -): { label: string; customContent: ReactNode; value: string }[] => - endpoints.map((x) => { - const spanCodeObjectId = x.endpointInfo.spanCodeObjectId; - const route = trimEndpointScheme(x.endpointInfo.route); - return { - label: route, - customContent: ( - - ), - value: spanCodeObjectId - }; - }); - export const SpanEndpointBottleneckInsightCard = ({ insight, onJiraTicketCreate, @@ -100,17 +78,25 @@ export const SpanEndpointBottleneckInsightCard = ({ Affected Endpoints ({slowEndpoints.length}) -