From 1ce31ce8d21c00b717d9cd8d8d9f0a7227673bf0 Mon Sep 17 00:00:00 2001 From: asafchen-dig Date: Tue, 20 Feb 2024 13:30:19 +0200 Subject: [PATCH 1/9] Scaling issue insight ticket --- .../ScalingIssueInsight.stories.tsx | 87 +-------------- .../Insights/ScalingIssueInsight/mockData.ts | 96 ++++++++++++++++ src/components/Insights/index.tsx | 17 ++- .../SpanScalingInsightTicket.stories.tsx | 27 +++++ .../ScalingIssueInsightTicket/index.tsx | 104 ++++++++++++++++++ .../ScalingIssueAffectedEndpoints/index.tsx | 31 ++++++ .../ScalingIssueAffectedEndpoints/styles.ts | 11 ++ .../ScalingIssueAffectedEndpoints/types.ts | 5 + .../common/ScalingIssueRootCauses/index.tsx | 25 +++++ .../common/ScalingIssueRootCauses/styles.ts | 11 ++ .../common/ScalingIssueRootCauses/types.ts | 5 + 11 files changed, 334 insertions(+), 85 deletions(-) create mode 100644 src/components/Insights/ScalingIssueInsight/mockData.ts create mode 100644 src/components/Insights/tickets/ScalingIssueInsightTicket/SpanScalingInsightTicket.stories.tsx create mode 100644 src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx create mode 100644 src/components/Insights/tickets/common/ScalingIssueAffectedEndpoints/index.tsx create mode 100644 src/components/Insights/tickets/common/ScalingIssueAffectedEndpoints/styles.ts create mode 100644 src/components/Insights/tickets/common/ScalingIssueAffectedEndpoints/types.ts create mode 100644 src/components/Insights/tickets/common/ScalingIssueRootCauses/index.tsx create mode 100644 src/components/Insights/tickets/common/ScalingIssueRootCauses/styles.ts create mode 100644 src/components/Insights/tickets/common/ScalingIssueRootCauses/types.ts diff --git a/src/components/Insights/ScalingIssueInsight/ScalingIssueInsight.stories.tsx b/src/components/Insights/ScalingIssueInsight/ScalingIssueInsight.stories.tsx index 0bca4619f..5c9125ba7 100644 --- a/src/components/Insights/ScalingIssueInsight/ScalingIssueInsight.stories.tsx +++ b/src/components/Insights/ScalingIssueInsight/ScalingIssueInsight.stories.tsx @@ -1,7 +1,6 @@ import { Meta, StoryObj } from "@storybook/react"; import { ScalingIssueInsight } from "."; -import { InsightType } from "../../../types"; -import { InsightCategory, InsightScope } from "../types"; +import { mockedSpanScalingInsight } from "./mockData"; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { @@ -17,88 +16,10 @@ export default meta; type Story = StoryObj; + + export const Default: Story = { args: { - insight: { - firstDetected: "2023-12-05T17:25:47.010Z", - lastDetected: "2024-01-05T13:14:47.010Z", - criticality: 0, - firstCommitId: "b3f7b3f", - lastCommitId: "a1b2c3d", - deactivatedCommitId: null, - reopenCount: 0, - ticketLink: null, - impact: 0, - name: "Scaling Issue Found", - type: InsightType.SpanScalingBadly, - category: InsightCategory.Performance, - specifity: 4, - importance: 2, - spanName: "WaitForLock", - spanInstrumentationLibrary: "SampleInsightsController", - turningPointConcurrency: 17, - maxConcurrency: 24, - minDuration: { - value: 100.67, - unit: "ms", - raw: 100671312.5 - }, - maxDuration: { - value: 7.22, - unit: "sec", - raw: 7222044625 - }, - rootCauseSpans: [], - affectedEndpoints: [ - { - route: "epHTTP:HTTP GET SampleInsights/lock/{milisec}", - serviceName: "Sample.MoneyTransfer.API", - sampleTraceId: "3E41E4197B696CA9BF1157AEB254DFE0", - flowHash: "2C8EE08C75056058690249E52382F5", - 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: - "Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$Lock(Double)", - kind: "Server", - codeObjectId: - "Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$Lock(Double)" - } - ], - scope: InsightScope.Span, - spanInfo: { - name: "WaitForLock", - displayName: "WaitForLock", - instrumentationLibrary: "SampleInsightsController", - spanCodeObjectId: "span:SampleInsightsController$_$WaitForLock", - methodCodeObjectId: null, - kind: "Internal", - codeObjectId: null - }, - shortDisplayInfo: { - title: "Scaling Issue Found", - targetDisplayName: "", - subtitle: "", - description: - "Significant performance degradation at 17 executions/second" - }, - codeObjectId: "SampleInsightsController$_$WaitForLock", - decorators: [ - { - title: "Scaling badly", - description: - "This code experiences exponential grows in duration after 17 concurrent executions" - } - ], - environment: "BOB-LAPTOP[LOCAL]", - severity: 0, - isRecalculateEnabled: false, - prefixedCodeObjectId: "span:SampleInsightsController$_$WaitForLock", - customStartTime: null, - actualStartTime: "2023-06-24T00:00:00.000Z", - flowHash: null - } + insight: mockedSpanScalingInsight } }; diff --git a/src/components/Insights/ScalingIssueInsight/mockData.ts b/src/components/Insights/ScalingIssueInsight/mockData.ts new file mode 100644 index 000000000..bce02bc30 --- /dev/null +++ b/src/components/Insights/ScalingIssueInsight/mockData.ts @@ -0,0 +1,96 @@ +import { InsightType } from "../../../types"; +import { SpanScalingBadlyInsight, InsightCategory, InsightScope } from "../types"; + +export const mockedSpanScalingInsight: SpanScalingBadlyInsight = { + firstDetected: "2023-12-05T17:25:47.010Z", + lastDetected: "2024-01-05T13:14:47.010Z", + criticality: 0, + firstCommitId: "b3f7b3f", + lastCommitId: "a1b2c3d", + deactivatedCommitId: null, + reopenCount: 0, + ticketLink: null, + impact: 0, + name: "Scaling Issue Found", + type: InsightType.SpanScalingBadly, + category: InsightCategory.Performance, + specifity: 4, + importance: 2, + spanName: "WaitForLock", + spanInstrumentationLibrary: "SampleInsightsController", + turningPointConcurrency: 17, + maxConcurrency: 24, + minDuration: { + value: 100.67, + unit: "ms", + raw: 100671312.5 + }, + maxDuration: { + value: 7.22, + unit: "sec", + raw: 7222044625 + }, + rootCauseSpans: [ + { + instrumentationLibrary: "io.opentelemetry.somedb-10.0", + name: "fc3425f345f4", + displayName: "SELECT * FROM users", + sampleTraceId: "3E41E4197B696CA9BF1157AEB254DFE0", + spanCodeObjectId: "io.opentelemetry.somedb-10.0$_$fc3425f345f4", + kind: "Client", + methodCodeObjectId: null, + flowHash: "2C8EE08C75056058690249E52382F5", + codeObjectId: null + } + ], + affectedEndpoints: [ + { + route: "epHTTP:HTTP GET SampleInsights/lock/{milisec}", + serviceName: "Sample.MoneyTransfer.API", + sampleTraceId: "3E41E4197B696CA9BF1157AEB254DFE0", + flowHash: "2C8EE08C75056058690249E52382F5", + 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: + "Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$Lock(Double)", + kind: "Server", + codeObjectId: + "Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$Lock(Double)" + } + ], + scope: InsightScope.Span, + spanInfo: { + name: "WaitForLock", + displayName: "WaitForLock", + instrumentationLibrary: "SampleInsightsController", + spanCodeObjectId: "span:SampleInsightsController$_$WaitForLock", + methodCodeObjectId: null, + kind: "Internal", + codeObjectId: null + }, + shortDisplayInfo: { + title: "Scaling Issue Found", + targetDisplayName: "", + subtitle: "", + description: + "Significant performance degradation at 17 executions/second" + }, + codeObjectId: "SampleInsightsController$_$WaitForLock", + decorators: [ + { + title: "Scaling badly", + description: + "This code experiences exponential grows in duration after 17 concurrent executions" + } + ], + environment: "BOB-LAPTOP[LOCAL]", + severity: 0, + isRecalculateEnabled: false, + prefixedCodeObjectId: "span:SampleInsightsController$_$WaitForLock", + customStartTime: null, + actualStartTime: "2023-06-24T00:00:00.000Z", + flowHash: null +}; \ No newline at end of file diff --git a/src/components/Insights/index.tsx b/src/components/Insights/index.tsx index 058b3fff8..7161382f9 100644 --- a/src/components/Insights/index.tsx +++ b/src/components/Insights/index.tsx @@ -30,6 +30,7 @@ import { EndpointQueryOptimizationInsightTicket } from "./tickets/EndpointQueryO import { NPlusOneInsightTicket } from "./tickets/NPlusOneInsightTicket"; import { QueryOptimizationInsightTicket } from "./tickets/QueryOptimizationInsightTicket"; import { SpanBottleneckInsightTicket } from "./tickets/SpanBottleneckInsightTicket"; +import { ScalingIssueInsightTicket } from "./tickets/ScalingIssueInsightTicket"; import { isEndpointHighNumberOfQueriesInsight, isEndpointQueryOptimizationInsight, @@ -37,7 +38,8 @@ import { isEndpointSuspectedNPlusOneInsight, isSpanEndpointBottleneckInsight, isSpanNPlusOneInsight, - isSpanQueryOptimizationInsight + isSpanQueryOptimizationInsight, + isSpanScalingBadlyInsight } from "./typeGuards"; import { EndpointHighNumberOfQueriesInsight, @@ -53,7 +55,7 @@ import { QueryOptimizationInsight, SpanEndpointBottleneckInsight, SpanNPlusOneInsight, - ViewMode + ViewMode, SpanScalingBadlyInsight } from "./types"; const REFRESH_INTERVAL = isNumber(window.insightsRefreshInterval) @@ -122,6 +124,17 @@ const renderInsightTicket = ( ); } + if (isSpanScalingBadlyInsight(data.insight)) { + const ticketData = + data as InsightTicketInfo; + return ( + + ); + } + return null; }; diff --git a/src/components/Insights/tickets/ScalingIssueInsightTicket/SpanScalingInsightTicket.stories.tsx b/src/components/Insights/tickets/ScalingIssueInsightTicket/SpanScalingInsightTicket.stories.tsx new file mode 100644 index 000000000..8dbf053a9 --- /dev/null +++ b/src/components/Insights/tickets/ScalingIssueInsightTicket/SpanScalingInsightTicket.stories.tsx @@ -0,0 +1,27 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { ScalingIssueInsightTicket } from "."; +import { mockedSpanScalingInsight } from "../../ScalingIssueInsight/mockData"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Insights/tickets/ScalingIssueInsightTicket", + component: ScalingIssueInsightTicket, + 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: { + data: { + insight: mockedSpanScalingInsight, + spanCodeObjectId: + mockedSpanScalingInsight.spanInfo?.spanCodeObjectId + } + } +}; diff --git a/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx b/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx new file mode 100644 index 000000000..938b2a977 --- /dev/null +++ b/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx @@ -0,0 +1,104 @@ +import { SpanScalingBadlyInsight } from "../../types"; +import { InsightTicketProps } from "../types"; +import { InsightJiraTicket } from "../../InsightJiraTicket"; +import { intersperse } from "../../../../utils/intersperse"; +import { ReactElement, useContext } from "react"; +import { CodeLocations } from "../common/CodeLocations"; +import { CommitInfos } from "../common/CommitInfos"; +import { DigmaSignature } from "../common/DigmaSignature"; +import { useSpanDataSource } from "../common"; +import { getCriticalityLabel } from "../../../../utils/getCriticalityLabel"; +import { ConfigContext } from "../../../common/App/ConfigContext"; +import { ScalingIssueAffectedEndpoints } from "../common/ScalingIssueAffectedEndpoints"; +import { ScalingIssueRootCauses } from "../common/ScalingIssueRootCauses"; +import { getDurationString } from "../../../../utils/getDurationString"; + + +export const ScalingIssueInsightTicket = ( + props: InsightTicketProps +) => { + const config = useContext(ConfigContext); + + const insight = props.data.insight; + + const { commitInfos, isLoading, codeLocations } = + useSpanDataSource( + props.data.insight.spanInfo, + props.data.insight + ); + + const renderDescription = () => { + return ( + <> + {intersperse( + [ +
{insight.shortDisplayInfo.description}
, +
+ Tested concurrency: {insight.maxConcurrency} +
, +
+ Duration range: + + {getDurationString(insight.minDuration)} -{" "} + {getDurationString(insight.maxDuration)} + +
, + , + , + , + , + + ], + (i: number) => ( +
+ ) + )} + + ); + }; + + + const criticalityString = + props.data.insight.criticality > 0 + ? `Criticality: ${getCriticalityLabel(props.data.insight.criticality)}` + : ""; + const summary = ["Scaling Issue", criticalityString] + .filter(Boolean) + .join(" - "); + + const traceId = props.data.insight.affectedEndpoints + ?.map(ep => ep.sampleTraceId) + ?.find(t => t); + const attachment = traceId + ? { + url: `${config.jaegerURL}/api/traces/${traceId}?prettyPrint=true`, + fileName: `trace-${traceId}.json` + } + : undefined; + + return ( + + ); +}; \ No newline at end of file diff --git a/src/components/Insights/tickets/common/ScalingIssueAffectedEndpoints/index.tsx b/src/components/Insights/tickets/common/ScalingIssueAffectedEndpoints/index.tsx new file mode 100644 index 000000000..eeaae8137 --- /dev/null +++ b/src/components/Insights/tickets/common/ScalingIssueAffectedEndpoints/index.tsx @@ -0,0 +1,31 @@ +import { trimEndpointScheme } from "../../../../../utils/trimEndpointScheme"; +import * as s from "./styles"; +import { ScalingIssueEndpointsProps } from "./types"; + +export const ScalingIssueAffectedEndpoints = (props: ScalingIssueEndpointsProps) => { + if (!props.insight) { + return null; + } + + const endpoints = props.insight.affectedEndpoints; + + if (endpoints.length === 0) { + return null; + } + + return ( +
+
Affected endpoints:
+ + {endpoints.map((x) => ( +
  • +
    + {x.serviceName}{" "} + {trimEndpointScheme(x.route)} +
    +
  • + ))} +
    +
    + ); +}; diff --git a/src/components/Insights/tickets/common/ScalingIssueAffectedEndpoints/styles.ts b/src/components/Insights/tickets/common/ScalingIssueAffectedEndpoints/styles.ts new file mode 100644 index 000000000..dcb2f372f --- /dev/null +++ b/src/components/Insights/tickets/common/ScalingIssueAffectedEndpoints/styles.ts @@ -0,0 +1,11 @@ +import styled from "styled-components"; + +export const List = styled.ul` + display: flex; + gap: 1em; + flex-direction: column; + list-style-type: disc; + padding: 0; + margin: 0; + margin-left: 1em; +`; diff --git a/src/components/Insights/tickets/common/ScalingIssueAffectedEndpoints/types.ts b/src/components/Insights/tickets/common/ScalingIssueAffectedEndpoints/types.ts new file mode 100644 index 000000000..39ff877d5 --- /dev/null +++ b/src/components/Insights/tickets/common/ScalingIssueAffectedEndpoints/types.ts @@ -0,0 +1,5 @@ +import { SpanScalingBadlyInsight } from "../../../types"; + +export interface ScalingIssueEndpointsProps { + insight: SpanScalingBadlyInsight | null; +} diff --git a/src/components/Insights/tickets/common/ScalingIssueRootCauses/index.tsx b/src/components/Insights/tickets/common/ScalingIssueRootCauses/index.tsx new file mode 100644 index 000000000..45cc4a99f --- /dev/null +++ b/src/components/Insights/tickets/common/ScalingIssueRootCauses/index.tsx @@ -0,0 +1,25 @@ +import * as s from "./styles"; +import { ScalingIssueRootCausesProps } from "./types"; + +export const ScalingIssueRootCauses = (props: ScalingIssueRootCausesProps) => { + if (!props.insight) { + return null; + } + + const rootCauses = props.insight.rootCauseSpans; + + if (rootCauses.length === 0) { + return null; + } + + return ( +
    +
    Root causes:
    + + {rootCauses.map((x) => ( +
  • {x.displayName}
  • + ))} +
    +
    + ); +}; diff --git a/src/components/Insights/tickets/common/ScalingIssueRootCauses/styles.ts b/src/components/Insights/tickets/common/ScalingIssueRootCauses/styles.ts new file mode 100644 index 000000000..dcb2f372f --- /dev/null +++ b/src/components/Insights/tickets/common/ScalingIssueRootCauses/styles.ts @@ -0,0 +1,11 @@ +import styled from "styled-components"; + +export const List = styled.ul` + display: flex; + gap: 1em; + flex-direction: column; + list-style-type: disc; + padding: 0; + margin: 0; + margin-left: 1em; +`; diff --git a/src/components/Insights/tickets/common/ScalingIssueRootCauses/types.ts b/src/components/Insights/tickets/common/ScalingIssueRootCauses/types.ts new file mode 100644 index 000000000..65280d830 --- /dev/null +++ b/src/components/Insights/tickets/common/ScalingIssueRootCauses/types.ts @@ -0,0 +1,5 @@ +import { SpanScalingBadlyInsight } from "../../../types"; + +export interface ScalingIssueRootCausesProps { + insight: SpanScalingBadlyInsight | null; +} From b91db738efc7e59f1c7cbbc3345209bdda5e0b2f Mon Sep 17 00:00:00 2001 From: asafchen-dig Date: Wed, 21 Feb 2024 12:35:57 +0200 Subject: [PATCH 2/9] Support multi attachments --- .../InsightJiraTicket.stories.tsx | 25 ++++++- .../Insights/InsightJiraTicket/index.tsx | 2 +- .../Insights/InsightJiraTicket/types.ts | 2 +- src/components/Insights/InsightList/index.tsx | 2 + .../Insights/ScalingIssueInsight/index.tsx | 26 ++++++- .../EndpointNPlusOneInsightTicket/index.tsx | 2 +- .../EndpointQueryOptimizationTicket/index.tsx | 2 +- .../tickets/NPlusOneInsightTicket/index.tsx | 2 +- .../QueryOptimizationInsightTicket/index.tsx | 2 +- .../ScalingIssueInsightTicket/index.tsx | 34 +++++---- src/components/Tests/TestTicket/index.tsx | 2 +- .../common/JiraTicket/AttachmentTag/index.tsx | 2 +- .../common/JiraTicket/AttachmentTag/styles.ts | 5 +- .../common/JiraTicket/Field/index.tsx | 2 +- .../common/JiraTicket/Field/types.ts | 2 +- .../common/JiraTicket/IconButton/index.tsx | 2 +- .../common/JiraTicket/IconButton/types.ts | 1 + .../common/JiraTicket/JiraTicket.stories.tsx | 4 +- src/components/common/JiraTicket/index.tsx | 70 +++++++++++-------- src/components/common/JiraTicket/types.ts | 2 +- 20 files changed, 128 insertions(+), 63 deletions(-) diff --git a/src/components/Insights/InsightJiraTicket/InsightJiraTicket.stories.tsx b/src/components/Insights/InsightJiraTicket/InsightJiraTicket.stories.tsx index b319191ac..40e949859 100644 --- a/src/components/Insights/InsightJiraTicket/InsightJiraTicket.stories.tsx +++ b/src/components/Insights/InsightJiraTicket/InsightJiraTicket.stories.tsx @@ -64,7 +64,6 @@ export const Linked: Story = { args: { summary: "Summary text", description: { content: "Multiline\ndescription text", isLoading: false }, - attachment: { url: "https://www.example.com", fileName: "attachment.ext" }, insight: { ticketLink: "https://digma.ai/ticket/1", ...insight } } }; @@ -73,7 +72,29 @@ export const Unlinked: Story = { args: { summary: "", description: { content: "Multiline\ndescription text", isLoading: false }, - attachment: { url: "https://www.example.com", fileName: "attachment.ext" }, + insight: { ticketLink: null, ...insight } + } +}; + +export const SingleAttachment: Story = { + args: { + summary: "", + description: { content: "Description text", isLoading: false }, + attachments: [ + { url: "https://www.example.com", fileName: "attachment.ext" } + ], + insight: { ticketLink: null, ...insight } + } +}; + +export const MultiAttachments: Story = { + args: { + summary: "Summary text", + description: { content: "Description text", isLoading: false }, + attachments: [ + { url: "https://www.example.com", fileName: "attachment_1.ext" }, + { url: "https://www.example.com", fileName: "attachment_2.ext" } + ], insight: { ticketLink: null, ...insight } } }; diff --git a/src/components/Insights/InsightJiraTicket/index.tsx b/src/components/Insights/InsightJiraTicket/index.tsx index 131a2c4bc..b975e6d49 100644 --- a/src/components/Insights/InsightJiraTicket/index.tsx +++ b/src/components/Insights/InsightJiraTicket/index.tsx @@ -89,7 +89,7 @@ export const InsightJiraTicket = (props: InsightJiraTicketProps) => { a!)} onClose={props.onClose} ticketLink={{ link: ticketLink, errorMessage }} unlinkTicket={unlinkTicket} diff --git a/src/components/Insights/InsightJiraTicket/types.ts b/src/components/Insights/InsightJiraTicket/types.ts index c323d65bb..357e3f7e0 100644 --- a/src/components/Insights/InsightJiraTicket/types.ts +++ b/src/components/Insights/InsightJiraTicket/types.ts @@ -8,7 +8,7 @@ export interface InsightJiraTicketProps { isLoading?: boolean; errorMessage?: string; }; - attachment?: { url: string; fileName: string }; + attachments?: ({ url: string; fileName: string } | undefined)[]; insight: GenericCodeObjectInsight; relatedInsight?: GenericCodeObjectInsight | null; onClose: () => void; diff --git a/src/components/Insights/InsightList/index.tsx b/src/components/Insights/InsightList/index.tsx index e58230cb4..8038087a1 100644 --- a/src/components/Insights/InsightList/index.tsx +++ b/src/components/Insights/InsightList/index.tsx @@ -478,6 +478,8 @@ const renderInsightCard = ( onHistogramButtonClick={handleHistogramButtonClick} onRecalculate={handleRecalculate} onRefresh={handleRefresh} + onJiraTicketCreate={onJiraTicketCreate} + isJiraHintEnabled={isJiraHintEnabled} /> ); } diff --git a/src/components/Insights/ScalingIssueInsight/index.tsx b/src/components/Insights/ScalingIssueInsight/index.tsx index 42c48b680..5cb6fe6e8 100644 --- a/src/components/Insights/ScalingIssueInsight/index.tsx +++ b/src/components/Insights/ScalingIssueInsight/index.tsx @@ -13,6 +13,9 @@ import { Description, Link } from "../styles"; import { Trace } from "../types"; import * as s from "./styles"; import { ScalingIssueInsightProps } from "./types"; +import { JiraButton } from "../common/JiraButton"; +import { sendTrackingEvent } from "../../../utils/sendTrackingEvent"; +import { trackingEvents } from "../tracking"; export const ScalingIssueInsight = (props: ScalingIssueInsightProps) => { const config = useContext(ConfigContext); @@ -39,6 +42,19 @@ export const ScalingIssueInsight = (props: ScalingIssueInsightProps) => { ); }; + const handleCreateJiraTicketButtonClick = (event: string) => { + sendTrackingEvent(trackingEvents.JIRA_TICKET_INFO_BUTTON_CLICKED, { + insightType: props.insight.type + }); + + props.onJiraTicketCreate && + props.onJiraTicketCreate( + props.insight, + props.insight.spanInfo?.spanCodeObjectId, + event + ); + }; + return ( { onClick={handleHistogramButtonClick} > Histogram - + , + ] : []) ]} diff --git a/src/components/Insights/tickets/EndpointNPlusOneInsightTicket/index.tsx b/src/components/Insights/tickets/EndpointNPlusOneInsightTicket/index.tsx index 8c0be4913..19572f477 100644 --- a/src/components/Insights/tickets/EndpointNPlusOneInsightTicket/index.tsx +++ b/src/components/Insights/tickets/EndpointNPlusOneInsightTicket/index.tsx @@ -97,7 +97,7 @@ export const EndpointNPlusOneInsightTicket = ( errorMessage: spanInsight === null ? "Failed to get insight details" : undefined }} - attachment={attachment} + attachments={[attachment]} insight={props.data.insight} relatedInsight={spanInsight} onClose={props.onClose} diff --git a/src/components/Insights/tickets/EndpointQueryOptimizationTicket/index.tsx b/src/components/Insights/tickets/EndpointQueryOptimizationTicket/index.tsx index 126d6dc4c..0352f7be5 100644 --- a/src/components/Insights/tickets/EndpointQueryOptimizationTicket/index.tsx +++ b/src/components/Insights/tickets/EndpointQueryOptimizationTicket/index.tsx @@ -117,7 +117,7 @@ export const EndpointQueryOptimizationInsightTicket = ( errorMessage: spanInsight === null ? "Failed to get insight details" : undefined }} - attachment={attachment} + attachments={[attachment]} insight={props.data.insight} relatedInsight={spanInsight} onClose={props.onClose} diff --git a/src/components/Insights/tickets/NPlusOneInsightTicket/index.tsx b/src/components/Insights/tickets/NPlusOneInsightTicket/index.tsx index cf5ccdaa9..477d13256 100644 --- a/src/components/Insights/tickets/NPlusOneInsightTicket/index.tsx +++ b/src/components/Insights/tickets/NPlusOneInsightTicket/index.tsx @@ -80,7 +80,7 @@ export const NPlusOneInsightTicket = ( content: renderDescription(), isLoading }} - attachment={attachment} + attachments={[attachment]} insight={props.data.insight} onClose={props.onClose} /> diff --git a/src/components/Insights/tickets/QueryOptimizationInsightTicket/index.tsx b/src/components/Insights/tickets/QueryOptimizationInsightTicket/index.tsx index e3bcfcc9d..48e0ba61c 100644 --- a/src/components/Insights/tickets/QueryOptimizationInsightTicket/index.tsx +++ b/src/components/Insights/tickets/QueryOptimizationInsightTicket/index.tsx @@ -130,7 +130,7 @@ export const QueryOptimizationInsightTicket = ( content: renderDescription(), isLoading: isInitialLoading }} - attachment={attachment} + attachments={[attachment]} insight={props.data.insight} onClose={props.onClose} /> diff --git a/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx b/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx index 938b2a977..1f14fe3ff 100644 --- a/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx +++ b/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx @@ -13,7 +13,6 @@ import { ScalingIssueAffectedEndpoints } from "../common/ScalingIssueAffectedEnd import { ScalingIssueRootCauses } from "../common/ScalingIssueRootCauses"; import { getDurationString } from "../../../../utils/getDurationString"; - export const ScalingIssueInsightTicket = ( props: InsightTicketProps ) => { @@ -37,7 +36,7 @@ export const ScalingIssueInsightTicket = ( Tested concurrency: {insight.maxConcurrency} ,
    - Duration range: + Duration range: {getDurationString(insight.minDuration)} -{" "} {getDurationString(insight.maxDuration)} @@ -70,7 +69,6 @@ export const ScalingIssueInsightTicket = ( ); }; - const criticalityString = props.data.insight.criticality > 0 ? `Criticality: ${getCriticalityLabel(props.data.insight.criticality)}` @@ -80,25 +78,37 @@ export const ScalingIssueInsightTicket = ( .join(" - "); const traceId = props.data.insight.affectedEndpoints - ?.map(ep => ep.sampleTraceId) - ?.find(t => t); - const attachment = traceId + ?.map((ep) => ep.sampleTraceId) + ?.find((t) => t); + const attachmentTrace = traceId ? { - url: `${config.jaegerURL}/api/traces/${traceId}?prettyPrint=true`, - fileName: `trace-${traceId}.json` - } + url: `${config.jaegerURL}/api/traces/${traceId}?prettyPrint=true`, + fileName: `trace-${traceId}.json` + } : undefined; + const histogramUrlParams = new URLSearchParams({ + env: insight.environment, + scoid: insight.spanInfo?.spanCodeObjectId || "" + }); + + const attachmentHistogram = { + url: `${ + config.digmaApiUrl + }/Graphs/graphForSpanScaling?${histogramUrlParams.toString()}`, + fileName: `histogram.html` + }; + return ( ); -}; \ No newline at end of file +}; diff --git a/src/components/Tests/TestTicket/index.tsx b/src/components/Tests/TestTicket/index.tsx index bc96156e1..f14a75442 100644 --- a/src/components/Tests/TestTicket/index.tsx +++ b/src/components/Tests/TestTicket/index.tsx @@ -64,7 +64,7 @@ export const TestTicket = (props: TestTicketProps) => { description={{ content: renderDescription() }} - attachment={attachment} + attachments={[attachment]?.filter(Boolean).map((a) => a!)} onClose={props.onClose} tracking={{ prefix: "tests" }} /> diff --git a/src/components/common/JiraTicket/AttachmentTag/index.tsx b/src/components/common/JiraTicket/AttachmentTag/index.tsx index 6d55bd2ba..fdf6e1266 100644 --- a/src/components/common/JiraTicket/AttachmentTag/index.tsx +++ b/src/components/common/JiraTicket/AttachmentTag/index.tsx @@ -5,7 +5,7 @@ import { AttachmentTagProps } from "./types"; export const AttachmentTag = (props: AttachmentTagProps) => ( - + {props.text} diff --git a/src/components/common/JiraTicket/AttachmentTag/styles.ts b/src/components/common/JiraTicket/AttachmentTag/styles.ts index 2e072b7bb..7b9f601fd 100644 --- a/src/components/common/JiraTicket/AttachmentTag/styles.ts +++ b/src/components/common/JiraTicket/AttachmentTag/styles.ts @@ -2,11 +2,8 @@ import styled from "styled-components"; export const Container = styled.div` display: flex; - padding: 4px 6px 4px 4px; + padding: 2px 0; gap: 8px; - border-radius: 4px; - border: 1px solid ${({ theme }) => theme.colors.attachmentTag.border}; - background: ${({ theme }) => theme.colors.attachmentTag.background}; color: ${({ theme }) => theme.colors.attachmentTag.text}; align-items: center; max-width: fit-content; diff --git a/src/components/common/JiraTicket/Field/index.tsx b/src/components/common/JiraTicket/Field/index.tsx index 891419311..f9e39e7d7 100644 --- a/src/components/common/JiraTicket/Field/index.tsx +++ b/src/components/common/JiraTicket/Field/index.tsx @@ -29,7 +29,7 @@ export const Field = (props: FieldProps) => { return ( - {props.label} + {props.label ? {props.label} : <>} {props.content} diff --git a/src/components/common/JiraTicket/Field/types.ts b/src/components/common/JiraTicket/Field/types.ts index dd14d5a7a..40a614316 100644 --- a/src/components/common/JiraTicket/Field/types.ts +++ b/src/components/common/JiraTicket/Field/types.ts @@ -10,7 +10,7 @@ export type ButtonPosition = "top" | "center"; export interface FieldProps { content: ReactNode; - label: string; + label?: string; button: ReactNode; multiline?: boolean; errorMessage?: string; diff --git a/src/components/common/JiraTicket/IconButton/index.tsx b/src/components/common/JiraTicket/IconButton/index.tsx index 1124f2f23..09485a69a 100644 --- a/src/components/common/JiraTicket/IconButton/index.tsx +++ b/src/components/common/JiraTicket/IconButton/index.tsx @@ -5,7 +5,7 @@ import { IconButtonProps } from "./types"; export const IconButton = (props: IconButtonProps) => ( - + ); diff --git a/src/components/common/JiraTicket/IconButton/types.ts b/src/components/common/JiraTicket/IconButton/types.ts index b3a6a8095..c48c8074c 100644 --- a/src/components/common/JiraTicket/IconButton/types.ts +++ b/src/components/common/JiraTicket/IconButton/types.ts @@ -6,4 +6,5 @@ export interface IconButtonProps { onClick: () => void; title: string; disabled?: boolean; + size?: number; } diff --git a/src/components/common/JiraTicket/JiraTicket.stories.tsx b/src/components/common/JiraTicket/JiraTicket.stories.tsx index b35dc8b9e..0746a57f4 100644 --- a/src/components/common/JiraTicket/JiraTicket.stories.tsx +++ b/src/components/common/JiraTicket/JiraTicket.stories.tsx @@ -19,6 +19,8 @@ export const Default: Story = { args: { summary: "Summary text", description: { content: "Multiline\ndescription text", isLoading: false }, - attachment: { url: "https://www.example.com", fileName: "attachment.ext" } + attachments: [ + { url: "https://www.example.com", fileName: "attachment.ext" } + ] } }; diff --git a/src/components/common/JiraTicket/index.tsx b/src/components/common/JiraTicket/index.tsx index 370d15733..b051968b7 100644 --- a/src/components/common/JiraTicket/index.tsx +++ b/src/components/common/JiraTicket/index.tsx @@ -79,23 +79,25 @@ export const JiraTicket = (props: JiraTicketProps) => { } }; - const handleDownloadButtonClick = () => { + const handleDownloadButtonClick = (attachment: { + url: string; + fileName: string; + }) => { sendTrackingEvent( prefixedTrackingEvents.JIRA_TICKET_ATTACHMENT_DOWNLOAD_BUTTON_CLICKED, { ...(props.tracking?.additionalInfo || {}) } ); - if (props.attachment) { - downloadFile(props.attachment.url, props.attachment.fileName).catch( - (e) => { - const errorMessageString = - e instanceof Error ? `Error: ${e.message}` : ""; - setDownloadErrorMessage( - `Failed to download file.\n${errorMessageString}` - ); - } - ); - } + downloadFile(attachment.url, attachment.fileName).catch( + // tmp + (e) => { + const errorMessageString = + e instanceof Error ? `Error: ${e.message}` : ""; + setDownloadErrorMessage( + `Failed to download file.\n${errorMessageString}` + ); + } + ); }; const errorMessage = props.description.isLoading @@ -157,26 +159,32 @@ export const JiraTicket = (props: JiraTicketProps) => { /> } /> - {props.attachment && ( - { + const isFirst = i == 0; + const isLast = i == props.attachments!.length - 1; + return ( + + } + button={ + handleDownloadButtonClick(attachment)} + /> + } + errorMessage={isLast ? downloadErrorMessage : undefined} /> - } - button={ - - } - errorMessage={downloadErrorMessage} - /> - )} + ); + })} {props.showLinkButton && ( void; tracking?: { prefix?: string; From 9eeb9d5b33e0d0dae456e27abcbe29e6b6df27b0 Mon Sep 17 00:00:00 2001 From: asafchen-dig Date: Thu, 22 Feb 2024 09:54:17 +0200 Subject: [PATCH 3/9] Fixed key --- src/components/common/JiraTicket/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/JiraTicket/index.tsx b/src/components/common/JiraTicket/index.tsx index b051968b7..8affefc2f 100644 --- a/src/components/common/JiraTicket/index.tsx +++ b/src/components/common/JiraTicket/index.tsx @@ -165,7 +165,7 @@ export const JiraTicket = (props: JiraTicketProps) => { const isLast = i == props.attachments!.length - 1; return ( Date: Mon, 11 Mar 2024 14:21:37 +0200 Subject: [PATCH 4/9] cleanup --- .../common/ScalingIssueRootCauses/index.tsx | 25 ------------------- .../common/ScalingIssueRootCauses/styles.ts | 11 -------- .../common/ScalingIssueRootCauses/types.ts | 5 ---- 3 files changed, 41 deletions(-) delete mode 100644 src/components/Insights/tickets/common/ScalingIssueRootCauses/index.tsx delete mode 100644 src/components/Insights/tickets/common/ScalingIssueRootCauses/styles.ts delete mode 100644 src/components/Insights/tickets/common/ScalingIssueRootCauses/types.ts diff --git a/src/components/Insights/tickets/common/ScalingIssueRootCauses/index.tsx b/src/components/Insights/tickets/common/ScalingIssueRootCauses/index.tsx deleted file mode 100644 index 45cc4a99f..000000000 --- a/src/components/Insights/tickets/common/ScalingIssueRootCauses/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as s from "./styles"; -import { ScalingIssueRootCausesProps } from "./types"; - -export const ScalingIssueRootCauses = (props: ScalingIssueRootCausesProps) => { - if (!props.insight) { - return null; - } - - const rootCauses = props.insight.rootCauseSpans; - - if (rootCauses.length === 0) { - return null; - } - - return ( -
    -
    Root causes:
    - - {rootCauses.map((x) => ( -
  • {x.displayName}
  • - ))} -
    -
    - ); -}; diff --git a/src/components/Insights/tickets/common/ScalingIssueRootCauses/styles.ts b/src/components/Insights/tickets/common/ScalingIssueRootCauses/styles.ts deleted file mode 100644 index dcb2f372f..000000000 --- a/src/components/Insights/tickets/common/ScalingIssueRootCauses/styles.ts +++ /dev/null @@ -1,11 +0,0 @@ -import styled from "styled-components"; - -export const List = styled.ul` - display: flex; - gap: 1em; - flex-direction: column; - list-style-type: disc; - padding: 0; - margin: 0; - margin-left: 1em; -`; diff --git a/src/components/Insights/tickets/common/ScalingIssueRootCauses/types.ts b/src/components/Insights/tickets/common/ScalingIssueRootCauses/types.ts deleted file mode 100644 index 65280d830..000000000 --- a/src/components/Insights/tickets/common/ScalingIssueRootCauses/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { SpanScalingBadlyInsight } from "../../../types"; - -export interface ScalingIssueRootCausesProps { - insight: SpanScalingBadlyInsight | null; -} From b61d90c3bd8c0f2041e1b00d399ff02d7ede5bf3 Mon Sep 17 00:00:00 2001 From: asafchen-dig Date: Mon, 11 Mar 2024 14:27:06 +0200 Subject: [PATCH 5/9] cleanup --- .../Insights/tickets/ScalingIssueInsightTicket/index.tsx | 4 +++- .../tickets/ScalingIssueInsightTicketByRootCause/index.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx b/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx index 41b2420ec..1e427642c 100644 --- a/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx +++ b/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx @@ -80,6 +80,8 @@ export const ScalingIssueInsightTicket = ( ?.find((t) => t); const attachmentTrace = getTraceAttachment(config, traceId); + // Add it to the attachment(s) after we'll support more than one and + // know how to make https calls while ignoring ssl cert verification const attachmentHistogram = getHistogramAttachment(config, insight); return ( @@ -89,7 +91,7 @@ export const ScalingIssueInsightTicket = ( content: renderDescription(), isLoading: isLoading }} - attachments={[attachmentHistogram, attachmentTrace]} + attachments={[attachmentTrace]} insight={props.data.insight} onClose={props.onClose} /> diff --git a/src/components/Insights/tickets/ScalingIssueInsightTicketByRootCause/index.tsx b/src/components/Insights/tickets/ScalingIssueInsightTicketByRootCause/index.tsx index 09688bf09..9282a4189 100644 --- a/src/components/Insights/tickets/ScalingIssueInsightTicketByRootCause/index.tsx +++ b/src/components/Insights/tickets/ScalingIssueInsightTicketByRootCause/index.tsx @@ -85,6 +85,8 @@ export const ScalingIssueInsightTicketByRootCause = ( const attachmentTrace = getTraceAttachment(config, spanInfo?.sampleTraceId); + // Add it to the attachment(s) after we'll support more than one and + // know how to make https calls while ignoring ssl cert verification const attachmentHistogram = getHistogramAttachment(config, spanInsight); return ( @@ -96,7 +98,7 @@ export const ScalingIssueInsightTicketByRootCause = ( errorMessage: spanInsight === null ? "Failed to get insight details" : undefined }} - attachments={[attachmentTrace, attachmentHistogram]} + attachments={[attachmentTrace]} insight={props.data.insight} relatedInsight={spanInsight} onClose={props.onClose} From f2abec38cfb088ec585d26d812e4cf9123386afc Mon Sep 17 00:00:00 2001 From: asafchen-dig Date: Mon, 11 Mar 2024 15:27:51 +0200 Subject: [PATCH 6/9] Added MultiField component --- .../JiraTicket/Field/MultiField/index.tsx | 51 ++++++++++++++++ .../JiraTicket/Field/MultiField/types.ts | 14 +++++ .../Field/{ => SingleField}/index.tsx | 8 +-- .../JiraTicket/Field/SingleField/types.ts | 10 ++++ .../common/JiraTicket/Field/types.ts | 2 +- src/components/common/JiraTicket/index.tsx | 59 +++++++++++++------ 6 files changed, 120 insertions(+), 24 deletions(-) create mode 100644 src/components/common/JiraTicket/Field/MultiField/index.tsx create mode 100644 src/components/common/JiraTicket/Field/MultiField/types.ts rename src/components/common/JiraTicket/Field/{ => SingleField}/index.tsx (85%) create mode 100644 src/components/common/JiraTicket/Field/SingleField/types.ts diff --git a/src/components/common/JiraTicket/Field/MultiField/index.tsx b/src/components/common/JiraTicket/Field/MultiField/index.tsx new file mode 100644 index 000000000..8ba90d42a --- /dev/null +++ b/src/components/common/JiraTicket/Field/MultiField/index.tsx @@ -0,0 +1,51 @@ +import { useCallback, useRef } from "react"; +import useDimensions from "react-cool-dimensions"; +import useScrollbarSize from "react-scrollbar-size"; +import { isString } from "../../../../../typeGuards/isString"; +import * as s from "../styles"; +import { MultiFieldProps } from "./types"; + +export const MultiField = (props: MultiFieldProps) => { + const scrollbar = useScrollbarSize(); + const contentRef = useRef(null); + const { observe } = useDimensions(); + + const getContentRef = useCallback( + (el: HTMLDivElement | null) => { + observe(el); + contentRef.current = el; + }, + [observe] + ); + + const scrollbarOffset = + contentRef.current && + contentRef.current.scrollHeight > contentRef.current.clientHeight + ? scrollbar.width + : 0; + + return ( + + {props.label} + {props.contents && + props.contents.map((field, i) => { + return ( + + + {field.content} + + {field.button} + + + + ); + })} + {isString(props.errorMessage) && ( + {props.errorMessage} + )} + + ); +}; diff --git a/src/components/common/JiraTicket/Field/MultiField/types.ts b/src/components/common/JiraTicket/Field/MultiField/types.ts new file mode 100644 index 000000000..d85660790 --- /dev/null +++ b/src/components/common/JiraTicket/Field/MultiField/types.ts @@ -0,0 +1,14 @@ +import { ReactNode } from "react"; + +export interface MultiFieldProps { + label: string; + contents?: ContentProps[]; + errorMessage?: string; + selectable?: boolean; +} + +export interface ContentProps { + content: ReactNode; + button: ReactNode; + multiline?: boolean; +} diff --git a/src/components/common/JiraTicket/Field/index.tsx b/src/components/common/JiraTicket/Field/SingleField/index.tsx similarity index 85% rename from src/components/common/JiraTicket/Field/index.tsx rename to src/components/common/JiraTicket/Field/SingleField/index.tsx index f9e39e7d7..a9e2cb77b 100644 --- a/src/components/common/JiraTicket/Field/index.tsx +++ b/src/components/common/JiraTicket/Field/SingleField/index.tsx @@ -1,11 +1,11 @@ import { useCallback, useRef } from "react"; import useDimensions from "react-cool-dimensions"; import useScrollbarSize from "react-scrollbar-size"; -import { isString } from "../../../../typeGuards/isString"; -import * as s from "./styles"; -import { ButtonPosition, FieldProps } from "./types"; +import { isString } from "../../../../../typeGuards/isString"; +import * as s from "../styles"; +import { ButtonPosition, SingleFieldProps } from "../types"; -export const Field = (props: FieldProps) => { +export const SingleField = (props: SingleFieldProps) => { const scrollbar = useScrollbarSize(); const contentRef = useRef(null); const { observe } = useDimensions(); diff --git a/src/components/common/JiraTicket/Field/SingleField/types.ts b/src/components/common/JiraTicket/Field/SingleField/types.ts new file mode 100644 index 000000000..fd74b9278 --- /dev/null +++ b/src/components/common/JiraTicket/Field/SingleField/types.ts @@ -0,0 +1,10 @@ +import { ReactNode } from "react"; + +export interface SingleFieldProps { + content: ReactNode; + label?: string; + button: ReactNode; + multiline?: boolean; + errorMessage?: string; + selectable?: boolean; +} diff --git a/src/components/common/JiraTicket/Field/types.ts b/src/components/common/JiraTicket/Field/types.ts index 40a614316..09ae55767 100644 --- a/src/components/common/JiraTicket/Field/types.ts +++ b/src/components/common/JiraTicket/Field/types.ts @@ -8,7 +8,7 @@ export interface FieldThemeColors { export type ButtonPosition = "top" | "center"; -export interface FieldProps { +export interface SingleFieldProps { content: ReactNode; label?: string; button: ReactNode; diff --git a/src/components/common/JiraTicket/index.tsx b/src/components/common/JiraTicket/index.tsx index 8affefc2f..06cae5334 100644 --- a/src/components/common/JiraTicket/index.tsx +++ b/src/components/common/JiraTicket/index.tsx @@ -16,7 +16,8 @@ import { DownloadIcon } from "../../common/icons/12px/DownloadIcon"; import { PaperclipIcon } from "../../common/icons/12px/PaperclipIcon"; import { JiraLogoIcon } from "../../common/icons/16px/JiraLogoIcon"; import { AttachmentTag } from "./AttachmentTag"; -import { Field } from "./Field"; +import { SingleField } from "./Field/SingleField"; +import { MultiField } from "./Field/MultiField"; import { IconButton } from "./IconButton"; import { TicketLinkButton } from "./TicketLinkButton"; import * as s from "./styles"; @@ -118,7 +119,7 @@ export const JiraTicket = (props: JiraTicketProps) => {
    - { } selectable={false} /> - { /> } /> - {props.attachments && - props.attachments.map((attachment, i) => { - const isFirst = i == 0; - const isLast = i == props.attachments!.length - 1; - return ( - + } + button={ + handleDownloadButtonClick(props.attachments![0])} + /> + } + errorMessage={downloadErrorMessage} + /> + )} + {props.attachments && props.attachments.length > 1 && ( + { + return { + content: ( - } - button={ + ), + button: ( handleDownloadButtonClick(attachment)} /> - } - errorMessage={isLast ? downloadErrorMessage : undefined} - /> - ); - })} + ) + }; + })} + errorMessage={downloadErrorMessage} + /> + )} {props.showLinkButton && ( Date: Mon, 11 Mar 2024 15:31:44 +0200 Subject: [PATCH 7/9] Better filter --- src/components/Tests/TestTicket/index.tsx | 3 ++- src/components/common/JiraTicket/types.ts | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/Tests/TestTicket/index.tsx b/src/components/Tests/TestTicket/index.tsx index f14a75442..d8291069b 100644 --- a/src/components/Tests/TestTicket/index.tsx +++ b/src/components/Tests/TestTicket/index.tsx @@ -6,6 +6,7 @@ import { DigmaSignature } from "../../Insights/tickets/common/DigmaSignature"; import { ConfigContext } from "../../common/App/ConfigContext"; import { JiraTicket } from "../../common/JiraTicket"; import { TestTicketProps } from "./types"; +import { Attachment } from "../../common/JiraTicket/types"; export const TestTicket = (props: TestTicketProps) => { const { @@ -64,7 +65,7 @@ export const TestTicket = (props: TestTicketProps) => { description={{ content: renderDescription() }} - attachments={[attachment]?.filter(Boolean).map((a) => a!)} + attachments={[attachment]?.filter((item): item is Attachment => !!item)} onClose={props.onClose} tracking={{ prefix: "tests" }} /> diff --git a/src/components/common/JiraTicket/types.ts b/src/components/common/JiraTicket/types.ts index c860049a4..5496cb820 100644 --- a/src/components/common/JiraTicket/types.ts +++ b/src/components/common/JiraTicket/types.ts @@ -17,7 +17,7 @@ export interface JiraTicketProps { isLoading?: boolean; errorMessage?: string; }; - attachments?: { url: string; fileName: string }[]; + attachments?: Attachment[]; onClose: () => void; tracking?: { prefix?: string; @@ -32,3 +32,8 @@ export interface JiraTicketProps { unlinkTicket?: () => void; linkTicket?: (link: string) => void; } + +export interface Attachment { + url: string; + fileName: string; +} From f2fe50fe77fa52a6d361ea4e4cd4c89511c75517 Mon Sep 17 00:00:00 2001 From: asafchen-dig Date: Mon, 11 Mar 2024 19:53:00 +0200 Subject: [PATCH 8/9] Added Section --- .../JiraTicket/Field/MultiField/index.tsx | 51 ------- .../JiraTicket/Field/MultiField/types.ts | 14 -- .../JiraTicket/Field/SingleField/index.tsx | 49 ------- .../JiraTicket/Field/SingleField/types.ts | 10 -- .../common/JiraTicket/Field/index.tsx | 42 ++++++ .../common/JiraTicket/Field/styles.ts | 30 +---- .../common/JiraTicket/Field/types.ts | 11 +- .../common/JiraTicket/Section/index.tsx | 15 +++ .../common/JiraTicket/Section/styles.ts | 30 +++++ .../common/JiraTicket/Section/types.ts | 12 ++ src/components/common/JiraTicket/index.tsx | 126 ++++++++---------- 11 files changed, 157 insertions(+), 233 deletions(-) delete mode 100644 src/components/common/JiraTicket/Field/MultiField/index.tsx delete mode 100644 src/components/common/JiraTicket/Field/MultiField/types.ts delete mode 100644 src/components/common/JiraTicket/Field/SingleField/index.tsx delete mode 100644 src/components/common/JiraTicket/Field/SingleField/types.ts create mode 100644 src/components/common/JiraTicket/Field/index.tsx create mode 100644 src/components/common/JiraTicket/Section/index.tsx create mode 100644 src/components/common/JiraTicket/Section/styles.ts create mode 100644 src/components/common/JiraTicket/Section/types.ts diff --git a/src/components/common/JiraTicket/Field/MultiField/index.tsx b/src/components/common/JiraTicket/Field/MultiField/index.tsx deleted file mode 100644 index 8ba90d42a..000000000 --- a/src/components/common/JiraTicket/Field/MultiField/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useCallback, useRef } from "react"; -import useDimensions from "react-cool-dimensions"; -import useScrollbarSize from "react-scrollbar-size"; -import { isString } from "../../../../../typeGuards/isString"; -import * as s from "../styles"; -import { MultiFieldProps } from "./types"; - -export const MultiField = (props: MultiFieldProps) => { - const scrollbar = useScrollbarSize(); - const contentRef = useRef(null); - const { observe } = useDimensions(); - - const getContentRef = useCallback( - (el: HTMLDivElement | null) => { - observe(el); - contentRef.current = el; - }, - [observe] - ); - - const scrollbarOffset = - contentRef.current && - contentRef.current.scrollHeight > contentRef.current.clientHeight - ? scrollbar.width - : 0; - - return ( - - {props.label} - {props.contents && - props.contents.map((field, i) => { - return ( - - - {field.content} - - {field.button} - - - - ); - })} - {isString(props.errorMessage) && ( - {props.errorMessage} - )} - - ); -}; diff --git a/src/components/common/JiraTicket/Field/MultiField/types.ts b/src/components/common/JiraTicket/Field/MultiField/types.ts deleted file mode 100644 index d85660790..000000000 --- a/src/components/common/JiraTicket/Field/MultiField/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ReactNode } from "react"; - -export interface MultiFieldProps { - label: string; - contents?: ContentProps[]; - errorMessage?: string; - selectable?: boolean; -} - -export interface ContentProps { - content: ReactNode; - button: ReactNode; - multiline?: boolean; -} diff --git a/src/components/common/JiraTicket/Field/SingleField/index.tsx b/src/components/common/JiraTicket/Field/SingleField/index.tsx deleted file mode 100644 index a9e2cb77b..000000000 --- a/src/components/common/JiraTicket/Field/SingleField/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useCallback, useRef } from "react"; -import useDimensions from "react-cool-dimensions"; -import useScrollbarSize from "react-scrollbar-size"; -import { isString } from "../../../../../typeGuards/isString"; -import * as s from "../styles"; -import { ButtonPosition, SingleFieldProps } from "../types"; - -export const SingleField = (props: SingleFieldProps) => { - const scrollbar = useScrollbarSize(); - const contentRef = useRef(null); - const { observe } = useDimensions(); - - const getContentRef = useCallback( - (el: HTMLDivElement | null) => { - observe(el); - contentRef.current = el; - }, - [observe] - ); - - const scrollbarOffset = - contentRef.current && - contentRef.current.scrollHeight > contentRef.current.clientHeight - ? scrollbar.width - : 0; - - const iconPosition: ButtonPosition = - props.multiline === true ? "top" : "center"; - - return ( - - {props.label ? {props.label} : <>} - - - {props.content} - - {props.button} - - - - {isString(props.errorMessage) && ( - {props.errorMessage} - )} - - ); -}; diff --git a/src/components/common/JiraTicket/Field/SingleField/types.ts b/src/components/common/JiraTicket/Field/SingleField/types.ts deleted file mode 100644 index fd74b9278..000000000 --- a/src/components/common/JiraTicket/Field/SingleField/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ReactNode } from "react"; - -export interface SingleFieldProps { - content: ReactNode; - label?: string; - button: ReactNode; - multiline?: boolean; - errorMessage?: string; - selectable?: boolean; -} diff --git a/src/components/common/JiraTicket/Field/index.tsx b/src/components/common/JiraTicket/Field/index.tsx new file mode 100644 index 000000000..fce3169db --- /dev/null +++ b/src/components/common/JiraTicket/Field/index.tsx @@ -0,0 +1,42 @@ +import { useCallback, useRef } from "react"; +import useDimensions from "react-cool-dimensions"; +import useScrollbarSize from "react-scrollbar-size"; +import * as s from "./styles"; +import { ButtonPosition, FieldProps } from "./types"; + +export const Field = (props: FieldProps) => { + const scrollbar = useScrollbarSize(); + const contentRef = useRef(null); + const { observe } = useDimensions(); + + const getContentRef = useCallback( + (el: HTMLDivElement | null) => { + observe(el); + contentRef.current = el; + }, + [observe] + ); + + const scrollbarOffset = + contentRef.current && + contentRef.current.scrollHeight > contentRef.current.clientHeight + ? scrollbar.width + : 0; + + const iconPosition: ButtonPosition = + props.multiline === true ? "top" : "center"; + + return ( + + + {props.children} + + {props.button} + + + + ); +}; diff --git a/src/components/common/JiraTicket/Field/styles.ts b/src/components/common/JiraTicket/Field/styles.ts index 32e16842a..737aa479c 100644 --- a/src/components/common/JiraTicket/Field/styles.ts +++ b/src/components/common/JiraTicket/Field/styles.ts @@ -1,17 +1,5 @@ import styled from "styled-components"; -import { redScale } from "../../../common/App/v2colors"; -import { ButtonContainerProps, ContainerProps, ContentProps } from "./types"; - -export const Container = styled.div` - display: flex; - flex-direction: column; - gap: 6px; - ${({ $selectable }) => ($selectable ? "" : "user-select: none;")} -`; - -export const Label = styled.label` - color: ${({ theme }) => theme.colors.jiraTicket.text.secondary}; -`; +import { ButtonContainerProps, ContentProps } from "./types"; export const ContentContainer = styled.div` display: flex; @@ -48,19 +36,3 @@ export const ButtonContainer = styled.div` } }} `; - -export const ErrorMessage = styled.span` - display: flex; - font-size: 13px; - align-items: center; - white-space: pre-line; - color: ${({ theme }) => { - switch (theme.mode) { - case "light": - return redScale[500]; - case "dark": - case "dark-jetbrains": - return redScale[300]; - } - }}; -`; diff --git a/src/components/common/JiraTicket/Field/types.ts b/src/components/common/JiraTicket/Field/types.ts index 09ae55767..a49dfa2fc 100644 --- a/src/components/common/JiraTicket/Field/types.ts +++ b/src/components/common/JiraTicket/Field/types.ts @@ -8,13 +8,10 @@ export interface FieldThemeColors { export type ButtonPosition = "top" | "center"; -export interface SingleFieldProps { - content: ReactNode; - label?: string; +export interface FieldProps { + children: ReactNode; button: ReactNode; multiline?: boolean; - errorMessage?: string; - selectable?: boolean; } export interface ButtonContainerProps { @@ -25,7 +22,3 @@ export interface ButtonContainerProps { export interface ContentProps { $multiline?: boolean; } - -export interface ContainerProps { - $selectable?: boolean; -} diff --git a/src/components/common/JiraTicket/Section/index.tsx b/src/components/common/JiraTicket/Section/index.tsx new file mode 100644 index 000000000..765def69b --- /dev/null +++ b/src/components/common/JiraTicket/Section/index.tsx @@ -0,0 +1,15 @@ +import { isString } from "../../../../typeGuards/isString"; +import * as s from "./styles"; +import { SectionProps } from "./types"; + +export const Section = (props: SectionProps) => { + return ( + + {props.title} + {props.children} + {isString(props.errorMessage) && ( + {props.errorMessage} + )} + + ); +}; diff --git a/src/components/common/JiraTicket/Section/styles.ts b/src/components/common/JiraTicket/Section/styles.ts new file mode 100644 index 000000000..60aca3953 --- /dev/null +++ b/src/components/common/JiraTicket/Section/styles.ts @@ -0,0 +1,30 @@ +import styled from "styled-components"; +import { redScale } from "../../../common/App/v2colors"; +import { ContainerProps } from "./types"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + gap: 6px; + ${({ $selectable }) => ($selectable ? "" : "user-select: none;")} +`; + +export const Label = styled.label` + color: ${({ theme }) => theme.colors.jiraTicket.text.secondary}; +`; + +export const ErrorMessage = styled.span` + display: flex; + font-size: 13px; + align-items: center; + white-space: pre-line; + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return redScale[500]; + case "dark": + case "dark-jetbrains": + return redScale[300]; + } + }}; +`; diff --git a/src/components/common/JiraTicket/Section/types.ts b/src/components/common/JiraTicket/Section/types.ts new file mode 100644 index 000000000..180c40414 --- /dev/null +++ b/src/components/common/JiraTicket/Section/types.ts @@ -0,0 +1,12 @@ +import { ReactNode } from "react"; + +export interface SectionProps { + children: ReactNode; + title: string; + errorMessage?: string; + selectable?: boolean; +} + +export interface ContainerProps { + $selectable?: boolean; +} diff --git a/src/components/common/JiraTicket/index.tsx b/src/components/common/JiraTicket/index.tsx index 06cae5334..429f63a37 100644 --- a/src/components/common/JiraTicket/index.tsx +++ b/src/components/common/JiraTicket/index.tsx @@ -16,8 +16,8 @@ import { DownloadIcon } from "../../common/icons/12px/DownloadIcon"; import { PaperclipIcon } from "../../common/icons/12px/PaperclipIcon"; import { JiraLogoIcon } from "../../common/icons/16px/JiraLogoIcon"; import { AttachmentTag } from "./AttachmentTag"; -import { SingleField } from "./Field/SingleField"; -import { MultiField } from "./Field/MultiField"; +import { Field } from "./Field"; +import { Section } from "./Section"; import { IconButton } from "./IconButton"; import { TicketLinkButton } from "./TicketLinkButton"; import * as s from "./styles"; @@ -119,25 +119,38 @@ export const JiraTicket = (props: JiraTicketProps) => { - copyToClipboard("summary", props.summary)} - /> - } - selectable={false} - /> - + copyToClipboard("summary", props.summary)} + /> + } + > + {props.summary} + + +
    + + copyToClipboard("description", descriptionContentRef.current) + } + /> + } + >
    {props.description.isLoading ? ( @@ -147,64 +160,35 @@ export const JiraTicket = (props: JiraTicketProps) => { props.description.content )}
    - } - errorMessage={errorMessage} - button={ - - copyToClipboard("description", descriptionContentRef.current) - } - /> - } - /> - {props.attachments && props.attachments.length == 1 && ( - - } - button={ - handleDownloadButtonClick(props.attachments![0])} - /> - } +
    +
    + {props.attachments && ( +
    - )} - {props.attachments && props.attachments.length > 1 && ( - { - return { - content: ( + > + {props.attachments.map((attachment, i) => { + return ( + handleDownloadButtonClick(attachment)} + /> + } + > - ), - button: ( - handleDownloadButtonClick(attachment)} - /> - ) - }; + + ); })} - errorMessage={downloadErrorMessage} - /> +
    )} {props.showLinkButton && ( Date: Tue, 12 Mar 2024 06:57:01 +0200 Subject: [PATCH 9/9] Fixes after kyrylo's CR --- .../Insights/InsightJiraTicket/index.tsx | 3 ++- .../Insights/InsightJiraTicket/types.ts | 3 ++- .../EndpointNPlusOneInsightTicket/index.tsx | 18 +++++++++++------- .../EndpointQueryOptimizationTicket/index.tsx | 18 +++++++++++------- .../tickets/NPlusOneInsightTicket/index.tsx | 18 +++++++++++------- .../QueryOptimizationInsightTicket/index.tsx | 18 +++++++++++------- .../ScalingIssueInsightTicket/index.tsx | 9 +++++---- .../index.tsx | 9 +++++---- src/components/Tests/TestTicket/index.tsx | 18 +++++++++++------- src/components/common/JiraTicket/index.tsx | 2 +- src/components/common/JiraTicket/types.ts | 2 +- 11 files changed, 71 insertions(+), 47 deletions(-) diff --git a/src/components/Insights/InsightJiraTicket/index.tsx b/src/components/Insights/InsightJiraTicket/index.tsx index 75514bfc5..3bf8a9753 100644 --- a/src/components/Insights/InsightJiraTicket/index.tsx +++ b/src/components/Insights/InsightJiraTicket/index.tsx @@ -11,6 +11,7 @@ import { InsightsGetDataListQuery, LinkTicketResponse } from "./types"; +import { Attachment } from "../../common/JiraTicket/types"; export const InsightJiraTicket = (props: InsightJiraTicketProps) => { const [errorMessage, setErrorMessage] = useState(); @@ -95,7 +96,7 @@ export const InsightJiraTicket = (props: InsightJiraTicketProps) => { a!)} + attachments={props.attachments || []} onClose={props.onClose} ticketLink={{ link: ticketLink, errorMessage }} unlinkTicket={unlinkTicket} diff --git a/src/components/Insights/InsightJiraTicket/types.ts b/src/components/Insights/InsightJiraTicket/types.ts index 7940a7cc4..2be51245a 100644 --- a/src/components/Insights/InsightJiraTicket/types.ts +++ b/src/components/Insights/InsightJiraTicket/types.ts @@ -1,6 +1,7 @@ import { ReactNode } from "react"; import { InsightsQuery } from "../../common/App/types"; import { GenericCodeObjectInsight } from "../types"; +import { Attachment } from "../../common/JiraTicket/types"; export interface InsightJiraTicketProps { summary: string; @@ -9,7 +10,7 @@ export interface InsightJiraTicketProps { isLoading?: boolean; errorMessage?: string; }; - attachments?: ({ url: string; fileName: string } | undefined)[]; + attachments?: Attachment[]; insight: GenericCodeObjectInsight; relatedInsight?: GenericCodeObjectInsight | null; onClose: () => void; diff --git a/src/components/Insights/tickets/EndpointNPlusOneInsightTicket/index.tsx b/src/components/Insights/tickets/EndpointNPlusOneInsightTicket/index.tsx index f62fc8abf..423963ebe 100644 --- a/src/components/Insights/tickets/EndpointNPlusOneInsightTicket/index.tsx +++ b/src/components/Insights/tickets/EndpointNPlusOneInsightTicket/index.tsx @@ -73,12 +73,16 @@ export const EndpointNPlusOneInsightTicket = ( ); const traceId = span?.traceId; - const attachment = traceId - ? { - url: `${config.jaegerURL}/api/traces/${traceId}?prettyPrint=true`, - fileName: `trace-${traceId}.json` - } - : undefined; + const attachments = [ + ...(traceId + ? [ + { + url: `${config.jaegerURL}/api/traces/${traceId}?prettyPrint=true`, + fileName: `trace-${traceId}.json` + } + ] + : []) + ]; return ( diff --git a/src/components/Insights/tickets/QueryOptimizationInsightTicket/index.tsx b/src/components/Insights/tickets/QueryOptimizationInsightTicket/index.tsx index 2ab6bbc8d..29d6bf373 100644 --- a/src/components/Insights/tickets/QueryOptimizationInsightTicket/index.tsx +++ b/src/components/Insights/tickets/QueryOptimizationInsightTicket/index.tsx @@ -80,12 +80,16 @@ export const QueryOptimizationInsightTicket = ( ); const traceId = props.data.insight.traceId; - const attachment = traceId - ? { - url: `${config.jaegerURL}/api/traces/${traceId}?prettyPrint=true`, - fileName: `trace-${traceId}.json` - } - : undefined; + const attachments = [ + ...(traceId + ? [ + { + url: `${config.jaegerURL}/api/traces/${traceId}?prettyPrint=true`, + fileName: `trace-${traceId}.json` + } + ] + : []) + ]; return ( diff --git a/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx b/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx index 1e427642c..08c394cad 100644 --- a/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx +++ b/src/components/Insights/tickets/ScalingIssueInsightTicket/index.tsx @@ -79,10 +79,11 @@ export const ScalingIssueInsightTicket = ( ?.map((ep) => ep.sampleTraceId) ?.find((t) => t); const attachmentTrace = getTraceAttachment(config, traceId); - - // Add it to the attachment(s) after we'll support more than one and - // know how to make https calls while ignoring ssl cert verification const attachmentHistogram = getHistogramAttachment(config, insight); + const attachments = [ + ...(attachmentTrace ? [attachmentTrace] : []), + ...(attachmentHistogram ? [attachmentHistogram] : []) + ]; return ( diff --git a/src/components/Insights/tickets/ScalingIssueInsightTicketByRootCause/index.tsx b/src/components/Insights/tickets/ScalingIssueInsightTicketByRootCause/index.tsx index 9282a4189..88c01d094 100644 --- a/src/components/Insights/tickets/ScalingIssueInsightTicketByRootCause/index.tsx +++ b/src/components/Insights/tickets/ScalingIssueInsightTicketByRootCause/index.tsx @@ -84,10 +84,11 @@ export const ScalingIssueInsightTicketByRootCause = ( const summary = getScalingIssueSummary(spanInsight); const attachmentTrace = getTraceAttachment(config, spanInfo?.sampleTraceId); - - // Add it to the attachment(s) after we'll support more than one and - // know how to make https calls while ignoring ssl cert verification const attachmentHistogram = getHistogramAttachment(config, spanInsight); + const attachments = [ + ...(attachmentTrace ? [attachmentTrace] : []), + ...(attachmentHistogram ? [attachmentHistogram] : []) + ]; return ( { ); - const attachment = traceId - ? { - url: `${config.jaegerURL}/api/traces/${traceId}?prettyPrint=true`, - fileName: `trace-${traceId}.json` - } - : undefined; + const attachments = [ + ...(traceId + ? [ + { + url: `${config.jaegerURL}/api/traces/${traceId}?prettyPrint=true`, + fileName: `trace-${traceId}.json` + } + ] + : []) + ]; return ( { description={{ content: renderDescription() }} - attachments={[attachment]?.filter((item): item is Attachment => !!item)} + attachments={attachments} onClose={props.onClose} tracking={{ prefix: "tests" }} /> diff --git a/src/components/common/JiraTicket/index.tsx b/src/components/common/JiraTicket/index.tsx index 429f63a37..624c2030f 100644 --- a/src/components/common/JiraTicket/index.tsx +++ b/src/components/common/JiraTicket/index.tsx @@ -162,7 +162,7 @@ export const JiraTicket = (props: JiraTicketProps) => {
    - {props.attachments && ( + {props.attachments.length > 0 && (
    void; tracking?: { prefix?: string;