diff --git a/.vscode/settings.json b/.vscode/settings.json index e93c83905..0a91227d4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,8 +11,10 @@ "digma", "digmathon", "digmo", - "Hotspot", + "dont", + "hotspot", "leaderboard", + "udemy", "undismiss", "zustand" ] diff --git a/src/components/Assets/AssetList/AssetEntry/index.tsx b/src/components/Assets/AssetList/AssetEntry/index.tsx index ade2ee55a..8ae616523 100644 --- a/src/components/Assets/AssetList/AssetEntry/index.tsx +++ b/src/components/Assets/AssetList/AssetEntry/index.tsx @@ -145,20 +145,26 @@ export const AssetEntry = ({ Performance - {performanceDuration ? performanceDuration.value : "N/A"} - {performanceDuration && ( - {performanceDuration.unit} + {performanceDuration?.value ? ( + <> + {performanceDuration.value} + {performanceDuration.unit} + + ) : ( + "N/A" )} Slowest 5% - {slowestFivePercentDuration - ? slowestFivePercentDuration.value - : "N/A"} - {slowestFivePercentDuration && ( - {slowestFivePercentDuration.unit} + {slowestFivePercentDuration?.value ? ( + <> + {slowestFivePercentDuration.value} + {slowestFivePercentDuration.unit} + + ) : ( + "N/A" )} diff --git a/src/components/Highlights/Highlights.stories.tsx b/src/components/Highlights/Highlights.stories.tsx index d4b1e9e6d..db79aeabf 100644 --- a/src/components/Highlights/Highlights.stories.tsx +++ b/src/components/Highlights/Highlights.stories.tsx @@ -9,7 +9,6 @@ import { DeploymentType } from "../common/App/types"; import { mockedImpactData } from "./Impact/mockData"; import { mockedPerformanceData } from "./Performance/mockData"; import { mockedScalingData } from "./Scaling/mockData"; -import { mockedSpanInfoData } from "./SpanInfo/mockData"; import { mockedTopIssuesData } from "./TopIssues/mockData"; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction @@ -18,9 +17,7 @@ const mockedConfig = { ...initialState, backendInfo: { applicationVersion: - featureFlagMinBackendVersions[ - FeatureFlag.IS_HIGHLIGHTS_SPAN_INFO_ENABLED - ], + featureFlagMinBackendVersions[FeatureFlag.IS_HIGHLIGHTS_SCALING_ENABLED], deploymentType: DeploymentType.HELM, centralize: true } @@ -57,11 +54,6 @@ export const Default: Story = { ], play: () => { window.setTimeout(() => { - window.postMessage({ - type: "digma", - action: mainActions.SET_HIGHLIGHTS_SPAN_INFO_DATA, - payload: mockedSpanInfoData - }); window.postMessage({ type: "digma", action: mainActions.SET_HIGHLIGHTS_TOP_ISSUES_DATA, @@ -96,11 +88,6 @@ export const Empty: Story = { ], play: () => { window.setTimeout(() => { - window.postMessage({ - type: "digma", - action: mainActions.SET_HIGHLIGHTS_SPAN_INFO_DATA, - payload: mockedSpanInfoData - }); window.postMessage({ type: "digma", action: mainActions.SET_HIGHLIGHTS_TOP_ISSUES_DATA, diff --git a/src/components/Highlights/SpanInfo/SpanInfo.stories.tsx b/src/components/Highlights/SpanInfo/SpanInfo.stories.tsx deleted file mode 100644 index b9791167f..000000000 --- a/src/components/Highlights/SpanInfo/SpanInfo.stories.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Meta, StoryObj } from "@storybook/react"; - -import { SpanInfo } from "."; -import { actions as mainActions } from "../../Main/actions"; -import { mockedSpanInfoData } from "./mockData"; - -// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction - -const meta: Meta = { - title: "Highlights/SpanInfo", - component: SpanInfo, - 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 = { - play: () => { - window.setTimeout(() => { - window.postMessage({ - type: "digma", - action: mainActions.SET_HIGHLIGHTS_SPAN_INFO_DATA, - payload: mockedSpanInfoData - }); - }, 1000); - } -}; - -export const LargeText: Story = { - play: () => { - window.setTimeout(() => { - window.postMessage({ - type: "digma", - action: mainActions.SET_HIGHLIGHTS_SPAN_INFO_DATA, - payload: { - ...mockedSpanInfoData, - displayName: - "Erat iriure lorem placerat dolor ex gubergren ipsum ea option justo vero duo gubergren dolores consetetur luptatum labore nulla aliquam ipsum placerat ipsum no est ea eos et dolore esse ea possim consetetur lorem laoreet elitr ipsum eros magna ut vero consectetuer et diam facilisis et et amet diam accusam soluta amet est dolor ea erat takimata exerci invidunt invidunt sed et mazim augue ut ipsum stet ut ut consetetur accusam at sea et stet dolores laoreet autem dolore amet et lobortis dolor sea dolore rebum labore possim ut lorem illum rebum in consetetur erat lorem sadipscing accusam sanctus erat no aliquam magna dolor dolore feugiat in dolor sed augue lorem consetetur euismod odio nibh ut ipsum ut magna lorem aliquyam elitr sit ea gubergren voluptua ea ea vero duis sea dolore hendrerit lorem vero esse vero nonumy tempor eos erat et autem est est in eirmod justo consequat nulla erat sea nostrud ut lorem sit odio eleifend in est voluptua at sed sed possim et aliquam erat ipsum sed sed aliquyam sed diam dolores ipsum at quis in diam tempor autem eleifend labore consequat labore facilisi elitr est et dolores diam ea eirmod eum no lorem eos ea eirmod et invidunt iusto elitr adipiscing elit dolores et ut dolor sed sea sanctus diam duo sed stet sed tincidunt accusam consetetur sed dolore mazim vero dolor autem lorem nulla sea sadipscing nonumy stet odio duo consequat diam sea ipsum tempor et feugait vero ipsum et no accusam sed et qui eum diam ipsum quod nostrud sadipscing lorem rebum duo elit sit dolor invidunt diam feugait dolor amet ut et ea dolor est vero in vero ut lorem sed diam ea aliquyam aliquip labore amet amet et sed eirmod consetetur euismod sit clita eos elitr ut et eos et consetetur rebum rebum ut sea ut praesent hendrerit ipsum erat et sadipscing eirmod euismod tempor ipsum duis magna clita sed eleifend diam facilisis diam elitr stet ea nonumy magna commodo ipsum lorem duis quod ut sit sit amet consequat et imperdiet ea stet clita duo amet ipsum voluptua vero nonumy ut lorem nibh ea nulla elit illum vero dolor at sea feugiat doming justo diam kasd wisi rebum est elitr dolor aliquip duo et nibh dolor ea blandit eos elitr diam justo lorem voluptua vero ea eum adipiscing sed invidunt clita diam ut illum accusam duo sea nostrud ut magna soluta eos et dolore invidunt eos tempor sadipscing lorem dolore dolor sit sit amet sit invidunt aliquyam vel autem doming aliquip labore vel justo aliquyam at ipsum labore sit duis diam ipsum dolore dignissim exerci lorem dolor wisi diam sit stet tempor sanctus sit blandit gubergren voluptua amet dolore consequat tempor takimata dolores sea ea blandit ullamcorper dolor sit sed esse ipsum ipsum laoreet elitr commodo ipsum lorem erat ipsum dolores sed amet quod et dolor facilisi esse sit justo amet justo dolor nostrud dolore ut dignissim at odio nonumy kasd sit diam nonumy eos kasd sanctus dignissim est eirmod sadipscing te est labore duo nonumy kasd hendrerit et vel diam duo amet blandit erat illum amet nisl dolor exerci diam clita at justo et sit et illum sit lorem sit et illum sit te ipsum amet sit placerat velit sed sed et vero stet ipsum sit consetetur et nihil te dolores qui enim kasd dolore dolores dolore justo ipsum dolor diam magna blandit invidunt sanctus ipsum lorem vero sea invidunt erat et sit duo lorem feugiat sanctus sea invidunt et exerci duo sed magna erat dolore et tempor quis et ut eos eirmod labore dolor luptatum duo tempor sed sit ipsum ipsum hendrerit clita sadipscing dolor invidunt dolor kasd iusto iriure lorem consequat invidunt laoreet diam sed in sed lorem lorem dolores erat ut blandit invidunt diam amet accusam dolor consetetur ut accusam sed justo elitr ut lorem nonumy imperdiet velit vero consetetur elitr sadipscing augue minim sit facilisis justo et amet aliquyam tempor sit aliquyam vero sanctus dolor sanctus vero ex at gubergren feugiat id illum consetetur amet dolores consectetuer stet amet nibh duo voluptua invidunt lorem sed est assum diam vero dolores duo eirmod aliquyam labore amet nonumy labore vel te dolore et dolore ipsum labore feugiat gubergren sed voluptua dignissim gubergren justo possim rebum invidunt justo accusam accumsan et rebum velit dolore ipsum euismod nulla tation amet diam adipiscing lorem kasd lorem ea ex labore tation ut diam sed facer nulla diam gubergren at rebum et erat duo justo rebum vero et ipsum et est aliquyam sea nonumy facilisi elitr velit sed sanctus vero sadipscing kasd tempor invidunt magna duis labore sadipscing duo ipsum diam augue et eos aliquyam facilisis eum sea tempor invidunt no diam labore amet congue diam autem dolor eirmod ipsum lorem minim autem gubergren dolore ipsum erat sadipscing nisl sed veniam dolore et amet congue nostrud kasd ut elitr dolore iriure voluptua diam facilisis lorem kasd diam sea sanctus sadipscing lorem accusam tincidunt aliquam consetetur feugait magna gubergren laoreet voluptua nihil ipsum velit nulla sanctus dolor kasd ut zzril nonumy facilisis quis ut vero eirmod sed in at amet lorem voluptua accusam ut at elitr tempor sanctus wisi tincidunt nonumy euismod dolor sed ea cum takimata liber gubergren clita dolor lorem elitr veniam gubergren velit ipsum ipsum sit erat et et nostrud et hendrerit sit in et voluptua takimata stet diam ea diam et erat at justo sea dolor rebum at luptatum accusam sadipscing augue labore nonumy et sea eirmod eu magna labore sea takimata et ut ipsum ea diam et euismod molestie takimata facilisis nonumy vel invidunt eirmod kasd ut in nihil dolores at invidunt in et stet et illum dolor illum erat ipsum praesent magna accusam justo voluptua hendrerit et tation voluptua esse vel amet eu dolore dolore accusam elitr molestie amet lorem sit voluptua sit et est zzril rebum eos amet ea magna consetetur tempor lorem sed tincidunt clita esse duis possim ut ea erat dolor duis diam est eos tation quis nonumy sed duo quis zzril et aliquyam in enim" - } - }); - }, 1000); - } -}; diff --git a/src/components/Highlights/SpanInfo/index.tsx b/src/components/Highlights/SpanInfo/index.tsx deleted file mode 100644 index d09a15a43..000000000 --- a/src/components/Highlights/SpanInfo/index.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { RefObject, useEffect, useRef, useState } from "react"; -import { CSSTransition } from "react-transition-group"; -import { getAssetTypeInfo } from "../../Assets/utils"; -import { ArrowsInsideIcon } from "../../common/icons/12px/ArrowsInsideIcon"; -import { ArrowsOutsideIcon } from "../../common/icons/12px/ArrowsOutsideIcon"; -import { GlobeIcon } from "../../common/icons/16px/GlobeIcon"; -import { RefreshIcon } from "../../common/icons/16px/RefreshIcon"; -import { WrenchIcon } from "../../common/icons/16px/WrenchIcon"; -import { Tag } from "../../common/v3/Tag"; -import { Tooltip } from "../../common/v3/Tooltip"; -import { EmptyStateCard } from "../EmptyStateCard"; -import * as s from "./styles"; -import { useSpanInfoData } from "./useSpanInfoData"; - -const EXPAND_BTN_TRANSITION_CLASS_NAME = "expand-btn-animations"; -const STATS_TRANSITION_CLASS_NAME = "stats-animation"; -const DEFAULT_TRANSITION_DURATION = 500; // in milliseconds - -const getLanguage = (assetTypeId: string) => { - if (assetTypeId === "DatabaseQueries") { - return "sql"; - } - - if (assetTypeId === "Endpoint" || assetTypeId === "EndpointClient") { - return "http"; - } - - return undefined; -}; - -export const SpanInfo = () => { - const [isExpanded, setIsExpanded] = useState(false); - const { data, getData } = useSpanInfoData(); - const expandedBtnRef = useRef(null); - const collapseBtnRef = useRef(null); - const statsRef = useRef(null); - - const handleExpandButtonClick = () => { - setIsExpanded(!isExpanded); - }; - - useEffect(() => { - getData(); - }, []); - - if (!data) { - return ; - } - - const assetTypeInfo = getAssetTypeInfo(data.assetTypeId); - - const ExpandButtonIcon = isExpanded ? ArrowsInsideIcon : ArrowsOutsideIcon; - - const servicesTooltipTitle = data.services.join(", "); - const environmentsTooltipTitle = data.environments - .map((x) => x.name) - .join(", "); - - const renderAnimatedButton = ( - isVisible: boolean, - name: string, - ref: RefObject - ) => { - return ( - - - - - - {name} - - - ); - }; - - return ( - - - - {assetTypeInfo?.icon && ( - - - - } - /> - )} - Scope - - - {renderAnimatedButton(!isExpanded, "Expand", expandedBtnRef)} - {renderAnimatedButton(isExpanded, "Collapse", collapseBtnRef)} - - - - - - - - Services - - - - - - - - {data.services[0]} - {data.services.length > 1 && ( - +{data.services.length - 1} - )} - - - - - - Environments - - - - - - - - - - {data.environments[0].name} - - {data.environments.length > 1 && ( - - +{data.environments.length - 1} - - )} - - - - - - - - - ); -}; diff --git a/src/components/Highlights/SpanInfo/styles.ts b/src/components/Highlights/SpanInfo/styles.ts deleted file mode 100644 index e5bd89630..000000000 --- a/src/components/Highlights/SpanInfo/styles.ts +++ /dev/null @@ -1,200 +0,0 @@ -import styled, { css } from "styled-components"; -import { - bodySemiboldTypography, - caption1RegularTypography, - subscriptMediumTypography, - subscriptRegularTypography -} from "../../common/App/typographies"; -import { CodeSnippet } from "../../common/CodeSnippet"; -import { - AnimatedButtonContainerProps, - ExpandButtonProps, - StatsContainerProps, - StyledCodeSnippetProps -} from "./types"; - -const COLLAPSE_BTN = "71px"; -const EXPAND_BTN = "65px"; - -export const Container = styled.div` - background: ${({ theme }) => theme.colors.v3.surface.secondary}; - border-radius: 4px; - display: flex; - flex-direction: column; - overflow: hidden; -`; - -export const Section = styled.div` - padding: 8px; -`; - -export const Header = styled(Section)` - display: flex; - justify-content: space-between; - border-bottom: 1px solid ${({ theme }) => theme.colors.v3.surface.primary}; -`; - -export const TitleContainer = styled.div` - ${bodySemiboldTypography} - - display: flex; - align-items: center; - gap: 8px; - color: ${({ theme }) => theme.colors.v3.text.primary}; -`; - -export const IconContainer = styled.div` - display: flex; -`; - -export const AnimatedButtonContainer = styled.div` - display: flex; - position: relative; - width: ${({ $isExpanded }) => ($isExpanded ? COLLAPSE_BTN : EXPAND_BTN)}; - transition: all 500ms; - align-items: center; -`; - -export const ExpandButton = styled.button` - ${subscriptMediumTypography} - - display: flex; - gap: 4px; - align-items: center; - color: ${({ theme }) => theme.colors.v3.text.link}; - background: none; - border: none; - padding: 0; - cursor: pointer; - position: absolute; - - ${({ $transitionClassName, $transitionDuration }) => { - return ` - &.${$transitionClassName}-enter { - opacity: 0; - } - &.${$transitionClassName}-enter-active { - opacity: 1; - transition: all ${$transitionDuration}ms; - } - &.${$transitionClassName}-exit { - opacity: 1; - } - &.${$transitionClassName}-exit-active { - opacity: 0; - transition: all ${$transitionDuration}ms; - }`; - }} -`; - -export const ExpandButtonIconButtonContainer = styled.div` - display: flex; - color: ${({ theme }) => theme.colors.v3.icon.brandTertiary}; - padding: 2px; -`; - -export const ContentContainer = styled(Section)` - display: flex; - flex-direction: column; - gap: 8px; - padding: 8px 8px 12px; -`; - -export const StatsContainer = styled.div` - display: flex; - gap: 5px; - flex-grow: 1; - height: 37px; - - ${({ $transitionClassName, $transitionDuration }) => { - return ` - &.${$transitionClassName}-enter { - opacity: 0; - height: 0; - } - - &.${$transitionClassName}-enter-active { - opacity: 1; - height: 37px; - transition: all ${$transitionDuration}ms; - } - &.${$transitionClassName}-exit { - opacity: 1; - height: 37px; - } - &.${$transitionClassName}-exit-active { - opacity: 0; - height: 0; - transition: all ${$transitionDuration}ms; - } - &.${$transitionClassName}-exit-done { - opacity: 0; - height: 0; - } - `; - }} -`; - -export const Stat = styled.div` - display: flex; - flex-direction: column; - gap: 4px; - color: ${({ theme }) => theme.colors.v3.text.tertiary}; - flex-grow: 1; -`; - -export const ServicesStat = styled(Stat)` - width: 60%; -`; - -export const EnvironmentsStat = styled(Stat)` - width: 40%; -`; - -export const StatIconContainer = styled.div` - display: flex; - color: ${({ theme }) => theme.colors.v3.icon.tertiary}; -`; - -export const StatLabel = styled.div` - ${caption1RegularTypography} -`; - -export const StatValue = styled.div` - ${subscriptRegularTypography} - - display: flex; - align-items: center; -`; - -export const StatValueContainer = styled.div` - display: flex; - gap: 4px; -`; - -export const StatValueText = styled.span` - color: ${({ theme }) => theme.colors.v3.text.primary}; -`; - -export const StyledCodeSnippet = styled(CodeSnippet)` - ${({ $isExpanded }) => - !$isExpanded - ? css` - & > div > code { - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - padding: 0; - } - ` - : css` - & > div > code { - padding: 0; - } - `} - - transition: height 500ms; - max-height: 140px; - overflow: auto; -`; diff --git a/src/components/Highlights/SpanInfo/types.ts b/src/components/Highlights/SpanInfo/types.ts deleted file mode 100644 index 0f3716a43..000000000 --- a/src/components/Highlights/SpanInfo/types.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Environment } from "../../common/App/types"; - -export interface GetHighlightsSpanInfoDataPayload { - query: { - spanCodeObjectId: string | null; - }; -} - -export interface SpanInfoData { - displayName: string; - services: string[]; - environments: Environment[]; - assetTypeId: string; -} - -export interface ExpandButtonProps { - $transitionClassName: string; - $transitionDuration: number; -} - -export interface AnimatedButtonContainerProps { - $isExpanded: boolean; -} - -export interface StyledCodeSnippetProps { - $isExpanded: boolean; -} - -export interface StatsContainerProps { - $transitionClassName: string; - $transitionDuration: number; -} diff --git a/src/components/Highlights/index.tsx b/src/components/Highlights/index.tsx index d72c6de72..84a814a67 100644 --- a/src/components/Highlights/index.tsx +++ b/src/components/Highlights/index.tsx @@ -4,17 +4,12 @@ import { FeatureFlag } from "../../types"; import { Impact } from "./Impact"; import { Performance } from "./Performance"; import { Scaling } from "./Scaling"; -import { SpanInfo } from "./SpanInfo"; import { TopIssues } from "./TopIssues"; import * as s from "./styles"; export const Highlights = () => { const { backendInfo } = useConfigSelector(); - const isSpanInfoVisible = getFeatureFlagValue( - backendInfo, - FeatureFlag.IS_HIGHLIGHTS_SPAN_INFO_ENABLED - ); const areImpactHighlightsVisible = getFeatureFlagValue( backendInfo, FeatureFlag.IS_HIGHLIGHTS_IMPACT_ENABLED @@ -26,7 +21,6 @@ export const Highlights = () => { return ( - {isSpanInfoVisible && } {areImpactHighlightsVisible && } diff --git a/src/components/Insights/InsightsCatalog/PromotionCard/index.tsx b/src/components/Insights/InsightsCatalog/PromotionCard/index.tsx index 3b8f584d8..dcedab471 100644 --- a/src/components/Insights/InsightsCatalog/PromotionCard/index.tsx +++ b/src/components/Insights/InsightsCatalog/PromotionCard/index.tsx @@ -43,7 +43,7 @@ export const PromotionCard = ({ onAccept, onDiscard }: PromotionCardProps) => { - Get our FREE Udemy course + Get our Udemy course FREE See more diff --git a/src/components/Navigation/ScopeBar/index.tsx b/src/components/Navigation/ScopeBar/index.tsx index b4b727573..b5e275b1c 100644 --- a/src/components/Navigation/ScopeBar/index.tsx +++ b/src/components/Navigation/ScopeBar/index.tsx @@ -5,7 +5,10 @@ import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActi import { CodeDetails, Scope } from "../../common/App/types"; import { NewPopover } from "../../common/NewPopover"; 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 { actions } from "../actions"; import { Popup } from "../common/Popup"; @@ -15,6 +18,8 @@ import { TargetButtonMenu } from "./TargetButtonMenu"; import * as s from "./styles"; import { ScopeBarProps } from "./types"; +const EXPANDED_SCOPE_DISPLAY_NAME_PLACEHOLDER = "Full scope text below"; + const isAlreadyAtCode = (codeContext?: CodeContext, scope?: Scope): boolean => { if (!codeContext || !scope?.span) { return false; @@ -49,7 +54,13 @@ const getTargetButtonTooltip = ( } }; -export const ScopeBar = ({ scope, codeContext }: ScopeBarProps) => { +export const ScopeBar = ({ + scope, + codeContext, + isExpanded, + onExpandCollapseChange, + isSpanInfoEnabled +}: ScopeBarProps) => { const [isTargetButtonMenuOpen, setIsTargetButtonMenuOpen] = useState(false); const location = history.getCurrentLocation(); @@ -62,6 +73,7 @@ export const ScopeBar = ({ scope, codeContext }: ScopeBarProps) => { spanCodeObjectId === location?.state?.spanCodeObjectId ? location?.state?.spanDisplayName : ""; + const targetButtonTooltip = getTargetButtonTooltip(codeContext, scope); const isTargetButtonTooltipOpen = @@ -95,6 +107,10 @@ export const ScopeBar = ({ scope, codeContext }: ScopeBarProps) => { setIsTargetButtonMenuOpen(false); }; + const handleExpandCollapseButtonClick = () => { + onExpandCollapseChange(!isExpanded); + }; + const handleTargetButtonClick = () => { sendUserActionTrackingEvent(trackingEvents.TARGET_BUTTON_CLICKED); if (scope && scope.code.codeDetailsList.length === 1) { @@ -104,12 +120,12 @@ export const ScopeBar = ({ scope, codeContext }: ScopeBarProps) => { const renderTargetButton = () => ( - - - + buttonType={"secondaryBorderless"} + /> ); @@ -119,15 +135,30 @@ export const ScopeBar = ({ scope, codeContext }: ScopeBarProps) => { - {scopeDisplayName && ( + {isSpanInfoEnabled && isExpanded ? ( + + + {EXPANDED_SCOPE_DISPLAY_NAME_PLACEHOLDER} + + + ) : scopeDisplayName ? ( <> {scopeDisplayName} - )} + ) : null} + {isSpanInfoEnabled && ( + + + + )} {isTargetButtonMenuEnabled ? ( theme.colors.v3.text.tertiary}; + text-transform: uppercase; +`; + export const ScopeBarButton = styled.button` display: flex; align-items: center; diff --git a/src/components/Navigation/ScopeBar/types.ts b/src/components/Navigation/ScopeBar/types.ts index f77ef757c..a7cb39a18 100644 --- a/src/components/Navigation/ScopeBar/types.ts +++ b/src/components/Navigation/ScopeBar/types.ts @@ -4,4 +4,7 @@ import { CodeContext } from "../types"; export interface ScopeBarProps { codeContext?: CodeContext; scope: Scope | null; + isExpanded: boolean; + onExpandCollapseChange: (isExpanded: boolean) => void; + isSpanInfoEnabled: boolean; } diff --git a/src/components/Navigation/SpanInfo/SpanInfo.stories.tsx b/src/components/Navigation/SpanInfo/SpanInfo.stories.tsx new file mode 100644 index 000000000..9fd92d062 --- /dev/null +++ b/src/components/Navigation/SpanInfo/SpanInfo.stories.tsx @@ -0,0 +1,36 @@ +import { Meta, StoryObj } from "@storybook/react"; + +import { SpanInfo } from "."; +import { mockedSpanInfoData } from "./mockData"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction + +const meta: Meta = { + title: "Navigation/SpanInfo", + component: SpanInfo, + 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: mockedSpanInfoData + } +}; + +export const LargeText: Story = { + args: { + data: { + ...mockedSpanInfoData, + displayName: + "Erat iriure lorem placerat dolor ex gubergren ipsum ea option justo vero duo gubergren dolores consetetur luptatum labore nulla aliquam ipsum placerat ipsum no est ea eos et dolore esse ea possim consetetur lorem laoreet elitr ipsum eros magna ut vero consectetuer et diam facilisis et et amet diam accusam soluta amet est dolor ea erat takimata exerci invidunt invidunt sed et mazim augue ut ipsum stet ut ut consetetur accusam at sea et stet dolores laoreet autem dolore amet et lobortis dolor sea dolore rebum labore possim ut lorem illum rebum in consetetur erat lorem sadipscing accusam sanctus erat no aliquam magna dolor dolore feugiat in dolor sed augue lorem consetetur euismod odio nibh ut ipsum ut magna lorem aliquyam elitr sit ea gubergren voluptua ea ea vero duis sea dolore hendrerit lorem vero esse vero nonumy tempor eos erat et autem est est in eirmod justo consequat nulla erat sea nostrud ut lorem sit odio eleifend in est voluptua at sed sed possim et aliquam erat ipsum sed sed aliquyam sed diam dolores ipsum at quis in diam tempor autem eleifend labore consequat labore facilisi elitr est et dolores diam ea eirmod eum no lorem eos ea eirmod et invidunt iusto elitr adipiscing elit dolores et ut dolor sed sea sanctus diam duo sed stet sed tincidunt accusam consetetur sed dolore mazim vero dolor autem lorem nulla sea sadipscing nonumy stet odio duo consequat diam sea ipsum tempor et feugait vero ipsum et no accusam sed et qui eum diam ipsum quod nostrud sadipscing lorem rebum duo elit sit dolor invidunt diam feugait dolor amet ut et ea dolor est vero in vero ut lorem sed diam ea aliquyam aliquip labore amet amet et sed eirmod consetetur euismod sit clita eos elitr ut et eos et consetetur rebum rebum ut sea ut praesent hendrerit ipsum erat et sadipscing eirmod euismod tempor ipsum duis magna clita sed eleifend diam facilisis diam elitr stet ea nonumy magna commodo ipsum lorem duis quod ut sit sit amet consequat et imperdiet ea stet clita duo amet ipsum voluptua vero nonumy ut lorem nibh ea nulla elit illum vero dolor at sea feugiat doming justo diam kasd wisi rebum est elitr dolor aliquip duo et nibh dolor ea blandit eos elitr diam justo lorem voluptua vero ea eum adipiscing sed invidunt clita diam ut illum accusam duo sea nostrud ut magna soluta eos et dolore invidunt eos tempor sadipscing lorem dolore dolor sit sit amet sit invidunt aliquyam vel autem doming aliquip labore vel justo aliquyam at ipsum labore sit duis diam ipsum dolore dignissim exerci lorem dolor wisi diam sit stet tempor sanctus sit blandit gubergren voluptua amet dolore consequat tempor takimata dolores sea ea blandit ullamcorper dolor sit sed esse ipsum ipsum laoreet elitr commodo ipsum lorem erat ipsum dolores sed amet quod et dolor facilisi esse sit justo amet justo dolor nostrud dolore ut dignissim at odio nonumy kasd sit diam nonumy eos kasd sanctus dignissim est eirmod sadipscing te est labore duo nonumy kasd hendrerit et vel diam duo amet blandit erat illum amet nisl dolor exerci diam clita at justo et sit et illum sit lorem sit et illum sit te ipsum amet sit placerat velit sed sed et vero stet ipsum sit consetetur et nihil te dolores qui enim kasd dolore dolores dolore justo ipsum dolor diam magna blandit invidunt sanctus ipsum lorem vero sea invidunt erat et sit duo lorem feugiat sanctus sea invidunt et exerci duo sed magna erat dolore et tempor quis et ut eos eirmod labore dolor luptatum duo tempor sed sit ipsum ipsum hendrerit clita sadipscing dolor invidunt dolor kasd iusto iriure lorem consequat invidunt laoreet diam sed in sed lorem lorem dolores erat ut blandit invidunt diam amet accusam dolor consetetur ut accusam sed justo elitr ut lorem nonumy imperdiet velit vero consetetur elitr sadipscing augue minim sit facilisis justo et amet aliquyam tempor sit aliquyam vero sanctus dolor sanctus vero ex at gubergren feugiat id illum consetetur amet dolores consectetuer stet amet nibh duo voluptua invidunt lorem sed est assum diam vero dolores duo eirmod aliquyam labore amet nonumy labore vel te dolore et dolore ipsum labore feugiat gubergren sed voluptua dignissim gubergren justo possim rebum invidunt justo accusam accumsan et rebum velit dolore ipsum euismod nulla tation amet diam adipiscing lorem kasd lorem ea ex labore tation ut diam sed facer nulla diam gubergren at rebum et erat duo justo rebum vero et ipsum et est aliquyam sea nonumy facilisi elitr velit sed sanctus vero sadipscing kasd tempor invidunt magna duis labore sadipscing duo ipsum diam augue et eos aliquyam facilisis eum sea tempor invidunt no diam labore amet congue diam autem dolor eirmod ipsum lorem minim autem gubergren dolore ipsum erat sadipscing nisl sed veniam dolore et amet congue nostrud kasd ut elitr dolore iriure voluptua diam facilisis lorem kasd diam sea sanctus sadipscing lorem accusam tincidunt aliquam consetetur feugait magna gubergren laoreet voluptua nihil ipsum velit nulla sanctus dolor kasd ut zzril nonumy facilisis quis ut vero eirmod sed in at amet lorem voluptua accusam ut at elitr tempor sanctus wisi tincidunt nonumy euismod dolor sed ea cum takimata liber gubergren clita dolor lorem elitr veniam gubergren velit ipsum ipsum sit erat et et nostrud et hendrerit sit in et voluptua takimata stet diam ea diam et erat at justo sea dolor rebum at luptatum accusam sadipscing augue labore nonumy et sea eirmod eu magna labore sea takimata et ut ipsum ea diam et euismod molestie takimata facilisis nonumy vel invidunt eirmod kasd ut in nihil dolores at invidunt in et stet et illum dolor illum erat ipsum praesent magna accusam justo voluptua hendrerit et tation voluptua esse vel amet eu dolore dolore accusam elitr molestie amet lorem sit voluptua sit et est zzril rebum eos amet ea magna consetetur tempor lorem sed tincidunt clita esse duis possim ut ea erat dolor duis diam est eos tation quis nonumy sed duo quis zzril et aliquyam in enim" + } + } +}; diff --git a/src/components/Navigation/SpanInfo/index.tsx b/src/components/Navigation/SpanInfo/index.tsx new file mode 100644 index 000000000..2b079cc04 --- /dev/null +++ b/src/components/Navigation/SpanInfo/index.tsx @@ -0,0 +1,69 @@ +import { GlobeIcon } from "../../common/icons/16px/GlobeIcon"; +import { WrenchIcon } from "../../common/icons/16px/WrenchIcon"; +import { Tooltip } from "../../common/v3/Tooltip"; +import * as s from "./styles"; +import { SpanInfoProps } from "./types"; + +const getLanguage = (assetTypeId: string) => { + if (assetTypeId === "DatabaseQueries") { + return "sql"; + } + + if (assetTypeId === "Endpoint" || assetTypeId === "EndpointClient") { + return "http"; + } + + return undefined; +}; + +export const SpanInfo = ({ onCollapse, data }: SpanInfoProps) => { + const handleCollapseButtonClick = () => { + onCollapse(); + }; + + const stats = [ + { + id: "services", + icon: WrenchIcon, + title: data.services.join(", "), + value: data.services[0], + count: data.services.length + }, + { + id: "environments", + icon: GlobeIcon, + title: data.environments.map((x) => x.name).join(", "), + value: data.environments[0].name, + count: data.environments.length + } + ]; + + return ( + + + + + {stats.map((x) => ( + + + + + + + {x.value} + {x.count > 1 && +{x.count - 1}} + + + + ))} + + + Collapse + + + + ); +}; diff --git a/src/components/Highlights/SpanInfo/mockData.ts b/src/components/Navigation/SpanInfo/mockData.ts similarity index 94% rename from src/components/Highlights/SpanInfo/mockData.ts rename to src/components/Navigation/SpanInfo/mockData.ts index 2e2f9cb2f..a34ade2e2 100644 --- a/src/components/Highlights/SpanInfo/mockData.ts +++ b/src/components/Navigation/SpanInfo/mockData.ts @@ -1,4 +1,4 @@ -import { SpanInfoData } from "./types"; +import { SpanInfoData } from "../useSpanInfoData"; export const mockedSpanInfoData: SpanInfoData = { displayName: diff --git a/src/components/Navigation/SpanInfo/styles.ts b/src/components/Navigation/SpanInfo/styles.ts new file mode 100644 index 000000000..80a5b7538 --- /dev/null +++ b/src/components/Navigation/SpanInfo/styles.ts @@ -0,0 +1,64 @@ +import styled from "styled-components"; +import { subscriptRegularTypography } from "../../common/App/typographies"; +import { CodeSnippet } from "../../common/CodeSnippet"; + +export const Container = styled.div` + background: ${({ theme }) => theme.colors.v3.surface.brandDark}; + border-radius: 4px; + display: flex; + flex-direction: column; + overflow: hidden; + gap: 8px; + padding: 8px 8px 12px; +`; + +export const StyledCodeSnippet = styled(CodeSnippet)` + max-height: 140px; + overflow: auto; +`; + +export const Footer = styled.div` + display: flex; + align-items: center; +`; + +export const StatsContainer = styled.div` + display: flex; + gap: 5px; + flex-grow: 1; + + & > * { + flex: 1 1 0; + } +`; + +export const Stat = styled.div` + ${subscriptRegularTypography} + + display: flex; + align-items: center; + gap: 4px; +`; + +export const StatIconContainer = styled.div` + display: flex; + color: ${({ theme }) => theme.colors.v3.icon.tertiary}; +`; + +export const StatValueContainer = styled.div` + display: flex; + gap: 4px; + color: ${({ theme }) => theme.colors.v3.text.tertiary}; +`; + +export const StatValueText = styled.span` + color: ${({ theme }) => theme.colors.v3.text.primary}; +`; + +export const CollapseButton = styled.div` + ${subscriptRegularTypography} + + font-family: inherit; + cursor: pointer; + color: ${({ theme }) => theme.colors.v3.text.link}; +`; diff --git a/src/components/Navigation/SpanInfo/types.ts b/src/components/Navigation/SpanInfo/types.ts new file mode 100644 index 000000000..979e9d4e3 --- /dev/null +++ b/src/components/Navigation/SpanInfo/types.ts @@ -0,0 +1,6 @@ +import { SpanInfoData } from "../useSpanInfoData"; + +export interface SpanInfoProps { + onCollapse: () => void; + data: SpanInfoData; +} diff --git a/src/components/Navigation/index.tsx b/src/components/Navigation/index.tsx index 0e7516f88..17b636ecf 100644 --- a/src/components/Navigation/index.tsx +++ b/src/components/Navigation/index.tsx @@ -4,7 +4,7 @@ import { dispatcher } from "../../dispatcher"; import { usePrevious } from "../../hooks/usePrevious"; // import { isNull } from "../../typeGuards/isNull"; // import { isUndefined } from "../../typeGuards/isUndefined"; -import { GetInsightStatsPayload } from "../../types"; +import { FeatureFlag, GetInsightStatsPayload } from "../../types"; import { changeScope } from "../../utils/actions/changeScope"; import { sendUserActionTrackingEvent } from "../../utils/actions/sendUserActionTrackingEvent"; // import { AsyncActionResultData } from "../InstallationWizard/types"; @@ -15,11 +15,14 @@ import { ThreeDotsIcon } from "../common/icons/ThreeDotsIcon"; // import { Tooltip } from "../common/v3/Tooltip"; // import { CodeButton } from "./CodeButton"; // import { CodeButtonMenu } from "./CodeButtonMenu"; +import useDimensions from "react-cool-dimensions"; +import { getFeatureFlagValue } from "../../featureFlags"; import { useConfigSelector } from "../../store/config/useConfigSelector"; import { EnvironmentBar } from "./EnvironmentBar"; import { HistoryNavigationPanel } from "./HistoryNavigationPanel"; import { KebabMenu } from "./KebabMenu"; import { ScopeBar } from "./ScopeBar"; +import { SpanInfo } from "./SpanInfo"; import { Tabs } from "./Tabs"; import { actions } from "./actions"; import { IconButton } from "./common/IconButton"; @@ -30,6 +33,18 @@ import { // AutoFixMissingDependencyPayload, CodeContext } from "./types"; +import { useSpanInfoData } from "./useSpanInfoData"; + +const isDisplayNameTooLong = (displayName: string, containerWidth: number) => { + const CHAR_WIDTH = 8; // in pixels + const MAX_DISPLAY_NAME_OVERFLOW_RATIO = 1.5; + const CONTAINER_PADDING = 64; // in pixels + return ( + (displayName.length * CHAR_WIDTH) / + Math.max(containerWidth - CONTAINER_PADDING, 1) > + MAX_DISPLAY_NAME_OVERFLOW_RATIO + ); +}; // const hasData = (codeContext?: CodeContext): boolean => // Boolean( @@ -87,13 +102,31 @@ export const Navigation = () => { // const [isAutoFixing, setIsAutoFixing] = useState(false); // const [isAnnotationAdding, setIsAnnotationAdding] = useState(false); // const previousCodeContext = usePrevious(codeContext); - const previousEnv = usePrevious(environment); + const previousEnvironment = usePrevious(environment); + const [isSpanInfoVisible, setIsSpanInfoVisible] = useState(false); + const { data: spanInfo, getData: getSpanInfo } = useSpanInfoData(); + const previousSpanInfo = usePrevious(spanInfo); + + const isAtSpan = Boolean(scope?.span); + + const { observe, width: containerWidth } = useDimensions(); // const codeButtonTooltip = getCodeButtonTooltip(codeContext, config.scope); // const isCodeButtonEnabled = codeContext && !isNull(codeContext.methodId); // const isCodeButtonMenuEnabled = // codeContext && codeContext.spans.assets.length !== 1; + const isSpanInfoEnabled = Boolean( + getFeatureFlagValue( + backendInfo, + FeatureFlag.IS_HIGHLIGHTS_SPAN_INFO_ENABLED + ) + ); + + useEffect(() => { + getSpanInfo(); + }, []); + useEffect(() => { const handleCodeContextData = (data: unknown) => { const payload = data as CodeContext; @@ -136,7 +169,7 @@ export const Navigation = () => { }, [userInfo?.id]); useEffect(() => { - if (environment?.id !== previousEnv?.id) { + if (environment?.id !== previousEnvironment?.id) { setSelectedEnvironment(environment); window.sendMessageToDigma({ action: globalActions.GET_INSIGHT_STATS, @@ -151,7 +184,7 @@ export const Navigation = () => { } }); } - }, [environment, scope, previousEnv?.id]); + }, [environment, scope, previousEnvironment?.id]); const handleEnvironmentChange = useCallback( (environment: Environment) => { @@ -190,6 +223,19 @@ export const Navigation = () => { } }, [environments, environment, scope]); + useEffect(() => { + if ( + previousSpanInfo?.displayName !== spanInfo?.displayName && + spanInfo?.displayName + ) { + const isSpanInfoVisible = isDisplayNameTooLong( + spanInfo.displayName, + containerWidth + ); + setIsSpanInfoVisible(isSpanInfoVisible); + } + }, [spanInfo, previousSpanInfo, containerWidth]); + // useEffect(() => { // setIsAutoFixing(false); // setIsAnnotationAdding(false); @@ -214,6 +260,14 @@ export const Navigation = () => { // } // }, [codeContext, previousCodeContext]); + const handleScopeDisplayNameExpandCollapseChange = (isExpanded: boolean) => { + setIsSpanInfoVisible(isExpanded); + }; + + const handleSpanInfoCollapse = () => { + setIsSpanInfoVisible(false); + }; + const handleKebabMenuOpenChange = (isOpen: boolean) => { if (isOpen) { sendUserActionTrackingEvent(trackingEvents.KEBAB_MENU_BUTTON_CLICKED); @@ -309,14 +363,18 @@ export const Navigation = () => { return ; } - const isAtSpan = Boolean(scope?.span); - return ( - + {isAtSpan ? ( - + ) : ( { /> - - {/* + )} + {/* + { onMouseLeave={handleCodeButtonMouseLeave} /> )} - */} - + + */} diff --git a/src/components/Navigation/styles.ts b/src/components/Navigation/styles.ts index bbcc00eb4..00b3a2036 100644 --- a/src/components/Navigation/styles.ts +++ b/src/components/Navigation/styles.ts @@ -9,7 +9,7 @@ export const Container = styled.div` : theme.colors.v3.surface.secondary}; display: flex; flex-direction: column; - gap: 4px; + gap: 8px; padding: 8px 8px 0; border-radius: 0 0 12px 12px; border-bottom: 1px solid ${({ theme }) => theme.colors.v3.stroke.primary}; diff --git a/src/components/Highlights/SpanInfo/useSpanInfoData.ts b/src/components/Navigation/useSpanInfoData.ts similarity index 68% rename from src/components/Highlights/SpanInfo/useSpanInfoData.ts rename to src/components/Navigation/useSpanInfoData.ts index 8153884f6..8aaad7507 100644 --- a/src/components/Highlights/SpanInfo/useSpanInfoData.ts +++ b/src/components/Navigation/useSpanInfoData.ts @@ -1,33 +1,53 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import { dispatcher } from "../../../dispatcher"; -import { usePrevious } from "../../../hooks/usePrevious"; -import { useConfigSelector } from "../../../store/config/useConfigSelector"; -import { actions as mainActions } from "../../Main/actions"; -import { GetHighlightsSpanInfoDataPayload, SpanInfoData } from "./types"; +import { dispatcher } from "../../dispatcher"; +import { usePrevious } from "../../hooks/usePrevious"; +import { useConfigSelector } from "../../store/config/useConfigSelector"; +import { Environment } from "../common/App/types"; +import { actions as mainActions } from "../Main/actions"; const REFRESH_INTERVAL = 10 * 1000; // in milliseconds +export interface GetHighlightsSpanInfoDataPayload { + query: { + spanCodeObjectId: string | null; + }; +} + +export interface SpanInfoData { + displayName: string; + services: string[]; + environments: Environment[]; + assetTypeId: string; +} + export const useSpanInfoData = () => { const [data, setData] = useState(); const { scope } = useConfigSelector(); const [lastSetDataTimeStamp, setLastSetDataTimeStamp] = useState(); const previousLastSetDataTimeStamp = usePrevious(lastSetDataTimeStamp); const refreshTimerId = useRef(); + const scopeSpanCodeObjectId = scope?.span?.spanCodeObjectId; const getData = useCallback(() => { - if (scope?.span?.spanCodeObjectId) { + if (scopeSpanCodeObjectId) { window.sendMessageToDigma({ action: mainActions.GET_HIGHLIGHTS_SPAN_INFO_DATA, payload: { query: { - spanCodeObjectId: scope?.span?.spanCodeObjectId + spanCodeObjectId: scopeSpanCodeObjectId } } }); } - }, [scope?.span?.spanCodeObjectId]); + }, [scopeSpanCodeObjectId]); const previousGetData = usePrevious(getData); + useEffect(() => { + if (!scopeSpanCodeObjectId) { + setData(undefined); + } + }, [scopeSpanCodeObjectId]); + useEffect(() => { if (previousGetData && previousGetData !== getData) { window.clearTimeout(refreshTimerId.current); diff --git a/src/components/common/icons/16px/MaximizeIcon.tsx b/src/components/common/icons/16px/MaximizeIcon.tsx new file mode 100644 index 000000000..ef5fb63ae --- /dev/null +++ b/src/components/common/icons/16px/MaximizeIcon.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const MaximizeIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const MaximizeIcon = React.memo(MaximizeIconComponent); diff --git a/src/components/common/icons/16px/MinimizeIcon.tsx b/src/components/common/icons/16px/MinimizeIcon.tsx new file mode 100644 index 000000000..04c4ae76a --- /dev/null +++ b/src/components/common/icons/16px/MinimizeIcon.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const MinimizeIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + + + + + + ); +}; + +export const MinimizeIcon = React.memo(MinimizeIconComponent);