From 14632501a708e553c6088402b7b5ebdb12301ca4 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Fri, 15 Dec 2023 15:53:15 +0100 Subject: [PATCH 1/2] Add Jira ticket popup --- .storybook/preview-body.html | 4 +- assets/index.ejs | 2 + src/actions.ts | 7 +- .../BottleneckInsight.stories.tsx | 2 + .../DurationBreakdownInsight.stories.tsx | 2 + .../DurationInsight.stories.tsx | 12 + .../DurationSlowdownSourceInsight.stories.tsx | 2 + .../EndpointNPlusOneInsight.stories.tsx | 2 + .../ErrorsInsight/ErrorsInsight.stories.tsx | 2 + .../ExcessiveAPICallsInsight.stories.tsx | 2 + .../InsightCard/InsightCard.stories.tsx | 2 + src/components/Insights/InsightList/index.tsx | 10 +- src/components/Insights/InsightList/types.ts | 1 + src/components/Insights/Insights.stories.tsx | 117 ++++++++ .../JiraTicket/AttachmentTag/index.tsx | 14 + .../JiraTicket/AttachmentTag/styles.ts | 27 ++ .../JiraTicket/AttachmentTag/types.ts | 17 ++ .../Insights/JiraTicket/Field/index.tsx | 49 ++++ .../Insights/JiraTicket/Field/styles.ts | 65 ++++ .../Insights/JiraTicket/Field/types.ts | 26 ++ .../Insights/JiraTicket/IconButton/index.tsx | 11 + .../Insights/JiraTicket/IconButton/styles.ts | 11 + .../Insights/JiraTicket/IconButton/types.ts | 8 + .../JiraTicket/JiraTicket.stories.tsx | 107 +++++++ src/components/Insights/JiraTicket/index.tsx | 277 ++++++++++++++++++ src/components/Insights/JiraTicket/styles.ts | 49 ++++ src/components/Insights/JiraTicket/types.ts | 20 ++ .../NPlusOneInsight.stories.tsx | 7 +- .../Insights/NPlusOneInsight/index.tsx | 19 ++ .../NoScalingIssueInsight.stories.tsx | 2 + .../PerformanceAtScaleInsight.stories.tsx | 2 + .../RequestBreakdownInsight.stories.tsx | 2 + .../ScalingIssueInsight.stories.tsx | 2 + .../SessionInViewInsight.stories.tsx | 2 + .../SlowEndpointInsight.stories.tsx | 2 + .../SpanBottleneckInsight.stories.tsx | 2 + .../TopUsageInsight.stories.tsx | 2 + .../TrafficInsight/TrafficInsight.stories.tsx | 4 + src/components/Insights/actions.ts | 24 ++ .../insights/SpanBottleneckInsight/types.ts | 6 + src/components/Insights/index.tsx | 95 ++++-- src/components/Insights/styles.ts | 17 ++ src/components/Insights/tracking.ts | 15 + src/components/Insights/types.ts | 6 + .../RecentActivity/EnvironmentPanel/index.tsx | 2 +- .../EnvironmentTypePanel/index.tsx | 11 +- .../EnvironmentTypePanel/styles.ts | 34 --- .../EnvironmentTypePanel/types.ts | 5 +- src/components/RecentActivity/index.tsx | 28 +- src/components/common/App/ConfigContext.ts | 4 + src/components/common/App/getTheme.ts | 56 ++++ src/components/common/App/index.tsx | 27 +- src/components/common/App/types.ts | 2 + .../common/IconTag/IconTag.stories.tsx | 31 ++ src/components/common/IconTag/index.tsx | 13 + src/components/common/IconTag/styles.ts | 17 ++ src/components/common/IconTag/types.ts | 19 ++ src/components/common/ImpactScore/index.tsx | 2 +- .../RegistrationDialog.stories.tsx} | 8 +- .../TextField/TextField.stories.tsx | 2 +- .../RegistrationDialog}/TextField/index.tsx | 4 +- .../RegistrationDialog}/TextField/styles.ts | 2 +- .../RegistrationDialog}/TextField/types.ts | 2 +- .../RegistrationDialog}/index.tsx | 14 +- .../RegistrationDialog}/isWorkEmail.ts | 0 .../RegistrationDialog}/styles.ts | 4 +- .../RegistrationDialog}/types.ts | 2 +- src/components/common/icons/12px/CopyIcon.tsx | 34 +++ .../common/icons/12px/CrossIcon.tsx | 33 +++ .../common/icons/12px/DownloadIcon.tsx | 34 +++ .../common/icons/12px/JiraLogoIcon.tsx | 24 ++ .../common/icons/12px/PaperclipIcon.tsx | 33 +++ src/components/common/icons/12px/PlusIcon.tsx | 33 +++ src/components/common/icons/16px/CodeIcon.tsx | 33 +++ .../common/icons/{ => 16px}/EnvelopeIcon.tsx | 4 +- .../common/icons/16px/InfinityIcon.tsx | 33 +++ .../common/icons/16px/JiraLogoIcon.tsx | 32 ++ src/globals.d.ts | 2 + src/styled.d.ts | 8 + 79 files changed, 1538 insertions(+), 111 deletions(-) create mode 100644 src/components/Insights/JiraTicket/AttachmentTag/index.tsx create mode 100644 src/components/Insights/JiraTicket/AttachmentTag/styles.ts create mode 100644 src/components/Insights/JiraTicket/AttachmentTag/types.ts create mode 100644 src/components/Insights/JiraTicket/Field/index.tsx create mode 100644 src/components/Insights/JiraTicket/Field/styles.ts create mode 100644 src/components/Insights/JiraTicket/Field/types.ts create mode 100644 src/components/Insights/JiraTicket/IconButton/index.tsx create mode 100644 src/components/Insights/JiraTicket/IconButton/styles.ts create mode 100644 src/components/Insights/JiraTicket/IconButton/types.ts create mode 100644 src/components/Insights/JiraTicket/JiraTicket.stories.tsx create mode 100644 src/components/Insights/JiraTicket/index.tsx create mode 100644 src/components/Insights/JiraTicket/styles.ts create mode 100644 src/components/Insights/JiraTicket/types.ts create mode 100644 src/components/Insights/actions.ts create mode 100644 src/components/Insights/common/insights/SpanBottleneckInsight/types.ts create mode 100644 src/components/Insights/tracking.ts create mode 100644 src/components/common/IconTag/IconTag.stories.tsx create mode 100644 src/components/common/IconTag/index.tsx create mode 100644 src/components/common/IconTag/styles.ts create mode 100644 src/components/common/IconTag/types.ts rename src/components/{RecentActivity/RegistrationPanel/RegistrationPanel.stories.tsx => common/RegistrationDialog/RegistrationDialog.stories.tsx} (75%) rename src/components/{RecentActivity/RegistrationPanel => common/RegistrationDialog}/TextField/TextField.stories.tsx (90%) rename src/components/{RecentActivity/RegistrationPanel => common/RegistrationDialog}/TextField/index.tsx (90%) rename src/components/{RecentActivity/RegistrationPanel => common/RegistrationDialog}/TextField/styles.ts (98%) rename src/components/{RecentActivity/RegistrationPanel => common/RegistrationDialog}/TextField/types.ts (90%) rename src/components/{RecentActivity/RegistrationPanel => common/RegistrationDialog}/index.tsx (88%) rename src/components/{RecentActivity/RegistrationPanel => common/RegistrationDialog}/isWorkEmail.ts (100%) rename src/components/{RecentActivity/RegistrationPanel => common/RegistrationDialog}/styles.ts (95%) rename src/components/{RecentActivity/RegistrationPanel => common/RegistrationDialog}/types.ts (82%) create mode 100644 src/components/common/icons/12px/CopyIcon.tsx create mode 100644 src/components/common/icons/12px/CrossIcon.tsx create mode 100644 src/components/common/icons/12px/DownloadIcon.tsx create mode 100644 src/components/common/icons/12px/JiraLogoIcon.tsx create mode 100644 src/components/common/icons/12px/PaperclipIcon.tsx create mode 100644 src/components/common/icons/12px/PlusIcon.tsx create mode 100644 src/components/common/icons/16px/CodeIcon.tsx rename src/components/common/icons/{ => 16px}/EnvelopeIcon.tsx (90%) create mode 100644 src/components/common/icons/16px/InfinityIcon.tsx create mode 100644 src/components/common/icons/16px/JiraLogoIcon.tsx diff --git a/.storybook/preview-body.html b/.storybook/preview-body.html index e5ebaceab..02ce46f25 100644 --- a/.storybook/preview-body.html +++ b/.storybook/preview-body.html @@ -5,9 +5,11 @@ window.ide = "IDEA"; window.mainFont; window.codeFont; + window.jaegerURL = "http://localhost:17686"; window.isJaegerEnabled = true; window.userEmail; - window.isObservabilityEnabled; + window.userRegistrationEmail = "email@example.com"; + window.isObservabilityEnabled = true; window.isDigmaEngineInstalled; window.isDigmaEngineRunning; window.isDockerInstalled = true; diff --git a/assets/index.ejs b/assets/index.ejs index fe08587ce..834adf204 100644 --- a/assets/index.ejs +++ b/assets/index.ejs @@ -21,8 +21,10 @@ window.ide; window.mainFont; window.codeFont; + window.jaegerURL; window.isJaegerEnabled; window.userEmail; + window.userRegistrationEmail; window.isObservabilityEnabled; window.isDigmaEngineInstalled; window.isDigmaEngineRunning; diff --git a/src/actions.ts b/src/actions.ts index 94adb2d90..00625e5bf 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -6,6 +6,7 @@ export const actions = addPrefix(ACTION_PREFIX, { SET_THEME: "SET_THEME", SET_MAIN_FONT: "SET_MAIN_FONT", SET_CODE_FONT: "SET_CODE_FONT", + SET_JAEGER_URL: "SET_JAEGER_URL", SET_IS_JAEGER_ENABLED: "SET_IS_JAEGER_ENABLED", SET_IS_DIGMA_ENGINE_INSTALLED: "SET_IS_DIGMA_ENGINE_INSTALLED", SET_IS_DIGMA_ENGINE_RUNNING: "SET_IS_DIGMA_ENGINE_RUNNING", @@ -18,10 +19,12 @@ export const actions = addPrefix(ACTION_PREFIX, { OPEN_TROUBLESHOOTING_GUIDE: "OPEN_TROUBLESHOOTING_GUIDE", OPEN_DOCUMENTATION: "OPEN_DOCUMENTATION", SET_DIGMA_API_URL: "SET_DIGMA_API_URL", - SET_USER_EMAIL: "SET_USER_EMAIL", + SET_USER_REGISTRATION_EMAIL: "SET_USER_REGISTRATION_EMAIL", SET_ENVIRONMENT: "SET_ENVIRONMENT", SET_IS_OBSERVABILITY_ENABLED: "SET_IS_OBSERVABILITY_ENABLED", SET_OBSERVABILITY: "SET_OBSERVABILITY", GET_BACKEND_INFO: "GET_BACKEND_INFO", - SET_BACKEND_INFO: "SET_BACKEND_INFO" + SET_BACKEND_INFO: "SET_BACKEND_INFO", + DOWNLOAD_TRACE_DATA: "DOWNLOAD_TRACE_DATA", + REGISTER: "REGISTER" }); diff --git a/src/components/Insights/BottleneckInsight/BottleneckInsight.stories.tsx b/src/components/Insights/BottleneckInsight/BottleneckInsight.stories.tsx index 4c1fc4e7e..3e9526a2d 100644 --- a/src/components/Insights/BottleneckInsight/BottleneckInsight.stories.tsx +++ b/src/components/Insights/BottleneckInsight/BottleneckInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const Default: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Bottleneck", type: InsightType.SpanEndpointBottleneck, category: InsightCategory.Performance, diff --git a/src/components/Insights/DurationBreakdownInsight/DurationBreakdownInsight.stories.tsx b/src/components/Insights/DurationBreakdownInsight/DurationBreakdownInsight.stories.tsx index 1402e2f96..0aa6261a7 100644 --- a/src/components/Insights/DurationBreakdownInsight/DurationBreakdownInsight.stories.tsx +++ b/src/components/Insights/DurationBreakdownInsight/DurationBreakdownInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const Default: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Span Duration Breakdown", type: InsightType.SpanDurationBreakdown, category: InsightCategory.Performance, diff --git a/src/components/Insights/DurationInsight/DurationInsight.stories.tsx b/src/components/Insights/DurationInsight/DurationInsight.stories.tsx index 5afdd717d..c1a9ec191 100644 --- a/src/components/Insights/DurationInsight/DurationInsight.stories.tsx +++ b/src/components/Insights/DurationInsight/DurationInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const WithAverage: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Performance Stats", type: InsightType.SpanDurations, category: InsightCategory.Performance, @@ -126,6 +128,8 @@ export const WithAverage: Story = { export const WithChange: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Performance Stats", type: InsightType.SpanDurations, category: InsightCategory.Performance, @@ -222,6 +226,8 @@ export const WithChange: Story = { export const WithEvaluatingChange: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Performance Stats", type: InsightType.SpanDurations, category: InsightCategory.Performance, @@ -318,6 +324,8 @@ export const WithEvaluatingChange: Story = { export const HistogramWithManyBars: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Performance Stats", type: InsightType.SpanDurations, category: InsightCategory.Performance, @@ -4832,6 +4840,8 @@ export const HistogramWithManyBars: Story = { export const HistogramWithGaps: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Performance Stats", type: InsightType.SpanDurations, category: InsightCategory.Performance, @@ -5076,6 +5086,8 @@ export const HistogramWithGaps: Story = { export const HistogramWithAFewBars: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Performance Stats", type: InsightType.SpanDurations, category: InsightCategory.Performance, diff --git a/src/components/Insights/DurationSlowdownSourceInsight/DurationSlowdownSourceInsight.stories.tsx b/src/components/Insights/DurationSlowdownSourceInsight/DurationSlowdownSourceInsight.stories.tsx index 837c3bcc6..58b5b24c9 100644 --- a/src/components/Insights/DurationSlowdownSourceInsight/DurationSlowdownSourceInsight.stories.tsx +++ b/src/components/Insights/DurationSlowdownSourceInsight/DurationSlowdownSourceInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const WithEvaluatingChange: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Endpoint Duration Slowdown Source", type: InsightType.EndpointDurationSlowdown, category: InsightCategory.Performance, diff --git a/src/components/Insights/EndpointNPlusOneInsight/EndpointNPlusOneInsight.stories.tsx b/src/components/Insights/EndpointNPlusOneInsight/EndpointNPlusOneInsight.stories.tsx index c82fcb063..2240f77e1 100644 --- a/src/components/Insights/EndpointNPlusOneInsight/EndpointNPlusOneInsight.stories.tsx +++ b/src/components/Insights/EndpointNPlusOneInsight/EndpointNPlusOneInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const Default: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Suspected N+1 Query", type: InsightType.EndpointSpanNPlusOne, category: InsightCategory.Performance, diff --git a/src/components/Insights/ErrorsInsight/ErrorsInsight.stories.tsx b/src/components/Insights/ErrorsInsight/ErrorsInsight.stories.tsx index 4479f3928..db1394c54 100644 --- a/src/components/Insights/ErrorsInsight/ErrorsInsight.stories.tsx +++ b/src/components/Insights/ErrorsInsight/ErrorsInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const Default: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Errors", type: InsightType.Errors, scope: InsightScope.Function, diff --git a/src/components/Insights/ExcessiveAPICallsInsight/ExcessiveAPICallsInsight.stories.tsx b/src/components/Insights/ExcessiveAPICallsInsight/ExcessiveAPICallsInsight.stories.tsx index 8b9c1d5af..a65c3269b 100644 --- a/src/components/Insights/ExcessiveAPICallsInsight/ExcessiveAPICallsInsight.stories.tsx +++ b/src/components/Insights/ExcessiveAPICallsInsight/ExcessiveAPICallsInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const Default: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "HTTP Chatter", type: InsightType.EndpointChattyApi, category: InsightCategory.Performance, diff --git a/src/components/Insights/InsightCard/InsightCard.stories.tsx b/src/components/Insights/InsightCard/InsightCard.stories.tsx index 3afd69f6f..222cc5b2a 100644 --- a/src/components/Insights/InsightCard/InsightCard.stories.tsx +++ b/src/components/Insights/InsightCard/InsightCard.stories.tsx @@ -21,6 +21,8 @@ type Story = StoryObj; export const Default: Story = { args: { data: { + criticality: 0, + impact: 0, name: "Suspected N+1 Query", type: InsightType.EndpointSpanNPlusOne, category: InsightCategory.Performance, diff --git a/src/components/Insights/InsightList/index.tsx b/src/components/Insights/InsightList/index.tsx index 7712fa264..722c10c44 100644 --- a/src/components/Insights/InsightList/index.tsx +++ b/src/components/Insights/InsightList/index.tsx @@ -1,6 +1,5 @@ import { useEffect, useState } from "react"; import { DefaultTheme, useTheme } from "styled-components"; -import { actions } from ".."; import { InsightType } from "../../../types"; import { getInsightTypeInfo } from "../../../utils/getInsightTypeInfo"; import { Card } from "../../common/Card"; @@ -26,6 +25,7 @@ import { SlowEndpointInsight } from "../SlowEndpointInsight"; import { SpanBottleneckInsight } from "../SpanBottleneckInsight"; import { TopUsageInsight } from "../TopUsageInsight"; import { TrafficInsight } from "../TrafficInsight"; +import { actions } from "../actions"; import { Description } from "../styles"; import { isChattyApiEndpointInsight, @@ -210,7 +210,8 @@ const getInsightGroupIconColor = (theme: DefaultTheme) => { }; const renderInsightCard = ( - insight: GenericCodeObjectInsight + insight: GenericCodeObjectInsight, + onJiraTicketCreate: (insight: GenericCodeObjectInsight) => void ): JSX.Element | undefined => { const handleErrorSelect = (errorId: string) => { window.sendMessageToDigma({ @@ -425,6 +426,7 @@ const renderInsightCard = ( onTraceButtonClick={handleTraceButtonClick} onRecalculate={handleRecalculate} onRefresh={handleRefresh} + onJiraTicketCreate={onJiraTicketCreate} /> ); } @@ -587,7 +589,9 @@ export const InsightList = (props: InsightListProps) => { )} {x.insights.length > 0 ? ( - x.insights.map((insight) => renderInsightCard(insight)) + x.insights.map((insight) => + renderInsightCard(insight, props.onJiraTicketCreate) + ) ) : ( No data yet} diff --git a/src/components/Insights/InsightList/types.ts b/src/components/Insights/InsightList/types.ts index 40f6129df..e57f61f2a 100644 --- a/src/components/Insights/InsightList/types.ts +++ b/src/components/Insights/InsightList/types.ts @@ -9,4 +9,5 @@ export interface InsightListProps { hasObservability: boolean; hasMissingDependency: boolean; canInstrumentMethod: boolean; + onJiraTicketCreate: (insight: GenericCodeObjectInsight) => void; } diff --git a/src/components/Insights/Insights.stories.tsx b/src/components/Insights/Insights.stories.tsx index 2762b24f6..179afc729 100644 --- a/src/components/Insights/Insights.stories.tsx +++ b/src/components/Insights/Insights.stories.tsx @@ -48,6 +48,107 @@ export const Default: Story = { needsObservabilityFix: false, insights: [ { + criticality: 0.8, + impact: 0, + name: "N+1", + type: InsightType.SpanNPlusOne, + category: InsightCategory.Performance, + specifity: 2, + importance: 2, + span: { + name: "OwnerValidation.ValidateOwner", + displayName: "OwnerValidation.ValidateOwner", + instrumentationLibrary: + "io.opentelemetry.opentelemetry-instrumentation-annotations-1.16", + spanCodeObjectId: + "span:io.opentelemetry.opentelemetry-instrumentation-annotations-1.16$_$OwnerValidation.ValidateOwner", + methodCodeObjectId: + "org.springframework.samples.petclinic.domain.OwnerValidation$_$ValidateOwner", + kind: "Internal", + codeObjectId: + "org.springframework.samples.petclinic.domain.OwnerValidation$_$ValidateOwner" + }, + occurrences: 100, + traceId: "00D37A4E7208E0F6E89AA7E2E37446A6", + clientSpanName: "select * from users where id = :id", + clientSpanCodeObjectId: + "span:OwnerController$_$1D138649EB4FFA92C0E3C8103404F2", + duration: { + value: 1.64, + unit: "sec", + raw: 1636050588.0 + }, + endpoints: [ + { + endpointInfo: { + route: "HTTP POST /owners/new", + instrumentationLibrary: "OwnerController", + spanCodeObjectId: + "span:OwnerController$_$1D138649EB4FFA92C0E3C8103404F2", + entrySpanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP POST /owners/new", + serviceName: "spring-petclinic" + }, + occurrences: 100, + criticality: 1, + impact: 0, + severity: 0 + }, + { + endpointInfo: { + route: "HTTP POST /owners/new2", + instrumentationLibrary: "OwnerController", + spanCodeObjectId: + "span:OwnerController$_$1D138649EB4FFA92C0E3C8103404F3", + entrySpanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP POST /owners/new2", + serviceName: "spring-petclinic" + }, + occurrences: 100, + criticality: 1, + impact: 0, + severity: 0 + } + ], + scope: InsightScope.Span, + spanInfo: { + name: "OwnerValidation.ValidateOwner", + displayName: "OwnerValidation.ValidateOwner", + instrumentationLibrary: + "io.opentelemetry.opentelemetry-instrumentation-annotations-1.16", + spanCodeObjectId: + "span:io.opentelemetry.opentelemetry-instrumentation-annotations-1.16$_$OwnerValidation.ValidateOwner", + methodCodeObjectId: + "org.springframework.samples.petclinic.domain.OwnerValidation$_$ValidateOwner", + kind: "Internal", + codeObjectId: + "org.springframework.samples.petclinic.domain.OwnerValidation$_$ValidateOwner" + }, + shortDisplayInfo: { + title: "", + targetDisplayName: "", + subtitle: "", + description: "" + }, + codeObjectId: + "org.springframework.samples.petclinic.domain.OwnerValidation$_$ValidateOwner", + decorators: [ + { + title: "N+1", + description: "Supected NPlus One" + } + ], + environment: "BOB-LAPTOP[LOCAL]", + severity: 0.0, + isRecalculateEnabled: false, + prefixedCodeObjectId: + "method:org.springframework.samples.petclinic.domain.OwnerValidation$_$ValidateOwner", + customStartTime: null, + actualStartTime: "2023-07-27T08:23:56.500827Z" + }, + { + criticality: 0, + impact: 0, name: "Request Breakdown", type: InsightType.EndpointBreakdown, category: InsightCategory.Usage, @@ -110,6 +211,8 @@ export const Default: Story = { actualStartTime: "2023-06-26T00:00:00.000Z" }, { + criticality: 0, + impact: 0, name: "Errors", type: InsightType.Errors, scope: InsightScope.Function, @@ -155,6 +258,8 @@ export const Default: Story = { actualStartTime: "2023-06-26T13:53:53.645Z" }, { + criticality: 0, + impact: 0, name: "Errors Hotspot", type: InsightType.HotSpot, scope: InsightScope.Function, @@ -186,6 +291,8 @@ export const Default: Story = { actualStartTime: "2023-06-26T13:53:57.956Z" }, { + criticality: 0, + impact: 0, name: "Low Usage", type: InsightType.LowUsage, category: InsightCategory.Usage, @@ -233,6 +340,8 @@ export const Default: Story = { actualStartTime: "2023-06-12T13:48:59.404Z" }, { + criticality: 0, + impact: 0, name: "Bottleneck Detected", type: InsightType.SlowestSpans, category: InsightCategory.Performance, @@ -318,6 +427,8 @@ export const Default: Story = { actualStartTime: "2023-06-12T13:49:08.186Z" }, { + criticality: 0, + impact: 0, name: "Span Duration Breakdown", type: InsightType.SpanDurationBreakdown, category: InsightCategory.Performance, @@ -384,6 +495,8 @@ export const Default: Story = { actualStartTime: "2023-06-12T13:49:03.486Z" }, { + criticality: 0, + impact: 0, name: "Performance Stats", type: InsightType.SpanDurations, category: InsightCategory.Performance, @@ -470,6 +583,8 @@ export const Default: Story = { actualStartTime: "2023-06-13T00:00:00.000Z" }, { + criticality: 0, + impact: 0, name: "Scaling Insufficient Data", type: InsightType.SpanScalingInsufficientData, category: InsightCategory.Performance, @@ -601,6 +716,8 @@ export const NoObservability: Story = { }; const errorsInsight: CodeObjectErrorsInsight = { + criticality: 0, + impact: 0, name: "Errors", type: InsightType.Errors, scope: InsightScope.Function, diff --git a/src/components/Insights/JiraTicket/AttachmentTag/index.tsx b/src/components/Insights/JiraTicket/AttachmentTag/index.tsx new file mode 100644 index 000000000..6d55bd2ba --- /dev/null +++ b/src/components/Insights/JiraTicket/AttachmentTag/index.tsx @@ -0,0 +1,14 @@ +import { Tooltip } from "../../../common/Tooltip"; +import * as s from "./styles"; +import { AttachmentTagProps } from "./types"; + +export const AttachmentTag = (props: AttachmentTagProps) => ( + + + + + + {props.text} + + +); diff --git a/src/components/Insights/JiraTicket/AttachmentTag/styles.ts b/src/components/Insights/JiraTicket/AttachmentTag/styles.ts new file mode 100644 index 000000000..2e072b7bb --- /dev/null +++ b/src/components/Insights/JiraTicket/AttachmentTag/styles.ts @@ -0,0 +1,27 @@ +import styled from "styled-components"; + +export const Container = styled.div` + display: flex; + padding: 4px 6px 4px 4px; + 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; +`; + +export const IconContainer = styled.div` + padding: 2px; + border-radius: 4px; + color: ${({ theme }) => theme.colors.attachmentTag.icon.stroke}; + background: ${({ theme }) => theme.colors.attachmentTag.icon.background}; + display: flex; +`; + +export const TextContainer = styled.span` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; diff --git a/src/components/Insights/JiraTicket/AttachmentTag/types.ts b/src/components/Insights/JiraTicket/AttachmentTag/types.ts new file mode 100644 index 000000000..dd182de52 --- /dev/null +++ b/src/components/Insights/JiraTicket/AttachmentTag/types.ts @@ -0,0 +1,17 @@ +import { ComponentType } from "react"; +import { IconProps } from "../../../common/icons/types"; + +export interface AttachmentTagThemeColors { + background: string; + border: string; + icon: { + background: string; + stroke: string; + }; + text: string; +} + +export interface AttachmentTagProps { + icon: ComponentType; + text: string; +} diff --git a/src/components/Insights/JiraTicket/Field/index.tsx b/src/components/Insights/JiraTicket/Field/index.tsx new file mode 100644 index 000000000..891200984 --- /dev/null +++ b/src/components/Insights/JiraTicket/Field/index.tsx @@ -0,0 +1,49 @@ +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"; + +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.label} + + + {props.content} + + {props.button} + + + + {isString(props.errorMessage) && ( + {props.errorMessage} + )} + + ); +}; diff --git a/src/components/Insights/JiraTicket/Field/styles.ts b/src/components/Insights/JiraTicket/Field/styles.ts new file mode 100644 index 000000000..076cedf73 --- /dev/null +++ b/src/components/Insights/JiraTicket/Field/styles.ts @@ -0,0 +1,65 @@ +import styled from "styled-components"; +import { redScale } from "../../../common/App/getTheme"; +import { ButtonContainerProps, ContentProps } from "./types"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + gap: 6px; +`; + +export const Label = styled.label` + color: ${({ theme }) => theme.colors.jiraTicket.text.secondary}; +`; + +export const ContentContainer = styled.div` + display: flex; + border-radius: 4px; + border: 1px solid ${({ theme }) => theme.colors.field.border}; + color: ${({ theme }) => theme.colors.field.text}; + position: relative; +`; + +export const Content = styled.div` + width: 100%; + max-height: 200px; + padding: 6px 28px 6px 8px; + overflow: ${({ $multiline }) => ($multiline ? "auto" : "hidden")}; + white-space: ${({ $multiline }) => ($multiline ? "pre-line" : "nowrap")}; + ${({ $multiline }) => + $multiline ? "word-wrap: break-word" : "text-overflow: ellipsis"}; +`; + +export const ButtonContainer = styled.div` + position: absolute; + right: ${({ $scrollbarOffset }) => $scrollbarOffset + 4}px; + ${({ $position }) => { + switch ($position) { + case "top": + return "top: 4px;"; + case "center": + return ` + top: 0; + bottom: 0; + margin: auto; + height: fit-content; + `; + } + }} +`; + +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/Insights/JiraTicket/Field/types.ts b/src/components/Insights/JiraTicket/Field/types.ts new file mode 100644 index 000000000..d3e635fd4 --- /dev/null +++ b/src/components/Insights/JiraTicket/Field/types.ts @@ -0,0 +1,26 @@ +import { ReactNode } from "react"; + +export interface FieldThemeColors { + border: string; + icon: string; + text: string; +} + +export type ButtonPosition = "top" | "center"; + +export interface FieldProps { + content: ReactNode; + label: string; + button: ReactNode; + multiline?: boolean; + errorMessage?: string; +} + +export interface ButtonContainerProps { + $position: ButtonPosition; + $scrollbarOffset: number; +} + +export interface ContentProps { + $multiline?: boolean; +} diff --git a/src/components/Insights/JiraTicket/IconButton/index.tsx b/src/components/Insights/JiraTicket/IconButton/index.tsx new file mode 100644 index 000000000..83da0fe32 --- /dev/null +++ b/src/components/Insights/JiraTicket/IconButton/index.tsx @@ -0,0 +1,11 @@ +import { Tooltip } from "../../../common/Tooltip"; +import * as s from "./styles"; +import { IconButtonProps } from "./types"; + +export const IconButton = (props: IconButtonProps) => ( + + + + + +); diff --git a/src/components/Insights/JiraTicket/IconButton/styles.ts b/src/components/Insights/JiraTicket/IconButton/styles.ts new file mode 100644 index 000000000..742a25a2c --- /dev/null +++ b/src/components/Insights/JiraTicket/IconButton/styles.ts @@ -0,0 +1,11 @@ +import styled from "styled-components"; + +export const Button = styled.button` + background: none; + border: none; + margin: 0; + padding: 4px; + cursor: pointer; + display: flex; + color: ${({ theme }) => theme.colors.field.icon}; +`; diff --git a/src/components/Insights/JiraTicket/IconButton/types.ts b/src/components/Insights/JiraTicket/IconButton/types.ts new file mode 100644 index 000000000..4d8913b69 --- /dev/null +++ b/src/components/Insights/JiraTicket/IconButton/types.ts @@ -0,0 +1,8 @@ +import { ComponentType } from "react"; +import { IconProps } from "../../../common/icons/types"; + +export interface IconButtonProps { + icon: ComponentType; + onClick: () => void; + title: string; +} diff --git a/src/components/Insights/JiraTicket/JiraTicket.stories.tsx b/src/components/Insights/JiraTicket/JiraTicket.stories.tsx new file mode 100644 index 000000000..ccefcfbaa --- /dev/null +++ b/src/components/Insights/JiraTicket/JiraTicket.stories.tsx @@ -0,0 +1,107 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { JiraTicket } from "."; +import { InsightType } from "../../../types"; +import { InsightCategory, InsightScope } from "../types"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Insights/JiraTicket", + component: JiraTicket, + 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 NPlusOneInsight: Story = { + args: { + insight: { + criticality: 1, + impact: 1, + name: "N+1", + type: InsightType.SpanNPlusOne, + category: InsightCategory.Performance, + specifity: 2, + importance: 2, + span: { + name: "OwnerValidation.ValidateOwner", + displayName: "OwnerValidation.ValidateOwner", + instrumentationLibrary: + "io.opentelemetry.opentelemetry-instrumentation-annotations-1.16", + spanCodeObjectId: + "span:io.opentelemetry.opentelemetry-instrumentation-annotations-1.16$_$OwnerValidation.ValidateOwner", + methodCodeObjectId: + "org.springframework.samples.petclinic.domain.OwnerValidation$_$ValidateOwner", + kind: "Internal", + codeObjectId: + "org.springframework.samples.petclinic.domain.OwnerValidation$_$ValidateOwner" + }, + occurrences: 100, + traceId: "00D37A4E7208E0F6E89AA7E2E37446A6", + clientSpanName: "select * from users where id = :id", + clientSpanCodeObjectId: + "span:OwnerController$_$1D138649EB4FFA92C0E3C8103404F2", + duration: { + value: 1.64, + unit: "sec", + raw: 1636050588.0 + }, + endpoints: [ + { + endpointInfo: { + route: "HTTP POST /owners/new", + instrumentationLibrary: "OwnerController", + spanCodeObjectId: + "span:OwnerController$_$1D138649EB4FFA92C0E3C8103404F2", + entrySpanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP POST /owners/new", + serviceName: "spring-petclinic" + }, + occurrences: 100, + criticality: 0, + impact: 0, + severity: 0 + } + ], + scope: InsightScope.Span, + spanInfo: { + name: "OwnerValidation.ValidateOwner", + displayName: "OwnerValidation.ValidateOwner", + instrumentationLibrary: + "io.opentelemetry.opentelemetry-instrumentation-annotations-1.16", + spanCodeObjectId: + "span:io.opentelemetry.opentelemetry-instrumentation-annotations-1.16$_$OwnerValidation.ValidateOwner", + methodCodeObjectId: + "org.springframework.samples.petclinic.domain.OwnerValidation$_$ValidateOwner", + kind: "Internal", + codeObjectId: + "org.springframework.samples.petclinic.domain.OwnerValidation$_$ValidateOwner" + }, + shortDisplayInfo: { + title: "", + targetDisplayName: "", + subtitle: "", + description: "" + }, + codeObjectId: + "org.springframework.samples.petclinic.domain.OwnerValidation$_$ValidateOwner", + decorators: [ + { + title: "N+1", + description: "Supected NPlus One" + } + ], + environment: "BOB-LAPTOP[LOCAL]", + severity: 0.0, + isRecalculateEnabled: false, + prefixedCodeObjectId: + "method:org.springframework.samples.petclinic.domain.OwnerValidation$_$ValidateOwner", + customStartTime: null, + actualStartTime: "2023-07-27T08:23:56.500827Z" + } + } +}; diff --git a/src/components/Insights/JiraTicket/index.tsx b/src/components/Insights/JiraTicket/index.tsx new file mode 100644 index 000000000..56a3c9c71 --- /dev/null +++ b/src/components/Insights/JiraTicket/index.tsx @@ -0,0 +1,277 @@ +import axios, { AxiosResponse } from "axios"; +import copy from "copy-to-clipboard"; +import { useContext, useEffect, useState } from "react"; +import { useTheme } from "styled-components"; +import { DefaultTheme } from "styled-components/dist/types"; +import { dispatcher } from "../../../dispatcher"; +import { isString } from "../../../typeGuards/isString"; +import { sendTrackingEvent } from "../../../utils/sendTrackingEvent"; +import { trimEndpointScheme } from "../../../utils/trimEndpointScheme"; +import { ConfigContext } from "../../common/App/ConfigContext"; +import { CircleLoader } from "../../common/CircleLoader"; +import { CircleLoaderColors } from "../../common/CircleLoader/types"; +import { IconTag } from "../../common/IconTag"; +import { Tooltip } from "../../common/Tooltip"; +import { CopyIcon } from "../../common/icons/12px/CopyIcon"; +import { CrossIcon } from "../../common/icons/12px/CrossIcon"; +import { DownloadIcon } from "../../common/icons/12px/DownloadIcon"; +import { PaperclipIcon } from "../../common/icons/12px/PaperclipIcon"; +import { JiraLogoIcon } from "../../common/icons/16px/JiraLogoIcon"; +import { actions } from "../actions"; +import { trackingEvents } from "../tracking"; +import { isSpanNPlusOneInsight } from "../typeGuards"; +import { AttachmentTag } from "./AttachmentTag"; +import { Field } from "./Field"; +import { IconButton } from "./IconButton"; +import * as s from "./styles"; +import { CodeLocationsData, JiraTicketProps } from "./types"; + +const getCircleLoaderColors = (theme: DefaultTheme): CircleLoaderColors => { + switch (theme.mode) { + case "light": + return { + start: "rgb(81 84 236 / 0%)", + end: "#5154ec", + background: "#fff" + }; + case "dark": + case "dark-jetbrains": + return { + start: "rgb(120 145 208 / 0%)", + end: "#7891d0", + background: "#222326" + }; + } +}; + +const getCriticalityLabel = (criticality: number) => { + if (criticality === 0) { + return "N/A"; + } + + if (criticality < 0.3) { + return "Low"; + } + + if (criticality < 0.7) { + return "Medium"; + } + + return "High"; +}; + +export const JiraTicket = (props: JiraTicketProps) => { + const [isInitialLoading, setIsInitialLoading] = useState(false); + const [codeLocations, setCodeLocations] = useState([]); + const config = useContext(ConfigContext); + const [errorMessage, setErrorMessage] = useState(); + const theme = useTheme(); + + let spanCodeObjectId: string | undefined; + let methodCodeObjectId: string | undefined; + + let summary = ""; + let description = ""; + let traceId: string | null = null; + const criticality = props.insight.criticality; + + const downloadFile = async (url: string, fileName: string) => { + try { + const response: AxiosResponse = await axios.get(url, { + responseType: "blob" + }); + const href = URL.createObjectURL(response.data); + const link = document.createElement("a"); + link.setAttribute("href", href); + link.setAttribute("download", fileName); + link.click(); + URL.revokeObjectURL(href); + } catch (e) { + const errorMessageString = + e instanceof Error ? `Error: ${e.message}` : ""; + setErrorMessage(`Failed to download file.\n${errorMessageString}`); + } + }; + + if (isSpanNPlusOneInsight(props.insight)) { + spanCodeObjectId = props.insight.spanInfo?.spanCodeObjectId; + methodCodeObjectId = + props.insight.spanInfo?.methodCodeObjectId || undefined; + + const services = [ + ...new Set(props.insight.endpoints.map((x) => x.endpointInfo.serviceName)) + ]; + + const serviceString = services.length > 0 ? services.join(", ") : ""; + const criticalityString = `Criticality: ${getCriticalityLabel( + criticality + )}`; + summary = ["N+1 Issue found", serviceString, criticalityString] + .filter(Boolean) + .join(" - "); + + const queryString = props.insight.spanInfo?.displayName || ""; + + const codeLocationsString = + codeLocations.length > 0 + ? ["Related code locations:", ...codeLocations].join("\n") + : ""; + + const endpointsDataString = props.insight.endpoints + .map((x) => + [ + `• ${x.endpointInfo.serviceName} ${trimEndpointScheme( + x.endpointInfo.route + )}`, + ` Repeats: ${x.occurrences} Criticality: ${getCriticalityLabel( + x.criticality + )}` + ].join("\n") + ) + .join("\n\n"); + + const affectedEndpointsString = + props.insight.endpoints.length > 0 + ? ["Affected endpoints:", endpointsDataString].join("\n") + : ""; + + description = [ + "N+1 Issue found", + queryString, + codeLocationsString, + affectedEndpointsString, + "info by digma.ai" + ] + .filter(Boolean) + .join("\n\n"); + + traceId = props.insight.traceId; + } + + useEffect(() => { + window.sendMessageToDigma({ + action: actions.GET_CODE_LOCATIONS, + payload: { + spanCodeObjectId, + methodCodeObjectId + } + }); + setIsInitialLoading(true); + + const handleCodeLocationsData = (data: unknown) => { + const codeLocationsData = data as CodeLocationsData; + setCodeLocations(codeLocationsData.codeLocations); + setIsInitialLoading(false); + }; + + dispatcher.addActionListener( + actions.SET_CODE_LOCATIONS, + handleCodeLocationsData + ); + + return () => { + dispatcher.removeActionListener( + actions.SET_CODE_LOCATIONS, + handleCodeLocationsData + ); + }; + }, []); + + const handleCloseButtonClick = () => { + props.onClose(); + }; + + const handleDownloadButtonClick = () => { + sendTrackingEvent( + trackingEvents.JIRA_TICKET_ATTACHMENT_DOWNLOAD_BUTTON_CLICKED, + { + insightType: props.insight.type + } + ); + + if (traceId) { + const url = `${config.jaegerURL}/api/traces/${traceId}?prettyPrint=true`; + void downloadFile(url, `trace-${traceId}.json`); + } + }; + + const copyToClipboard = (field: string, value: string) => { + sendTrackingEvent(trackingEvents.JIRA_TICKET_FIELD_COPY_BUTTON_CLICKED, { + insightType: props.insight.type, + field + }); + copy(value); + }; + + return ( + + + + + Create Jira Ticket + Bug details + + + + + + + + copyToClipboard("summary", summary)} + /> + } + /> + + {isInitialLoading ? ( + + + + ) : ( + description + )} + + } + button={ + copyToClipboard("description", description)} + /> + } + /> + {isString(traceId) && config.isJaegerEnabled && config.jaegerURL && ( + + } + button={ + + } + errorMessage={errorMessage} + /> + )} + + ); +}; diff --git a/src/components/Insights/JiraTicket/styles.ts b/src/components/Insights/JiraTicket/styles.ts new file mode 100644 index 000000000..a6c84d9eb --- /dev/null +++ b/src/components/Insights/JiraTicket/styles.ts @@ -0,0 +1,49 @@ +import styled from "styled-components"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + border-radius: 7px; + border: 1px solid ${({ theme }) => theme.colors.jiraTicket.border}; + background: ${({ theme }) => theme.colors.jiraTicket.background}; + box-shadow: 0 1px 4px 0 rgb(0 0 0 / 45%); + padding: 12px; + gap: 12px; + font-size: 14px; + width: 100%; + box-sizing: border-box; +`; + +export const Header = styled.div` + display: flex; + gap: 12px; +`; + +export const TitleContainer = styled.div` + display: flex; + flex-direction: column; + color: ${({ theme }) => theme.colors.jiraTicket.text.secondary}; +`; + +export const Title = styled.span` + color: ${({ theme }) => theme.colors.jiraTicket.text.primary}; +`; + +export const CloseButton = styled.button` + background: none; + border: none; + margin: 0; + padding: 0; + display: flex; + cursor: pointer; + margin-left: auto; + height: fit-content; + color: ${({ theme }) => theme.colors.jiraTicket.icon}; +`; + +export const LoaderContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + height: 200px; +`; diff --git a/src/components/Insights/JiraTicket/types.ts b/src/components/Insights/JiraTicket/types.ts new file mode 100644 index 000000000..028266bf5 --- /dev/null +++ b/src/components/Insights/JiraTicket/types.ts @@ -0,0 +1,20 @@ +import { GenericCodeObjectInsight } from "../types"; + +export interface JiraTicketThemeColors { + background: string; + border: string; + text: { + primary: string; + secondary: string; + }; + icon: string; +} + +export interface JiraTicketProps { + insight: GenericCodeObjectInsight; + onClose: () => void; +} + +export interface CodeLocationsData { + codeLocations: []; +} diff --git a/src/components/Insights/NPlusOneInsight/NPlusOneInsight.stories.tsx b/src/components/Insights/NPlusOneInsight/NPlusOneInsight.stories.tsx index 8663b1e8b..af6659c67 100644 --- a/src/components/Insights/NPlusOneInsight/NPlusOneInsight.stories.tsx +++ b/src/components/Insights/NPlusOneInsight/NPlusOneInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const Default: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "N+1", type: InsightType.SpanNPlusOne, category: InsightCategory.Performance, @@ -59,7 +61,10 @@ export const Default: Story = { "span:io.opentelemetry.tomcat-10.0$_$HTTP POST /owners/new", serviceName: "spring-petclinic" }, - occurrences: 100 + occurrences: 100, + criticality: 0, + impact: 0, + severity: 0 } ], scope: InsightScope.Span, diff --git a/src/components/Insights/NPlusOneInsight/index.tsx b/src/components/Insights/NPlusOneInsight/index.tsx index ca3e6dbe0..ea01bf67f 100644 --- a/src/components/Insights/NPlusOneInsight/index.tsx +++ b/src/components/Insights/NPlusOneInsight/index.tsx @@ -1,11 +1,14 @@ import { useContext } from "react"; import { InsightType } from "../../../types"; +import { sendTrackingEvent } from "../../../utils/sendTrackingEvent"; import { trimEndpointScheme } from "../../../utils/trimEndpointScheme"; import { ConfigContext } from "../../common/App/ConfigContext"; import { Tooltip } from "../../common/Tooltip"; +import { JiraLogoIcon } from "../../common/icons/12px/JiraLogoIcon"; import { CrosshairIcon } from "../../common/icons/CrosshairIcon"; import { InsightCard } from "../InsightCard"; import { Description, Link } from "../styles"; +import { trackingEvents } from "../tracking"; import { Trace } from "../types"; import * as s from "./styles"; import { NPlusOneInsightProps } from "./types"; @@ -25,6 +28,13 @@ export const NPlusOneInsight = (props: NPlusOneInsightProps) => { props.onTraceButtonClick(trace, insightType, spanCodeObjectId); }; + const handleCreateJiraTicketButtonClick = () => { + sendTrackingEvent(trackingEvents.JIRA_TICKET_INFO_BUTTON_CLICKED, { + insightType: props.insight.type + }); + props.onJiraTicketCreate && props.onJiraTicketCreate(props.insight); + }; + const spanName = props.insight.clientSpanName || undefined; const spanCodeObjectId = props.insight.clientSpanCodeObjectId || undefined; const traceId = props.insight.traceId; @@ -106,6 +116,15 @@ export const NPlusOneInsight = (props: NPlusOneInsightProps) => { } onRecalculate={props.onRecalculate} onRefresh={props.onRefresh} + buttons={[ + + Ticket Info + + ]} /> ); }; diff --git a/src/components/Insights/NoScalingIssueInsight/NoScalingIssueInsight.stories.tsx b/src/components/Insights/NoScalingIssueInsight/NoScalingIssueInsight.stories.tsx index 73be68acb..b97bde174 100644 --- a/src/components/Insights/NoScalingIssueInsight/NoScalingIssueInsight.stories.tsx +++ b/src/components/Insights/NoScalingIssueInsight/NoScalingIssueInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const Default: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Scaling Well", type: InsightType.SpanScalingWell, category: InsightCategory.Performance, diff --git a/src/components/Insights/PerformanceAtScaleInsight/PerformanceAtScaleInsight.stories.tsx b/src/components/Insights/PerformanceAtScaleInsight/PerformanceAtScaleInsight.stories.tsx index 7e37440dd..de934fc91 100644 --- a/src/components/Insights/PerformanceAtScaleInsight/PerformanceAtScaleInsight.stories.tsx +++ b/src/components/Insights/PerformanceAtScaleInsight/PerformanceAtScaleInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const Default: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Scaling Insufficient Data", type: InsightType.SpanScalingInsufficientData, category: InsightCategory.Performance, diff --git a/src/components/Insights/RequestBreakdownInsight/RequestBreakdownInsight.stories.tsx b/src/components/Insights/RequestBreakdownInsight/RequestBreakdownInsight.stories.tsx index 46f9abefb..126675e20 100644 --- a/src/components/Insights/RequestBreakdownInsight/RequestBreakdownInsight.stories.tsx +++ b/src/components/Insights/RequestBreakdownInsight/RequestBreakdownInsight.stories.tsx @@ -23,6 +23,8 @@ export default meta; type Story = StoryObj; const data: EndpointBreakdownInsight = { + criticality: 0, + impact: 0, name: "Request Breakdown", type: InsightType.EndpointBreakdown, category: InsightCategory.Usage, diff --git a/src/components/Insights/ScalingIssueInsight/ScalingIssueInsight.stories.tsx b/src/components/Insights/ScalingIssueInsight/ScalingIssueInsight.stories.tsx index 416cb337d..773b2dd98 100644 --- a/src/components/Insights/ScalingIssueInsight/ScalingIssueInsight.stories.tsx +++ b/src/components/Insights/ScalingIssueInsight/ScalingIssueInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const Default: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Scaling Issue Found", type: InsightType.SpanScalingBadly, category: InsightCategory.Performance, diff --git a/src/components/Insights/SessionInViewInsight/SessionInViewInsight.stories.tsx b/src/components/Insights/SessionInViewInsight/SessionInViewInsight.stories.tsx index 33a39af52..2303ce3c0 100644 --- a/src/components/Insights/SessionInViewInsight/SessionInViewInsight.stories.tsx +++ b/src/components/Insights/SessionInViewInsight/SessionInViewInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const Default: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Session in View Query", type: InsightType.EndpointSessionInView, category: InsightCategory.Performance, diff --git a/src/components/Insights/SlowEndpointInsight/SlowEndpointInsight.stories.tsx b/src/components/Insights/SlowEndpointInsight/SlowEndpointInsight.stories.tsx index 5008c7874..d8785bce2 100644 --- a/src/components/Insights/SlowEndpointInsight/SlowEndpointInsight.stories.tsx +++ b/src/components/Insights/SlowEndpointInsight/SlowEndpointInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const Default: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Slow Endpoint", type: InsightType.SlowEndpoint, category: InsightCategory.Performance, diff --git a/src/components/Insights/SpanBottleneckInsight/SpanBottleneckInsight.stories.tsx b/src/components/Insights/SpanBottleneckInsight/SpanBottleneckInsight.stories.tsx index a529e7257..daa07563f 100644 --- a/src/components/Insights/SpanBottleneckInsight/SpanBottleneckInsight.stories.tsx +++ b/src/components/Insights/SpanBottleneckInsight/SpanBottleneckInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const Default: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Bottleneck Detected", type: InsightType.SlowestSpans, category: InsightCategory.Performance, diff --git a/src/components/Insights/TopUsageInsight/TopUsageInsight.stories.tsx b/src/components/Insights/TopUsageInsight/TopUsageInsight.stories.tsx index 1d786e063..e6733ac1d 100644 --- a/src/components/Insights/TopUsageInsight/TopUsageInsight.stories.tsx +++ b/src/components/Insights/TopUsageInsight/TopUsageInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const Default: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Top Usage", type: InsightType.SpanUsages, category: InsightCategory.Usage, diff --git a/src/components/Insights/TrafficInsight/TrafficInsight.stories.tsx b/src/components/Insights/TrafficInsight/TrafficInsight.stories.tsx index deb36b0eb..1d0311679 100644 --- a/src/components/Insights/TrafficInsight/TrafficInsight.stories.tsx +++ b/src/components/Insights/TrafficInsight/TrafficInsight.stories.tsx @@ -20,6 +20,8 @@ type Story = StoryObj; export const LowTraffic: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "Low Usage", type: InsightType.LowUsage, category: InsightCategory.Usage, @@ -72,6 +74,8 @@ export const LowTraffic: Story = { export const HighTraffic: Story = { args: { insight: { + criticality: 0, + impact: 0, name: "High Usage", type: InsightType.HighUsage, category: InsightCategory.Usage, diff --git a/src/components/Insights/actions.ts b/src/components/Insights/actions.ts new file mode 100644 index 000000000..e2e34a9d3 --- /dev/null +++ b/src/components/Insights/actions.ts @@ -0,0 +1,24 @@ +import { addPrefix } from "../../utils/addPrefix"; + +const ACTION_PREFIX = "INSIGHTS"; + +export const actions = addPrefix(ACTION_PREFIX, { + INITIALIZE: "INITIALIZE", + GET_DATA: "GET_DATA", + SET_DATA: "SET_DATA", + GO_TO_ERRORS: "GO_TO_ERRORS", + GO_TO_ERROR: "GO_TO_ERROR", + GO_TO_METHOD: "GO_TO_METHOD", + GO_TO_TRACE: "GO_TO_TRACE", + GO_TO_TRACE_COMPARISON: "GO_TO_TRACE_COMPARISON", + GO_TO_ASSET: "GO_TO_ASSET", + OPEN_HISTOGRAM: "OPEN_HISTOGRAM", + OPEN_LIVE_VIEW: "OPEN_LIVE_VIEW", + RECALCULATE: "RECALCULATE", + AUTOFIX_MISSING_DEPENDENCY: "AUTOFIX_MISSING_DEPENDENCY", + ADD_ANNOTATION: "ADD_ANNOTATION", + REFRESH_ALL: "REFRESH_ALL", + MARK_INSIGHT_TYPES_AS_VIEWED: "MARK_INSIGHT_TYPES_AS_VIEWED", + GET_CODE_LOCATIONS: "GET_CODE_LOCATIONS", + SET_CODE_LOCATIONS: "SET_CODE_LOCATIONS" +}); diff --git a/src/components/Insights/common/insights/SpanBottleneckInsight/types.ts b/src/components/Insights/common/insights/SpanBottleneckInsight/types.ts new file mode 100644 index 000000000..91705531a --- /dev/null +++ b/src/components/Insights/common/insights/SpanBottleneckInsight/types.ts @@ -0,0 +1,6 @@ +import { EndpointSlowestSpansInsight, InsightProps } from "../../../types"; + +export interface SpanBottleneckInsightProps extends InsightProps { + insight: EndpointSlowestSpansInsight; + onAssetLinkClick: (spanCodeObjectId: string) => void; +} diff --git a/src/components/Insights/index.tsx b/src/components/Insights/index.tsx index d7da71826..c367a1d14 100644 --- a/src/components/Insights/index.tsx +++ b/src/components/Insights/index.tsx @@ -1,16 +1,18 @@ -import { useEffect, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { actions as globalActions } from "../../actions"; import { SLACK_WORKSPACE_URL } from "../../constants"; import { dispatcher } from "../../dispatcher"; import { usePrevious } from "../../hooks/usePrevious"; import { trackingEvents as globalTrackingEvents } from "../../trackingEvents"; import { isNumber } from "../../typeGuards/isNumber"; -import { addPrefix } from "../../utils/addPrefix"; import { openURLInDefaultBrowser } from "../../utils/openURLInDefaultBrowser"; import { sendTrackingEvent } from "../../utils/sendTrackingEvent"; +import { ConfigContext } from "../common/App/ConfigContext"; import { Button } from "../common/Button"; import { CircleLoader } from "../common/CircleLoader"; import { EmptyState } from "../common/EmptyState"; +import { RegistrationDialog } from "../common/RegistrationDialog"; +import { RegistrationFormValues } from "../common/RegistrationDialog/types"; import { CardsIcon } from "../common/icons/CardsIcon"; import { DocumentWithMagnifierIcon } from "../common/icons/DocumentWithMagnifierIcon"; import { LightBulbSmallCrossedIcon } from "../common/icons/LightBulbSmallCrossedIcon"; @@ -18,9 +20,12 @@ import { LightBulbSmallIcon } from "../common/icons/LightBulbSmallIcon"; import { OpenTelemetryLogoCrossedSmallIcon } from "../common/icons/OpenTelemetryLogoCrossedSmallIcon"; import { SlackLogoIcon } from "../common/icons/SlackLogoIcon"; import { InsightList } from "./InsightList"; +import { JiraTicket } from "./JiraTicket"; import { Preview } from "./Preview"; +import { actions } from "./actions"; import * as s from "./styles"; import { + GenericCodeObjectInsight, InsightsData, InsightsProps, InsightsStatus, @@ -32,27 +37,6 @@ const REFRESH_INTERVAL = isNumber(window.insightsRefreshInterval) ? window.insightsRefreshInterval : 10 * 1000; // in milliseconds -const ACTION_PREFIX = "INSIGHTS"; - -export const actions = addPrefix(ACTION_PREFIX, { - INITIALIZE: "INITIALIZE", - GET_DATA: "GET_DATA", - SET_DATA: "SET_DATA", - GO_TO_ERRORS: "GO_TO_ERRORS", - GO_TO_ERROR: "GO_TO_ERROR", - GO_TO_METHOD: "GO_TO_METHOD", - GO_TO_TRACE: "GO_TO_TRACE", - GO_TO_TRACE_COMPARISON: "GO_TO_TRACE_COMPARISON", - GO_TO_ASSET: "GO_TO_ASSET", - OPEN_HISTOGRAM: "OPEN_HISTOGRAM", - OPEN_LIVE_VIEW: "OPEN_LIVE_VIEW", - RECALCULATE: "RECALCULATE", - AUTOFIX_MISSING_DEPENDENCY: "AUTOFIX_MISSING_DEPENDENCY", - ADD_ANNOTATION: "ADD_ANNOTATION", - REFRESH_ALL: "REFRESH_ALL", - MARK_INSIGHT_TYPES_AS_VIEWED: "MARK_INSIGHT_TYPES_AS_VIEWED" -}); - export const Insights = (props: InsightsProps) => { const [data, setData] = useState(); const previousData = usePrevious(data); @@ -60,6 +44,15 @@ export const Insights = (props: InsightsProps) => { const [isInitialLoading, setIsInitialLoading] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isAutofixing, setIsAutofixing] = useState(false); + const [insightToOpenJiraTicket, setInsightToOpenJiraTicket] = + useState(); + const config = useContext(ConfigContext); + const previousUserRegistrationEmail = usePrevious( + config.userRegistrationEmail + ); + useState(false); + const [isRegistrationInProgress, setIsRegistrationInProgress] = + useState(false); useEffect(() => { window.sendMessageToDigma({ @@ -121,6 +114,19 @@ export const Insights = (props: InsightsProps) => { } }, [previousData, data]); + useEffect(() => { + if ( + previousUserRegistrationEmail !== config.userRegistrationEmail && + isRegistrationInProgress + ) { + setIsRegistrationInProgress(false); + } + }, [ + config.userRegistrationEmail, + isRegistrationInProgress, + previousUserRegistrationEmail + ]); + const handleMethodSelect = (method: Method) => { window.sendMessageToDigma({ action: actions.GO_TO_METHOD, @@ -165,6 +171,30 @@ export const Insights = (props: InsightsProps) => { }); }; + const handleJiraTicketPopupOpen = (insight: GenericCodeObjectInsight) => { + setInsightToOpenJiraTicket(insight); + }; + + const handleJiraTicketPopupClose = () => { + setInsightToOpenJiraTicket(undefined); + }; + + const handleRegistrationSubmit = (formData: RegistrationFormValues) => { + window.sendMessageToDigma({ + action: globalActions.REGISTER, + payload: { + ...formData, + scope: "insights view jira ticket info" + } + }); + + setIsRegistrationInProgress(true); + }; + + const handleRegistrationDialogClose = () => { + setInsightToOpenJiraTicket(undefined); + }; + const renderDefaultContent = (data?: InsightsData): JSX.Element => { if (data?.viewMode === ViewMode.PREVIEW) { return ( @@ -184,6 +214,7 @@ export const Insights = (props: InsightsProps) => { hasMissingDependency={data.hasMissingDependency} canInstrumentMethod={data.canInstrumentMethod} hasObservability={!data.needsObservabilityFix} + onJiraTicketCreate={handleJiraTicketPopupOpen} /> ); } @@ -300,6 +331,24 @@ export const Insights = (props: InsightsProps) => { )} {renderContent(data, isInitialLoading)} + {insightToOpenJiraTicket && ( + + + {config.userRegistrationEmail ? ( + + ) : ( + + )} + + + )} ); }; diff --git a/src/components/Insights/styles.ts b/src/components/Insights/styles.ts index 3b233516d..920420e34 100644 --- a/src/components/Insights/styles.ts +++ b/src/components/Insights/styles.ts @@ -1,4 +1,5 @@ import styled from "styled-components"; +import { LAYERS } from "../common/App/styles"; import { Link as CommonLink } from "../common/Link"; export const Container = styled.div` @@ -107,3 +108,19 @@ export const TroubleshootingLink = styled(Link)` font-size: 14px; text-decoration: underline; `; + +export const Overlay = styled.div` + position: fixed; + inset: 0; + margin: auto; + background: rgb(18 18 21 / 70%); + z-index: ${LAYERS.OVERLAY}; +`; + +export const PopupContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + height: 100%; + padding: 0 4%; +`; diff --git a/src/components/Insights/tracking.ts b/src/components/Insights/tracking.ts new file mode 100644 index 000000000..21667d5bb --- /dev/null +++ b/src/components/Insights/tracking.ts @@ -0,0 +1,15 @@ +import { addPrefix } from "../../utils/addPrefix"; + +const TRACKING_PREFIX = "insights"; + +export const trackingEvents = addPrefix( + TRACKING_PREFIX, + { + JIRA_TICKET_INFO_BUTTON_CLICKED: "jira ticket info button clicked", + JIRA_TICKET_FIELD_COPY_BUTTON_CLICKED: + "jira ticket field copy button clicked", + JIRA_TICKET_ATTACHMENT_DOWNLOAD_BUTTON_CLICKED: + "jira ticket attachment download button clicked" + }, + " " +); diff --git a/src/components/Insights/types.ts b/src/components/Insights/types.ts index c12e7f5b2..85e5cbe40 100644 --- a/src/components/Insights/types.ts +++ b/src/components/Insights/types.ts @@ -81,6 +81,7 @@ export interface InsightProps { insightType: InsightType ) => void; onRefresh: (insightType: InsightType) => void; + onJiraTicketCreate?: (insight: GenericCodeObjectInsight) => void; } export enum InsightScope { @@ -151,6 +152,8 @@ export interface CodeObjectInsight extends Insight { prefixedCodeObjectId: string | null; customStartTime: string | null; actualStartTime: string | null; + criticality: number; + impact: number; } export interface SpanInsight extends CodeObjectInsight { @@ -491,6 +494,9 @@ export interface SpanNPlusOneInsight extends SpanInsight { serviceName: string; }; occurrences: number; + criticality: number; + impact: number; + severity: number; }[]; /** diff --git a/src/components/RecentActivity/EnvironmentPanel/index.tsx b/src/components/RecentActivity/EnvironmentPanel/index.tsx index 9146c9577..cfddb5efe 100644 --- a/src/components/RecentActivity/EnvironmentPanel/index.tsx +++ b/src/components/RecentActivity/EnvironmentPanel/index.tsx @@ -3,9 +3,9 @@ import useDimensions from "react-cool-dimensions"; import { RECENT_ACTIVITY_CONTAINER_ID } from ".."; import { NewButton } from "../../common/NewButton"; import { NewPopover } from "../../common/NewPopover"; +import { PlusIcon } from "../../common/icons/12px/PlusIcon"; import { ChevronIcon } from "../../common/icons/ChevronIcon"; import { DigmaLogoIcon } from "../../common/icons/DigmaLogoIcon"; -import { PlusIcon } from "../../common/icons/PlusIcon"; import { Direction } from "../../common/icons/types"; import { AddEnvironmentDialog } from "../AddEnvironmentDialog"; import { ExtendedEnvironment } from "../types"; diff --git a/src/components/RecentActivity/EnvironmentTypePanel/index.tsx b/src/components/RecentActivity/EnvironmentTypePanel/index.tsx index c5bc08417..a0b37d83c 100644 --- a/src/components/RecentActivity/EnvironmentTypePanel/index.tsx +++ b/src/components/RecentActivity/EnvironmentTypePanel/index.tsx @@ -1,6 +1,7 @@ import { sendTrackingEvent } from "../../../utils/sendTrackingEvent"; +import { IconTag } from "../../common/IconTag"; import { NewButton } from "../../common/NewButton"; -import { CodeIcon } from "../../common/icons/CodeIcon"; +import { CodeIcon } from "../../common/icons/16px/CodeIcon"; import { InfinityIcon } from "../../common/icons/InfinityIcon"; import { trackingEvents } from "../tracking"; import { EnvironmentType } from "../types"; @@ -26,7 +27,7 @@ export const EnvironmentTypePanel = (props: EnvironmentTypePanelProps) => { title: "Local environment", description: "Define an environment for specific branches, types of tests or other criteria", - icon: , + icon: CodeIcon, button: ( handleEnvironmentTypeButtonClick("local")} @@ -41,7 +42,7 @@ export const EnvironmentTypePanel = (props: EnvironmentTypePanelProps) => { title: "CI/Prod environment", description: "Connect to centralized org systems such as CI builds, production servers etc.", - icon: , + icon: InfinityIcon, button: ( handleEnvironmentTypeButtonClick("shared")} @@ -62,9 +63,7 @@ export const EnvironmentTypePanel = (props: EnvironmentTypePanelProps) => { {environmentTypes.map((x) => ( - - {x.icon} - + {x.title} {x.description} diff --git a/src/components/RecentActivity/EnvironmentTypePanel/styles.ts b/src/components/RecentActivity/EnvironmentTypePanel/styles.ts index e9eb79f66..def2e3830 100644 --- a/src/components/RecentActivity/EnvironmentTypePanel/styles.ts +++ b/src/components/RecentActivity/EnvironmentTypePanel/styles.ts @@ -101,37 +101,3 @@ export const EnvironmentTypeTitle = styled.span` }}; font-weight: 500; `; - -export const EnvironmentTypeIconContainer = styled.div` - display: flex; - border-radius: 4px; - padding: 6px; - border: 1px solid - ${({ theme }) => { - switch (theme.mode) { - case "light": - return grayScale[300]; - case "dark": - case "dark-jetbrains": - return grayScale[700]; - } - }}; - background: ${({ theme }) => { - switch (theme.mode) { - case "light": - return grayScale[50]; - case "dark": - case "dark-jetbrains": - return grayScale[1000]; - } - }}; - color: ${({ theme }) => { - switch (theme.mode) { - case "light": - return grayScale[800]; - case "dark": - case "dark-jetbrains": - return grayScale[200]; - } - }}; -`; diff --git a/src/components/RecentActivity/EnvironmentTypePanel/types.ts b/src/components/RecentActivity/EnvironmentTypePanel/types.ts index 6da7c876a..8f8e993ac 100644 --- a/src/components/RecentActivity/EnvironmentTypePanel/types.ts +++ b/src/components/RecentActivity/EnvironmentTypePanel/types.ts @@ -1,4 +1,5 @@ -import { ReactNode } from "react"; +import { ComponentType, ReactNode } from "react"; +import { IconProps } from "../../common/icons/types"; import { EnvironmentType, ExtendedEnvironment } from "../types"; export interface EnvironmentTypePanelProps { @@ -10,6 +11,6 @@ export interface EnvironmentTypeData { type: EnvironmentType; title: string; description: ReactNode; - icon: ReactNode; + icon: ComponentType; button: ReactNode; } diff --git a/src/components/RecentActivity/index.tsx b/src/components/RecentActivity/index.tsx index 62bc4b23a..1c4ab8391 100644 --- a/src/components/RecentActivity/index.tsx +++ b/src/components/RecentActivity/index.tsx @@ -10,6 +10,8 @@ import { groupBy } from "../../utils/groupBy"; import { sendTrackingEvent } from "../../utils/sendTrackingEvent"; import { ConfigContext } from "../common/App/ConfigContext"; import { CursorFollower } from "../common/CursorFollower"; +import { RegistrationDialog } from "../common/RegistrationDialog"; +import { RegistrationFormValues } from "../common/RegistrationDialog/types"; import { DigmaLogoFlatIcon } from "../common/icons/DigmaLogoFlatIcon"; import { ListIcon } from "../common/icons/ListIcon"; import { TableIcon } from "../common/icons/TableIcon"; @@ -22,8 +24,6 @@ import { LiveView } from "./LiveView"; import { LiveData } from "./LiveView/types"; import { ObservabilityStatusBadge } from "./ObservabilityStatusBadge"; import { RecentActivityTable, isRecent } from "./RecentActivityTable"; -import { RegistrationPanel } from "./RegistrationPanel"; -import { RegistrationFormValues } from "./RegistrationPanel/types"; import { SetupOrgDigmaPanel } from "./SetupOrgDigmaPanel"; import { Toggle } from "./Toggle"; import { actions } from "./actions"; @@ -91,7 +91,9 @@ export const RecentActivity = (props: RecentActivityProps) => { const [isRegistrationInProgress, setIsRegistrationInProgress] = useState(false); const config = useContext(ConfigContext); - const previousUserEmail = usePrevious(config.userEmail); + const previousUserRegistrationEmail = usePrevious( + config.userRegistrationEmail + ); const { observe, entry } = useDimensions(); const environmentActivities = useMemo( @@ -163,7 +165,10 @@ export const RecentActivity = (props: RecentActivityProps) => { }, [props.liveData]); useEffect(() => { - if (previousUserEmail !== config.userEmail && isRegistrationInProgress) { + if ( + previousUserRegistrationEmail !== config.userRegistrationEmail && + isRegistrationInProgress + ) { setIsRegistrationPopupVisible(false); setIsRegistrationInProgress(false); @@ -177,10 +182,10 @@ export const RecentActivity = (props: RecentActivityProps) => { } } }, [ - config.userEmail, + config.userRegistrationEmail, isRegistrationInProgress, environmentToSetType, - previousUserEmail + previousUserRegistrationEmail ]); const handleEnvironmentSelect = (environment: ExtendedEnvironment) => { @@ -269,7 +274,7 @@ export const RecentActivity = (props: RecentActivityProps) => { environment: string, type: EnvironmentType ) => { - if (!config.userEmail) { + if (!config.userRegistrationEmail) { setIsRegistrationPopupVisible(true); setEnvironmentToSetType({ environment, @@ -328,9 +333,10 @@ export const RecentActivity = (props: RecentActivityProps) => { const handleRegistrationSubmit = (formData: RegistrationFormValues) => { window.sendMessageToDigma({ - action: actions.REGISTER, + action: globalActions.REGISTER, payload: { ...formData, + scope: "recent activity add environment", selectedEnvironmentType: environmentToSetType?.type } }); @@ -338,7 +344,7 @@ export const RecentActivity = (props: RecentActivityProps) => { setIsRegistrationInProgress(true); }; - const handleRegistrationPopupClose = () => { + const handleRegistrationDialogClose = () => { setIsRegistrationPopupVisible(false); }; @@ -455,9 +461,9 @@ export const RecentActivity = (props: RecentActivityProps) => { )} {isRegistrationPopupVisible && ( - diff --git a/src/components/common/App/ConfigContext.ts b/src/components/common/App/ConfigContext.ts index 335bb9814..2411e830f 100644 --- a/src/components/common/App/ConfigContext.ts +++ b/src/components/common/App/ConfigContext.ts @@ -6,12 +6,16 @@ export const ConfigContext = createContext({ digmaApiUrl: isString(window.digmaApiUrl) ? window.digmaApiUrl : "", digmaStatus: undefined, isObservabilityEnabled: window.isObservabilityEnabled === true, + jaegerURL: isString(window.jaegerURL) ? window.jaegerURL : "", isJaegerEnabled: window.isJaegerEnabled === true, isDigmaEngineInstalled: window.isDigmaEngineInstalled === true, isDigmaEngineRunning: window.isDigmaEngineRunning === true, isDockerInstalled: window.isDockerInstalled === true, isDockerComposeInstalled: window.isDockerComposeInstalled === true, userEmail: isString(window.userEmail) ? window.userEmail : "", + userRegistrationEmail: isString(window.userRegistrationEmail) + ? window.userRegistrationEmail + : "", environment: isString(window.environment) ? window.environment : "", backendInfo: undefined }); diff --git a/src/components/common/App/getTheme.ts b/src/components/common/App/getTheme.ts index c42d2e84f..07a990b54 100644 --- a/src/components/common/App/getTheme.ts +++ b/src/components/common/App/getTheme.ts @@ -164,6 +164,11 @@ const darkThemeColors: ThemeColors = { text: greenScale[300] } }, + iconTag: { + background: grayScale[1000], + border: grayScale[700], + icon: grayScale[200] + }, toggle: { background: grayScale[1000], border: grayScale[850], @@ -206,6 +211,29 @@ const darkThemeColors: ThemeColors = { tooltip: { background: grayScale[800], text: grayScale[100] + }, + attachmentTag: { + background: grayScale[1000], + border: grayScale[850], + icon: { + background: primaryScale[300], + stroke: grayScale[200] + }, + text: grayScale[0] + }, + jiraTicket: { + background: grayScale[1000], + border: grayScale[900], + text: { + primary: grayScale[100], + secondary: grayScale[500] + }, + icon: grayScale[200] + }, + field: { + border: grayScale[700], + icon: grayScale[200], + text: grayScale[100] } }; @@ -317,6 +345,11 @@ const lightThemeColors: ThemeColors = { text: grayScale[0] } }, + iconTag: { + background: grayScale[150], + border: grayScale[0], + icon: grayScale[800] + }, toggle: { background: grayScale[100], border: grayScale[200], @@ -359,6 +392,29 @@ const lightThemeColors: ThemeColors = { tooltip: { background: grayScale[100], text: grayScale[900] + }, + attachmentTag: { + background: grayScale[150], + border: grayScale[0], + icon: { + background: primaryScale[300], + stroke: grayScale[200] + }, + text: grayScale[900] + }, + jiraTicket: { + background: grayScale[50], + border: grayScale[300], + text: { + primary: grayScale[900], + secondary: grayScale[700] + }, + icon: grayScale[800] + }, + field: { + border: grayScale[300], + icon: grayScale[800], + text: grayScale[800] } }; diff --git a/src/components/common/App/index.tsx b/src/components/common/App/index.tsx index 36a20cb88..c71175951 100644 --- a/src/components/common/App/index.tsx +++ b/src/components/common/App/index.tsx @@ -73,6 +73,15 @@ export const App = (props: AppProps) => { } }; + const handleSetJaegerURL = (data: unknown) => { + if (isObject(data) && isString(data.jaegerURL)) { + setConfig((config) => ({ + ...config, + jaegerURL: data.jaegerURL as string + })); + } + }; + const handleSetIsJaegerEnabled = (data: unknown) => { if (isObject(data) && isBoolean(data.isJaegerEnabled)) { setConfig((config) => ({ @@ -136,11 +145,11 @@ export const App = (props: AppProps) => { } }; - const handleSetUserEmail = (data: unknown) => { + const handleSetUserRegistrationEmail = (data: unknown) => { if (isObject(data) && isString(data.email)) { setConfig((config) => ({ ...config, - userEmail: data.email as string + userRegistrationEmail: data.email as string })); } }; @@ -175,6 +184,7 @@ export const App = (props: AppProps) => { dispatcher.addActionListener(actions.SET_THEME, handleSetTheme); dispatcher.addActionListener(actions.SET_MAIN_FONT, handleSetMainFont); dispatcher.addActionListener(actions.SET_CODE_FONT, handleSetCodeFont); + dispatcher.addActionListener(actions.SET_JAEGER_URL, handleSetJaegerURL); dispatcher.addActionListener( actions.SET_IS_JAEGER_ENABLED, handleSetIsJaegerEnabled @@ -203,7 +213,10 @@ export const App = (props: AppProps) => { actions.SET_DIGMA_API_URL, handleSetDigmaApiUrl ); - dispatcher.addActionListener(actions.SET_USER_EMAIL, handleSetUserEmail); + dispatcher.addActionListener( + actions.SET_USER_REGISTRATION_EMAIL, + handleSetUserRegistrationEmail + ); dispatcher.addActionListener(actions.SET_ENVIRONMENT, handleSetEnvironment); dispatcher.addActionListener( actions.SET_IS_OBSERVABILITY_ENABLED, @@ -218,6 +231,10 @@ export const App = (props: AppProps) => { dispatcher.removeActionListener(actions.SET_THEME, handleSetTheme); dispatcher.removeActionListener(actions.SET_MAIN_FONT, handleSetMainFont); dispatcher.removeActionListener(actions.SET_CODE_FONT, handleSetCodeFont); + dispatcher.removeActionListener( + actions.SET_JAEGER_URL, + handleSetJaegerURL + ); dispatcher.removeActionListener( actions.SET_IS_JAEGER_ENABLED, handleSetIsJaegerEnabled @@ -247,8 +264,8 @@ export const App = (props: AppProps) => { handleSetDigmaApiUrl ); dispatcher.removeActionListener( - actions.SET_USER_EMAIL, - handleSetUserEmail + actions.SET_USER_REGISTRATION_EMAIL, + handleSetUserRegistrationEmail ); dispatcher.removeActionListener( actions.SET_ENVIRONMENT, diff --git a/src/components/common/App/types.ts b/src/components/common/App/types.ts index 757d6e5d5..78d3c27c7 100644 --- a/src/components/common/App/types.ts +++ b/src/components/common/App/types.ts @@ -32,12 +32,14 @@ export interface ConfigContextData { digmaApiUrl: string; digmaStatus: DigmaStatus | undefined; isObservabilityEnabled: boolean; + jaegerURL: string; isJaegerEnabled: boolean; isDigmaEngineInstalled: boolean; isDigmaEngineRunning: boolean; isDockerInstalled: boolean; isDockerComposeInstalled: boolean; userEmail: string; + userRegistrationEmail: string; environment: string; backendInfo: BackendInfo | undefined; } diff --git a/src/components/common/IconTag/IconTag.stories.tsx b/src/components/common/IconTag/IconTag.stories.tsx new file mode 100644 index 000000000..20f1d7b6f --- /dev/null +++ b/src/components/common/IconTag/IconTag.stories.tsx @@ -0,0 +1,31 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { IconTag } from "."; +import { CodeIcon } from "../icons/16px/CodeIcon"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "common/IconTag", + component: IconTag, + 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 Small: Story = { + args: { + icon: CodeIcon, + size: "small" + } +}; + +export const Large: Story = { + args: { + icon: CodeIcon, + size: "large" + } +}; diff --git a/src/components/common/IconTag/index.tsx b/src/components/common/IconTag/index.tsx new file mode 100644 index 000000000..a5456a3a5 --- /dev/null +++ b/src/components/common/IconTag/index.tsx @@ -0,0 +1,13 @@ +import * as s from "./styles"; +import { IconTagProps } from "./types"; + +export const IconTag = (props: IconTagProps) => { + const size = props.size || "small"; + const iconSize = size === "large" ? 16 : 12; + + return ( + + + + ); +}; diff --git a/src/components/common/IconTag/styles.ts b/src/components/common/IconTag/styles.ts new file mode 100644 index 000000000..bd0f98675 --- /dev/null +++ b/src/components/common/IconTag/styles.ts @@ -0,0 +1,17 @@ +import styled from "styled-components"; +import { ContainerProps, IconTagSize } from "./types"; + +const getDimensions = (size: IconTagSize) => (size === "large" ? 28 : 20); //in pixels + +export const Container = styled.div` + display: flex; + height: ${({ $size }) => getDimensions($size)}px; + width: ${({ $size }) => getDimensions($size)}px; + justify-content: center; + align-items: center; + flex-shrink: 0; + border-radius: 4px; + border: 1px solid ${({ theme }) => theme.colors.iconTag.border}; + background: ${({ theme }) => theme.colors.iconTag.background}; + color: ${({ theme }) => theme.colors.iconTag.icon}; +`; diff --git a/src/components/common/IconTag/types.ts b/src/components/common/IconTag/types.ts new file mode 100644 index 000000000..725db0ac3 --- /dev/null +++ b/src/components/common/IconTag/types.ts @@ -0,0 +1,19 @@ +import { ComponentType } from "react"; +import { IconProps } from "../icons/types"; + +export type IconTagSize = "small" | "large"; + +export interface IconTagThemeColors { + background: string; + border: string; + icon: string; +} + +export interface IconTagProps { + icon: ComponentType; + size?: IconTagSize; +} + +export interface ContainerProps { + $size: IconTagSize; +} diff --git a/src/components/common/ImpactScore/index.tsx b/src/components/common/ImpactScore/index.tsx index ee76694bb..9919f10e3 100644 --- a/src/components/common/ImpactScore/index.tsx +++ b/src/components/common/ImpactScore/index.tsx @@ -44,7 +44,7 @@ export const ImpactScore = (props: ImpactScoreProps) => { const config = useContext(ConfigContext); let indicatorPosition: "start" | "end" | undefined; - if (props.score >= 0 && props.showIndicator) { + if (props.score > 0 && props.showIndicator) { indicatorPosition = "end"; if (props.indicatorPosition) { diff --git a/src/components/RecentActivity/RegistrationPanel/RegistrationPanel.stories.tsx b/src/components/common/RegistrationDialog/RegistrationDialog.stories.tsx similarity index 75% rename from src/components/RecentActivity/RegistrationPanel/RegistrationPanel.stories.tsx rename to src/components/common/RegistrationDialog/RegistrationDialog.stories.tsx index 7d52bb2b3..29d408e8a 100644 --- a/src/components/RecentActivity/RegistrationPanel/RegistrationPanel.stories.tsx +++ b/src/components/common/RegistrationDialog/RegistrationDialog.stories.tsx @@ -1,10 +1,10 @@ import { Meta, StoryObj } from "@storybook/react"; -import { RegistrationPanel } from "."; +import { RegistrationDialog } from "."; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction -const meta: Meta = { - title: "Recent Activity/RegistrationPanel", - component: RegistrationPanel, +const meta: Meta = { + title: "common/RegistrationDialog", + component: RegistrationDialog, parameters: { // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout layout: "fullscreen" diff --git a/src/components/RecentActivity/RegistrationPanel/TextField/TextField.stories.tsx b/src/components/common/RegistrationDialog/TextField/TextField.stories.tsx similarity index 90% rename from src/components/RecentActivity/RegistrationPanel/TextField/TextField.stories.tsx rename to src/components/common/RegistrationDialog/TextField/TextField.stories.tsx index 8bfe1feca..cec383058 100644 --- a/src/components/RecentActivity/RegistrationPanel/TextField/TextField.stories.tsx +++ b/src/components/common/RegistrationDialog/TextField/TextField.stories.tsx @@ -1,6 +1,6 @@ import { Meta, StoryObj } from "@storybook/react"; import { TextField } from "."; -import { DigmaLogoIcon } from "../../../common/icons/DigmaLogoIcon"; +import { DigmaLogoIcon } from "../../icons/DigmaLogoIcon"; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { diff --git a/src/components/RecentActivity/RegistrationPanel/TextField/index.tsx b/src/components/common/RegistrationDialog/TextField/index.tsx similarity index 90% rename from src/components/RecentActivity/RegistrationPanel/TextField/index.tsx rename to src/components/common/RegistrationDialog/TextField/index.tsx index 021c331e6..e2d325796 100644 --- a/src/components/RecentActivity/RegistrationPanel/TextField/index.tsx +++ b/src/components/common/RegistrationDialog/TextField/index.tsx @@ -1,6 +1,6 @@ import { FocusEvent, ForwardedRef, forwardRef, useState } from "react"; -import { CheckmarkCircleInvertedIcon } from "../../../common/icons/CheckmarkCircleInvertedIcon"; -import { CrossCircleIcon } from "../../../common/icons/CrossCircleIcon"; +import { CheckmarkCircleInvertedIcon } from "../../icons/CheckmarkCircleInvertedIcon"; +import { CrossCircleIcon } from "../../icons/CrossCircleIcon"; import * as s from "./styles"; import { TextFieldProps } from "./types"; diff --git a/src/components/RecentActivity/RegistrationPanel/TextField/styles.ts b/src/components/common/RegistrationDialog/TextField/styles.ts similarity index 98% rename from src/components/RecentActivity/RegistrationPanel/TextField/styles.ts rename to src/components/common/RegistrationDialog/TextField/styles.ts index 042caf549..b95a5f599 100644 --- a/src/components/RecentActivity/RegistrationPanel/TextField/styles.ts +++ b/src/components/common/RegistrationDialog/TextField/styles.ts @@ -4,7 +4,7 @@ import { greenScale, primaryScale, redScale -} from "../../../common/App/getTheme"; +} from "../../App/getTheme"; import { ContainerProps, IconContainerProps, InputProps } from "./types"; export const Container = styled.div` diff --git a/src/components/RecentActivity/RegistrationPanel/TextField/types.ts b/src/components/common/RegistrationDialog/TextField/types.ts similarity index 90% rename from src/components/RecentActivity/RegistrationPanel/TextField/types.ts rename to src/components/common/RegistrationDialog/TextField/types.ts index ac5ecb103..92315c4c0 100644 --- a/src/components/RecentActivity/RegistrationPanel/TextField/types.ts +++ b/src/components/common/RegistrationDialog/TextField/types.ts @@ -1,5 +1,5 @@ import { ChangeEventHandler, FocusEventHandler } from "react"; -import { IconProps } from "../../../common/icons/types"; +import { IconProps } from "../../icons/types"; export interface TextFieldProps { placeholder?: string; diff --git a/src/components/RecentActivity/RegistrationPanel/index.tsx b/src/components/common/RegistrationDialog/index.tsx similarity index 88% rename from src/components/RecentActivity/RegistrationPanel/index.tsx rename to src/components/common/RegistrationDialog/index.tsx index fd5a62e66..bc862d005 100644 --- a/src/components/RecentActivity/RegistrationPanel/index.tsx +++ b/src/components/common/RegistrationDialog/index.tsx @@ -1,14 +1,14 @@ import { KeyboardEvent, useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; import { isValidEmailFormat } from "../../../utils/isValidEmailFormat"; -import { NewCircleLoader } from "../../common/NewCircleLoader"; -import { CrossIcon } from "../../common/icons/CrossIcon"; -import { EnvelopeIcon } from "../../common/icons/EnvelopeIcon"; -import { UserIcon } from "../../common/icons/UserIcon"; +import { NewCircleLoader } from "../NewCircleLoader"; +import { EnvelopeIcon } from "../icons/16px/EnvelopeIcon"; +import { CrossIcon } from "../icons/CrossIcon"; +import { UserIcon } from "../icons/UserIcon"; import { TextField } from "./TextField"; import { isWorkEmail } from "./isWorkEmail"; import * as s from "./styles"; -import { RegistrationFormValues, RegistrationPanelProps } from "./types"; +import { RegistrationDialogProps, RegistrationFormValues } from "./types"; const validateEmail = (email: string): string | boolean => { const emailMessage = "Please enter a valid work email address"; @@ -33,7 +33,7 @@ const formDefaultValues: RegistrationFormValues = { email: "" }; -export const RegistrationPanel = (props: RegistrationPanelProps) => { +export const RegistrationDialog = (props: RegistrationDialogProps) => { const { handleSubmit, control, @@ -78,7 +78,7 @@ export const RegistrationPanel = (props: RegistrationPanelProps) => { - Please register first to create new environments in Digma + Please register with your email address { diff --git a/src/components/RecentActivity/RegistrationPanel/isWorkEmail.ts b/src/components/common/RegistrationDialog/isWorkEmail.ts similarity index 100% rename from src/components/RecentActivity/RegistrationPanel/isWorkEmail.ts rename to src/components/common/RegistrationDialog/isWorkEmail.ts diff --git a/src/components/RecentActivity/RegistrationPanel/styles.ts b/src/components/common/RegistrationDialog/styles.ts similarity index 95% rename from src/components/RecentActivity/RegistrationPanel/styles.ts rename to src/components/common/RegistrationDialog/styles.ts index f6b55bff5..0f7dfb2d0 100644 --- a/src/components/RecentActivity/RegistrationPanel/styles.ts +++ b/src/components/common/RegistrationDialog/styles.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; -import { grayScale, redScale } from "../../common/App/getTheme"; -import { NewButton } from "../../common/NewButton"; +import { grayScale, redScale } from "../App/getTheme"; +import { NewButton } from "../NewButton"; export const Container = styled.div` display: flex; diff --git a/src/components/RecentActivity/RegistrationPanel/types.ts b/src/components/common/RegistrationDialog/types.ts similarity index 82% rename from src/components/RecentActivity/RegistrationPanel/types.ts rename to src/components/common/RegistrationDialog/types.ts index 905c97d89..cafa18618 100644 --- a/src/components/RecentActivity/RegistrationPanel/types.ts +++ b/src/components/common/RegistrationDialog/types.ts @@ -1,4 +1,4 @@ -export interface RegistrationPanelProps { +export interface RegistrationDialogProps { onSubmit: (data: RegistrationFormValues) => void; onClose: () => void; isRegistrationInProgress: boolean; diff --git a/src/components/common/icons/12px/CopyIcon.tsx b/src/components/common/icons/12px/CopyIcon.tsx new file mode 100644 index 000000000..61e4273a3 --- /dev/null +++ b/src/components/common/icons/12px/CopyIcon.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const CopyIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + + + + + + + ); +}; + +export const CopyIcon = React.memo(CopyIconComponent); diff --git a/src/components/common/icons/12px/CrossIcon.tsx b/src/components/common/icons/12px/CrossIcon.tsx new file mode 100644 index 000000000..d58314e38 --- /dev/null +++ b/src/components/common/icons/12px/CrossIcon.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const CrossIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + + + + + + ); +}; + +export const CrossIcon = React.memo(CrossIconComponent); diff --git a/src/components/common/icons/12px/DownloadIcon.tsx b/src/components/common/icons/12px/DownloadIcon.tsx new file mode 100644 index 000000000..7c8472908 --- /dev/null +++ b/src/components/common/icons/12px/DownloadIcon.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const DownloadIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + + + + + + + ); +}; + +export const DownloadIcon = React.memo(DownloadIconComponent); diff --git a/src/components/common/icons/12px/JiraLogoIcon.tsx b/src/components/common/icons/12px/JiraLogoIcon.tsx new file mode 100644 index 000000000..dd73e486a --- /dev/null +++ b/src/components/common/icons/12px/JiraLogoIcon.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const JiraLogoIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const JiraLogoIcon = React.memo(JiraLogoIconComponent); diff --git a/src/components/common/icons/12px/PaperclipIcon.tsx b/src/components/common/icons/12px/PaperclipIcon.tsx new file mode 100644 index 000000000..73aad72e3 --- /dev/null +++ b/src/components/common/icons/12px/PaperclipIcon.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const PaperclipIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + + + + + + ); +}; + +export const PaperclipIcon = React.memo(PaperclipIconComponent); diff --git a/src/components/common/icons/12px/PlusIcon.tsx b/src/components/common/icons/12px/PlusIcon.tsx new file mode 100644 index 000000000..cbc146952 --- /dev/null +++ b/src/components/common/icons/12px/PlusIcon.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const PlusIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + + + + + + ); +}; + +export const PlusIcon = React.memo(PlusIconComponent); diff --git a/src/components/common/icons/16px/CodeIcon.tsx b/src/components/common/icons/16px/CodeIcon.tsx new file mode 100644 index 000000000..d54495168 --- /dev/null +++ b/src/components/common/icons/16px/CodeIcon.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const CodeIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + + + + + + ); +}; + +export const CodeIcon = React.memo(CodeIconComponent); diff --git a/src/components/common/icons/EnvelopeIcon.tsx b/src/components/common/icons/16px/EnvelopeIcon.tsx similarity index 90% rename from src/components/common/icons/EnvelopeIcon.tsx rename to src/components/common/icons/16px/EnvelopeIcon.tsx index e8ddd3c0e..c159dae21 100644 --- a/src/components/common/icons/EnvelopeIcon.tsx +++ b/src/components/common/icons/16px/EnvelopeIcon.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { useIconProps } from "./hooks"; -import { IconProps } from "./types"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; const EnvelopeIconComponent = (props: IconProps) => { const { size, color } = useIconProps(props); diff --git a/src/components/common/icons/16px/InfinityIcon.tsx b/src/components/common/icons/16px/InfinityIcon.tsx new file mode 100644 index 000000000..32a6b6ba6 --- /dev/null +++ b/src/components/common/icons/16px/InfinityIcon.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const CodeIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + + + + + + ); +}; + +export const CodeIcon = React.memo(CodeIconComponent); diff --git a/src/components/common/icons/16px/JiraLogoIcon.tsx b/src/components/common/icons/16px/JiraLogoIcon.tsx new file mode 100644 index 000000000..2022ed899 --- /dev/null +++ b/src/components/common/icons/16px/JiraLogoIcon.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const JiraLogoIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + ); +}; + +export const JiraLogoIcon = React.memo(JiraLogoIconComponent); diff --git a/src/globals.d.ts b/src/globals.d.ts index 4f5cbc559..b307fc97f 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -28,8 +28,10 @@ declare global { ide?: unknown; mainFont?: unknown; codeFont?: unknown; + jaegerURL?: unknown; isJaegerEnabled?: unknown; userEmail?: unknown; + userRegistrationEmail?: unknown; environment?: unknown; isObservabilityEnabled?: unknown; isDigmaEngineInstalled?: unknown; diff --git a/src/styled.d.ts b/src/styled.d.ts index 14433d236..3c32f094e 100644 --- a/src/styled.d.ts +++ b/src/styled.d.ts @@ -1,7 +1,11 @@ import "styled-components"; +import { AttachmentTagThemeColors } from "./components/Insights/JiraTicket/AttachmentTag/types"; +import { FieldThemeColors } from "./components/Insights/JiraTicket/Field/types"; +import { JiraTicketThemeColors } from "./components/Insights/JiraTicket/types"; import { TabThemeColors } from "./components/RecentActivity/EnvironmentPanel/EnvironmentTab/types"; import { ToggleThemeColors } from "./components/RecentActivity/Toggle/types"; import { RecentActivityThemeColors } from "./components/RecentActivity/types"; +import { IconTagThemeColors } from "./components/common/IconTag/types"; import { ButtonThemeColors } from "./components/common/NewButton/types"; import { TagThemeColors } from "./components/common/Tag/types"; import { TooltipThemeColors } from "./components/common/Tooltip/types"; @@ -20,9 +24,13 @@ export interface ThemeColors { background: string; }; tag: TagThemeColors; + iconTag: IconTagThemeColors; toggle: ToggleThemeColors; recentActivity: RecentActivityThemeColors; tooltip: TooltipThemeColors; + attachmentTag: AttachmentTagThemeColors; + jiraTicket: JiraTicketThemeColors; + field: FieldThemeColors; } declare module "styled-components" { From 010721d996b1f2079c0bbc8e59014cab8eecc0e6 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Fri, 15 Dec 2023 16:03:38 +0100 Subject: [PATCH 2/2] Remove redundant action --- src/actions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/actions.ts b/src/actions.ts index 00625e5bf..84abdb4dc 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -25,6 +25,5 @@ export const actions = addPrefix(ACTION_PREFIX, { SET_OBSERVABILITY: "SET_OBSERVABILITY", GET_BACKEND_INFO: "GET_BACKEND_INFO", SET_BACKEND_INFO: "SET_BACKEND_INFO", - DOWNLOAD_TRACE_DATA: "DOWNLOAD_TRACE_DATA", REGISTER: "REGISTER" });