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..84abdb4dc 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,11 @@ 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",
+ 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" {