diff --git a/src/components/Navigation/ScopeBar/LinkedEndpointsMenu/LinkedEndpointsMenu.stories.tsx b/src/components/Navigation/ScopeBar/LinkedEndpointsMenu/LinkedEndpointsMenu.stories.tsx new file mode 100644 index 000000000..2972a370a --- /dev/null +++ b/src/components/Navigation/ScopeBar/LinkedEndpointsMenu/LinkedEndpointsMenu.stories.tsx @@ -0,0 +1,33 @@ +import { Meta, StoryObj } from "@storybook/react"; + +import { LinkedEndpointsMenu } from "."; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Navigation/ScopeBar/LinkedEndpointsMenu", + component: LinkedEndpointsMenu, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen" + } +}; + +export default meta; + +export const Default: StoryObj = { + args: { + endpoints: [ + { + spanCodeObjectId: "span:codeObject", + displayName: + "testMethodCallasdasdsadsadasdasdasdasdasdasdasdsadasdsads", + environment: "TEST" + }, + { + spanCodeObjectId: "span:codeObject2", + displayName: "restMethodCall", + environment: "local" + } + ] + } +}; diff --git a/src/components/Navigation/ScopeBar/LinkedEndpointsMenu/index.tsx b/src/components/Navigation/ScopeBar/LinkedEndpointsMenu/index.tsx new file mode 100644 index 000000000..01fce27f9 --- /dev/null +++ b/src/components/Navigation/ScopeBar/LinkedEndpointsMenu/index.tsx @@ -0,0 +1,35 @@ +import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; +import { HTTPClientIcon } from "../../../common/icons/HTTPClientIcon"; +import { MenuList } from "../../common/MenuList"; +import { LinkedEndpoint } from "../../SpanInfo/types"; +import { trackingEvents } from "../../tracking"; +import * as s from "./styles"; +import { LinkedEndpointsMenuProps } from "./types"; + +export const LinkedEndpointsMenu = ({ + endpoints, + onEndpointsClick +}: LinkedEndpointsMenuProps) => { + const handleMenuItemClick = (endpoint: LinkedEndpoint) => { + sendUserActionTrackingEvent(trackingEvents.CODE_LOCATION_SELECTED); + onEndpointsClick(endpoint); + }; + + return ( + + This client assets calls the following endpoint: + ({ + id: x.spanCodeObjectId, + customContent: ( + handleMenuItemClick(x)}> + + {x.displayName} + + ), + disabled: false + }))} + /> + + ); +}; diff --git a/src/components/Navigation/ScopeBar/LinkedEndpointsMenu/styles.ts b/src/components/Navigation/ScopeBar/LinkedEndpointsMenu/styles.ts new file mode 100644 index 000000000..f47d844c3 --- /dev/null +++ b/src/components/Navigation/ScopeBar/LinkedEndpointsMenu/styles.ts @@ -0,0 +1,41 @@ +import styled from "styled-components"; +import { + bodyRegularTypography, + footnoteRegularTypography +} from "../../../common/App/typographies"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + gap: 4px; +`; + +export const MenuItem = styled.div` + display: flex; + flex-direction: 4px; + padding: 0 8px; + gap: 4px; + align-items: center; + color: ${({ theme }) => theme.colors.v3.icon.brandSecondary}; + cursor: pointer; + + ${bodyRegularTypography} + + &:hover { + text-decoration: underline; + } + + span { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } +`; + +export const Title = styled.div` + ${footnoteRegularTypography} + color: ${({ theme }) => theme.colors.v3.text.tertiary}; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +`; diff --git a/src/components/Navigation/ScopeBar/LinkedEndpointsMenu/types.ts b/src/components/Navigation/ScopeBar/LinkedEndpointsMenu/types.ts new file mode 100644 index 000000000..64195706a --- /dev/null +++ b/src/components/Navigation/ScopeBar/LinkedEndpointsMenu/types.ts @@ -0,0 +1,6 @@ +import { LinkedEndpoint } from "../../SpanInfo/types"; + +export interface LinkedEndpointsMenuProps { + endpoints: LinkedEndpoint[]; + onEndpointsClick: (endpoint: LinkedEndpoint) => void; +} diff --git a/src/components/Navigation/ScopeBar/ScopeBar.stories.tsx b/src/components/Navigation/ScopeBar/ScopeBar.stories.tsx index f225ffd09..01a958022 100644 --- a/src/components/Navigation/ScopeBar/ScopeBar.stories.tsx +++ b/src/components/Navigation/ScopeBar/ScopeBar.stories.tsx @@ -68,7 +68,20 @@ const mockedCodeContext: CodeContext = { export const Default: Story = { args: { scope: mockedScope, - codeContext: mockedCodeContext + codeContext: mockedCodeContext, + linkedEndpoints: [ + { + spanCodeObjectId: + "span:codeObjectasdasdasdsadasdassadasdasdasdsadasdasdasdasdasdasdasdsadsadasdasd", + displayName: "testMethodCall", + environment: "TEST" + }, + { + spanCodeObjectId: "span:codeObject2", + displayName: "restMethodCall", + environment: "local" + } + ] } }; @@ -94,7 +107,19 @@ export const HasMultipleCodeLocations: Story = { ] } }, - codeContext: mockedCodeContext + codeContext: mockedCodeContext, + linkedEndpoints: [ + { + spanCodeObjectId: "span:codeObject", + displayName: "testMethodCallasdasdasdasdadasdsadasdsadsadasdasdsads", + environment: "TEST" + }, + { + spanCodeObjectId: "span:codeObject2", + displayName: "restMethodCall", + environment: "local" + } + ] } }; @@ -104,6 +129,7 @@ export const AlreadyAtCode: Story = { codeContext: { ...mockedCodeContext, methodId: mockedScope.span?.methodId ?? null - } + }, + linkedEndpoints: [] } }; diff --git a/src/components/Navigation/ScopeBar/index.tsx b/src/components/Navigation/ScopeBar/index.tsx index 489a09f84..afd8c859c 100644 --- a/src/components/Navigation/ScopeBar/index.tsx +++ b/src/components/Navigation/ScopeBar/index.tsx @@ -1,20 +1,24 @@ import { useEffect, useState } from "react"; import { history } from "../../../containers/Main/history"; import { isString } from "../../../typeGuards/isString"; +import { changeScope } from "../../../utils/actions/changeScope"; import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; import { trackingEvents as mainTrackingEvents } from "../../Main/tracking"; import { CodeDetails, Scope } from "../../common/App/types"; import { NewPopover } from "../../common/NewPopover"; +import { ChainIcon } from "../../common/icons/14px/ChainIcon"; import { CrosshairIcon } from "../../common/icons/16px/CrosshairIcon"; import { MaximizeIcon } from "../../common/icons/16px/MaximizeIcon"; import { MinimizeIcon } from "../../common/icons/16px/MinimizeIcon"; import { EndpointIcon } from "../../common/icons/EndpointIcon"; import { NewIconButton } from "../../common/v3/NewIconButton"; import { Tooltip } from "../../common/v3/Tooltip"; +import { LinkedEndpoint } from "../SpanInfo/types"; import { actions } from "../actions"; import { Popup } from "../common/Popup"; import { trackingEvents } from "../tracking"; import { CodeContext, GoToCodeLocationPayload } from "../types"; +import { LinkedEndpointsMenu } from "./LinkedEndpointsMenu"; import { TargetButtonMenu } from "./TargetButtonMenu"; import * as s from "./styles"; import { ScopeBarProps } from "./types"; @@ -60,9 +64,12 @@ export const ScopeBar = ({ codeContext, isExpanded, onExpandCollapseChange, - isSpanInfoEnabled + isSpanInfoEnabled, + linkedEndpoints }: ScopeBarProps) => { const [isTargetButtonMenuOpen, setIsTargetButtonMenuOpen] = useState(false); + const [isLinkedEndpointsMenuOpen, setIsLinkedEndpointsMenuOpen] = + useState(false); const location = history.getCurrentLocation(); const spanDisplayName = scope?.span?.displayName; @@ -94,6 +101,8 @@ export const ScopeBar = ({ (scope.code.codeDetailsList.length > 1 || scope.code.relatedCodeDetailsList.length > 0); + const isLinkedEndpointsButtonEnabled = linkedEndpoints.length > 0; + useEffect(() => { setIsTargetButtonMenuOpen(false); }, [scope]); @@ -108,6 +117,15 @@ export const ScopeBar = ({ setIsTargetButtonMenuOpen(false); }; + const handleLinkedEndpointsClick = (endpoint: LinkedEndpoint) => { + changeScope({ + span: { + spanCodeObjectId: endpoint.spanCodeObjectId + } + }); + setIsLinkedEndpointsMenuOpen(true); + }; + const handleExpandCollapseButtonClick = () => { if (isExpanded) { sendUserActionTrackingEvent( @@ -121,6 +139,11 @@ export const ScopeBar = ({ onExpandCollapseChange(!isExpanded); }; + const handleLinkedEndpointsButtonClick = () => { + sendUserActionTrackingEvent(trackingEvents.LINKED_ENDPOINTS_BUTTON_CLICKED); + setIsLinkedEndpointsMenuOpen(!isLinkedEndpointsMenuOpen); + }; + const handleTargetButtonClick = () => { sendUserActionTrackingEvent(trackingEvents.TARGET_BUTTON_CLICKED); if (scope && scope.code.codeDetailsList.length === 1) { @@ -160,6 +183,32 @@ export const ScopeBar = ({ ) : null} + {isLinkedEndpointsButtonEnabled && ( + + + + } + onOpenChange={setIsLinkedEndpointsMenuOpen} + isOpen={isLinkedEndpointsMenuOpen} + placement={"bottom-start"} + width={"100%"} + > +
+ + + +
+
+ )} {isSpanInfoEnabled && ( theme.colors.v3.icon.primary}; } `; + +export const LinkedEndpointsPopup = styled(Popup)` + margin: 4px 8px; +`; diff --git a/src/components/Navigation/ScopeBar/types.ts b/src/components/Navigation/ScopeBar/types.ts index a7cb39a18..1d66fcdaa 100644 --- a/src/components/Navigation/ScopeBar/types.ts +++ b/src/components/Navigation/ScopeBar/types.ts @@ -1,4 +1,5 @@ import { Scope } from "../../common/App/types"; +import { LinkedEndpoint } from "../SpanInfo/types"; import { CodeContext } from "../types"; export interface ScopeBarProps { @@ -7,4 +8,5 @@ export interface ScopeBarProps { isExpanded: boolean; onExpandCollapseChange: (isExpanded: boolean) => void; isSpanInfoEnabled: boolean; + linkedEndpoints: LinkedEndpoint[]; } diff --git a/src/components/Navigation/SpanInfo/types.ts b/src/components/Navigation/SpanInfo/types.ts index c9a43a668..f0bd9662b 100644 --- a/src/components/Navigation/SpanInfo/types.ts +++ b/src/components/Navigation/SpanInfo/types.ts @@ -11,6 +11,12 @@ export interface GetHighlightsSpanInfoDataPayload { }; } +export interface LinkedEndpoint { + spanCodeObjectId: string; + displayName: string; + environment: string; +} + export interface SpanInfoData { displayName: string; services: string[]; @@ -18,4 +24,5 @@ export interface SpanInfoData { assetTypeId: string; firstSeen?: string; lastSeen?: string; + linkedEndpoints?: LinkedEndpoint[]; } diff --git a/src/components/Navigation/index.tsx b/src/components/Navigation/index.tsx index 29f0ae3e2..28198ba01 100644 --- a/src/components/Navigation/index.tsx +++ b/src/components/Navigation/index.tsx @@ -395,6 +395,11 @@ export const Navigation = () => { isExpanded={isSpanInfoVisible} onExpandCollapseChange={handleScopeDisplayNameExpandCollapseChange} isSpanInfoEnabled={isSpanInfoEnabled} + linkedEndpoints={ + spanInfo?.linkedEndpoints?.filter( + (x) => x.environment === environment?.id + ) ?? [] + } /> ) : ( { + const { size, color } = useIconProps(props); + + return ( + + + + + + + ); +}; + +export const ChainIcon = React.memo(ChainIconComponent);