diff --git a/packages/jaeger-ui/index.html b/packages/jaeger-ui/index.html index 1f0be049bb..de638a857d 100644 --- a/packages/jaeger-ui/index.html +++ b/packages/jaeger-ui/index.html @@ -42,6 +42,7 @@ window.apiBaseUrl; window.initialRoutePath; window.embeddedMode; + window.isUserDefinedJaegerQueryURL; window.staticPath; diff --git a/packages/jaeger-ui/src/api/digma/actions.ts b/packages/jaeger-ui/src/api/digma/actions.ts index 46d5f45833..07d4625321 100644 --- a/packages/jaeger-ui/src/api/digma/actions.ts +++ b/packages/jaeger-ui/src/api/digma/actions.ts @@ -1,5 +1,6 @@ export const actions = { - GO_TO_SPAN: "GO_TO_SPAN", - GET_SPANS_WITH_RESOLVED_LOCATION: "GET_SPANS_WITH_RESOLVED_LOCATION", - SET_SPANS_WITH_RESOLVED_LOCATION:"SET_SPANS_WITH_RESOLVED_LOCATION" -} \ No newline at end of file + GO_TO_SPAN: 'GO_TO_SPAN', + GET_SPANS_DATA: 'GET_SPANS_DATA', + SET_SPANS_DATA: 'SET_SPANS_DATA', + CLEAR: 'CLEAR', +}; diff --git a/packages/jaeger-ui/src/api/digma/state.ts b/packages/jaeger-ui/src/api/digma/state.ts index 8f94e5b03d..dc44c6e49a 100644 --- a/packages/jaeger-ui/src/api/digma/state.ts +++ b/packages/jaeger-ui/src/api/digma/state.ts @@ -1,24 +1,27 @@ -import { actions } from "./actions"; -import { SetSpansWithResolvedLocationsData } from "./types"; +import { actions } from './actions'; +import { SetSpansDataPayload } from './types'; export const state: { - pendingOperationsCount: number, - spansWithResolvedLocation: SetSpansWithResolvedLocationsData + pendingOperationsCount: number; + spans: SetSpansDataPayload; } = { pendingOperationsCount: 0, - spansWithResolvedLocation: {} + spans: {}, }; export const updateState = (action: string, payload: any) => { - switch(action) { - case (actions.GET_SPANS_WITH_RESOLVED_LOCATION): + switch (action) { + case actions.GET_SPANS_DATA: state.pendingOperationsCount++; break; - case (actions.SET_SPANS_WITH_RESOLVED_LOCATION): - state.spansWithResolvedLocation = payload; + case actions.SET_SPANS_DATA: + state.spans = payload; + state.pendingOperationsCount--; + break; + case actions.CLEAR: + state.spans = {}; state.pendingOperationsCount--; break; default: } -} - +}; diff --git a/packages/jaeger-ui/src/api/digma/types.ts b/packages/jaeger-ui/src/api/digma/types.ts index 4f411b8a7b..f4f9393b28 100644 --- a/packages/jaeger-ui/src/api/digma/types.ts +++ b/packages/jaeger-ui/src/api/digma/types.ts @@ -1,7 +1,9 @@ +import { InsightType } from '../../components/common/InsightIcon/types'; + export type ActionListener = (data: unknown) => void; export interface IDigmaIncomingMessageData { - type: "digma"; + type: 'digma'; action: string; payload?: unknown; } @@ -13,8 +15,14 @@ export interface IDigmaOutgoingMessageData { export type DigmaMessageEvent = MessageEvent; +export interface ISpanInsight { + type: InsightType; + importance: number; +} + interface ISpanInfo { - importance?: number + hasCodeLocation: boolean; + insights: ISpanInsight[]; } -export type SetSpansWithResolvedLocationsData = Record \ No newline at end of file +export type SetSpansDataPayload = Record; diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.css b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.css index 26deb1a842..c502d6c76b 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.css +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.css @@ -81,9 +81,10 @@ limitations under the License. outline: none; overflow: hidden; padding-left: 4px; - padding-right: 40px; + padding-right: 0.25em; position: relative; text-overflow: ellipsis; + display: flex; } .span-name::before { @@ -109,6 +110,7 @@ limitations under the License. position: absolute; top: 0; width: 1000px; + pointer-events: none; } .span-name:focus { @@ -117,23 +119,37 @@ limitations under the License. .endpoint-name { color: #808080; + overflow: hidden; + text-overflow: ellipsis; + padding-right: 0.25em; } .span-name:hover > .endpoint-name { color: #000; } -.importance-marker { - position: absolute; - top: 5%; - right: 25px; +.span-name .icons-container { + display: flex; + gap: 6px; + margin-left: auto; + align-items: center; } -.code-location-icon { - position: absolute; - height: 70%; - top: 15%; - right: 5px; +.span-name .icon { + height: 20px; + width: 20px; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + border-radius: 4px; + background: #f5f5f5; + border: 1px solid #d3d3d3; +} + +.span-name .code-location-icon { + background: #3538cd; + border: none; } .span-svc-name { diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx index a9a4bc569c..e82f16f18d 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx @@ -17,6 +17,7 @@ import IoAlert from 'react-icons/lib/io/alert'; import IoArrowRightA from 'react-icons/lib/io/arrow-right-a'; import IoNetwork from 'react-icons/lib/io/network'; import MdFileUpload from 'react-icons/lib/md/file-upload'; +import { Tooltip } from 'antd'; import ReferencesButton from './ReferencesButton'; import TimelineRow from './TimelineRow'; import { formatDuration, ViewedBoundsFunctionType } from './utils'; @@ -26,14 +27,14 @@ import Ticks from './Ticks'; import { dispatcher } from '../../../api/digma/dispatcher'; import { actions } from '../../../api/digma/actions'; import { state as globalState } from '../../../api/digma/state'; -import { getStaticAssetPath } from '../../../utils/getStaticAssetPath'; +import { ISpanInsight, SetSpansDataPayload } from '../../../api/digma/types'; +import { LightBulbIcon } from '../../common/icons/LightBulbIcon'; +import { CrosshairIcon } from '../../common/icons/CrosshairIcon'; + import { TNil } from '../../../types'; import { Span } from '../../../types/trace'; -import codeIcon from '../../../img/code.svg'; - import './SpanBarRow.css'; -import { SetSpansWithResolvedLocationsData } from '../../../api/digma/types'; type SpanBarRowProps = { className?: string; @@ -68,11 +69,9 @@ type SpanBarRowProps = { }; type SpanBarRowState = { - hasResolvedLocation: boolean; - importance?: number; -} - -const codeIconUrl = getStaticAssetPath(codeIcon); + hasCodeLocation: boolean; + insights: ISpanInsight[]; +}; /** * This was originally a stateless function, but changing to a PureComponent @@ -85,11 +84,11 @@ const codeIconUrl = getStaticAssetPath(codeIcon); export default class SpanBarRow extends React.PureComponent { constructor(props: SpanBarRowProps) { super(props); - const span = globalState.spansWithResolvedLocation[props.span.spanID]; + const span = globalState.spans[props.span.spanID]; this.state = { - hasResolvedLocation: Boolean(span), - importance: span && span.importance, - } + hasCodeLocation: Boolean(span && span.hasCodeLocation), + insights: span ? span.insights : [], + }; } static defaultProps = { @@ -106,30 +105,19 @@ export default class SpanBarRow extends React.PureComponent { - const span = (data as SetSpansWithResolvedLocationsData)[this.props.span.spanID]; - this.setState({ - hasResolvedLocation: Boolean(span), - importance: span && span.importance - }); - } + const span = (data as SetSpansDataPayload)[this.props.span.spanID]; + this.setState({ + hasCodeLocation: Boolean(span && span.hasCodeLocation), + insights: span ? span.insights : [], + }); + }; componentDidMount(): void { - dispatcher.addActionListener(actions.SET_SPANS_WITH_RESOLVED_LOCATION, this.updateSpanInfo); + dispatcher.addActionListener(actions.SET_SPANS_DATA, this.updateSpanInfo); } componentWillUnmount(): void { - dispatcher.removeActionListener(actions.SET_SPANS_WITH_RESOLVED_LOCATION, this.updateSpanInfo); - } - - getImportanceAltText(importance?: number): string { - switch (importance) { - case 1: - return "Showstopper"; - case 2: - return "Critical"; - default: - return ""; - } + dispatcher.removeActionListener(actions.SET_SPANS_DATA, this.updateSpanInfo); } render() { @@ -171,7 +159,6 @@ export default class SpanBarRow extends React.PureComponent {rpc ? rpc.operationName : operationName} - { - typeof this.state.importance === "number" && [1,2].includes(this.state.importance) && this.state.hasResolvedLocation && - ❗️ - } - {this.state.hasResolvedLocation && Code location available} + + {this.state.insights.length > 0 && ( + + + + + + )} + {this.state.hasCodeLocation && ( + + + + + + )} + {span.references && span.references.length > 1 && ( { constructor(props: SpanDetailProps) { super(props); - const span = globalState.spansWithResolvedLocation[props.span.spanID]; + const span = globalState.spans[props.span.spanID]; this.state = { - hasResolvedLocation: Boolean(span), - importance: span && span.importance, - } + hasCodeLocation: Boolean(span && span.hasCodeLocation), + insights: span ? span.insights : [], + }; } componentDidMount(): void { - dispatcher.addActionListener(actions.SET_SPANS_WITH_RESOLVED_LOCATION, this.updateSpanInfo); + dispatcher.addActionListener(actions.SET_SPANS_DATA, this.updateSpanInfo); } componentWillUnmount(): void { - dispatcher.removeActionListener(actions.SET_SPANS_WITH_RESOLVED_LOCATION, this.updateSpanInfo); + dispatcher.removeActionListener(actions.SET_SPANS_DATA, this.updateSpanInfo); + dispatcher.dispatch(actions.CLEAR); } updateSpanInfo = (data: unknown) => { - const span = (data as SetSpansWithResolvedLocationsData)[this.props.span.spanID]; - this.setState({ - hasResolvedLocation: Boolean(span), - importance: span && span.importance - }); - } - - getImportanceAltText(importance?: number): string { - switch (importance) { - case 1: - return "Showstopper"; - case 2: - return "Critical"; - default: - return ""; - } - } + const span = (data as SetSpansDataPayload)[this.props.span.spanID]; + this.setState({ + hasCodeLocation: Boolean(span && span.hasCodeLocation), + insights: span ? span.insights : [], + }); + }; handleGoToCodeLinkClick = (e: React.MouseEvent) => { e.preventDefault(); - const otelLibraryNameTag = this.props.span.tags.find((tag: any) => tag.key === "otel.library.name"); - const functionTag = this.props.span.tags.find((tag: any) => tag.key === "code.function"); - const namespaceTag = this.props.span.tags.find((tag: any) => tag.key === "code.namespace"); + const otelLibraryNameTag = this.props.span.tags.find((tag: any) => tag.key === 'otel.library.name'); + const functionTag = this.props.span.tags.find((tag: any) => tag.key === 'code.function'); + const namespaceTag = this.props.span.tags.find((tag: any) => tag.key === 'code.namespace'); if (otelLibraryNameTag) { window.sendMessageToDigma({ @@ -103,12 +95,12 @@ export default class SpanDetail extends React.Component -
- {this.state.hasResolvedLocation ? - - {operationName} - :

{operationName}

- } - { - typeof this.state.importance === "number" && [1,2].includes(this.state.importance) && this.state.hasResolvedLocation && - - ❗️ - - } - +
+
+ {this.state.hasCodeLocation ? ( + + {operationName} + + ) : ( +

{operationName}

+ )} +
+ {this.state.insights.map(insight => { + const insightTypeInfo = getInsightTypeInfo(insight.type); + + return insightTypeInfo ? ( + + + + + + ) : null; + })} +
+
+
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.css b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.css index 5c8bab68b7..14964efe41 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.css +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.css @@ -38,4 +38,5 @@ limitations under the License. .TimelineHeaderRow--loading-text { margin: 0 8px; + line-height: 14px; } diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx index 36d5d87f89..a710eb92af 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx @@ -43,30 +43,33 @@ type TimelineHeaderRowProps = { type TimelineHeaderRowState = { isLoading: boolean; -} +}; -export default class SpanDetailRow extends React.PureComponent { +export default class SpanDetailRow extends React.PureComponent< + TimelineHeaderRowProps, + TimelineHeaderRowState +> { constructor(props: TimelineHeaderRowProps) { - super(props) + super(props); this.state = { - isLoading: Boolean(globalState.pendingOperationsCount) - } + isLoading: Boolean(globalState.pendingOperationsCount), + }; } - + componentDidMount(): void { - dispatcher.addActionListener(actions.SET_SPANS_WITH_RESOLVED_LOCATION, this.updateIsLoading); + dispatcher.addActionListener(actions.SET_SPANS_DATA, this.updateIsLoading); } componentWillUnmount(): void { - dispatcher.removeActionListener(actions.SET_SPANS_WITH_RESOLVED_LOCATION, this.updateIsLoading); + dispatcher.removeActionListener(actions.SET_SPANS_DATA, this.updateIsLoading); } updateIsLoading = () => { this.setState({ - isLoading: false + isLoading: false, }); - } - + }; + render() { const { duration, @@ -87,10 +90,12 @@ export default class SpanDetailRow extends React.PureComponent

Service & Operation

- {this.state.isLoading &&
- - Loading data... -
} + {this.state.isLoading && ( +
+ + Loading data... +
+ )} - +
); } -} \ No newline at end of file +} diff --git a/packages/jaeger-ui/src/components/TracePage/index.tsx b/packages/jaeger-ui/src/components/TracePage/index.tsx index bcfe9b2feb..755aa8231c 100644 --- a/packages/jaeger-ui/src/components/TracePage/index.tsx +++ b/packages/jaeger-ui/src/components/TracePage/index.tsx @@ -60,6 +60,7 @@ import TraceSpanView from './TraceSpanView/index'; import TraceFlamegraph from './TraceFlamegraph/index'; import { TraceGraphConfig } from '../../types/config'; import { actions } from '../../api/digma/actions'; +import { dispatcher } from '../../api/digma/dispatcher'; import './index.css'; @@ -191,14 +192,8 @@ export class TracePageImpl extends React.PureComponent { componentDidUpdate({ id: prevID, trace: prevTrace }: TProps) { const { id, trace } = this.props; - if ( - trace && - trace !== prevTrace && - trace.data && - trace.state && - trace.state === fetchedState.DONE - ) { - this.getSpansWithResolvedLocations(trace.data) + if (trace && trace !== prevTrace && trace.data && trace.state && trace.state === fetchedState.DONE) { + this.getSpansWithResolvedLocations(trace.data); } this._scrollManager.setTrace(trace && trace.data); @@ -222,27 +217,30 @@ export class TracePageImpl extends React.PureComponent { scrollBy, scrollTo, }); + dispatcher.dispatch(actions.CLEAR); } getSpansWithResolvedLocations(trace: Trace) { // Get all the trace spans and send it Digma IDE plugin - // to verify if they have resolved location window.sendMessageToDigma({ - action: actions.GET_SPANS_WITH_RESOLVED_LOCATION, + action: actions.GET_SPANS_DATA, payload: { - spans: trace.spans.map(span => { - const otelLibraryNameTag = span.tags.find(tag => tag.key === "otel.library.name"); - const functionTag = span.tags.find(tag => tag.key === "code.function"); - const namespaceTag = span.tags.find(tag => tag.key === "code.namespace"); - - return { - id: span.spanID, - name: span.operationName, - instrumentationLibrary: otelLibraryNameTag && otelLibraryNameTag.value, - ...(functionTag ? {function: functionTag.value} : {}), - ...(namespaceTag ? {namespace: namespaceTag.value} : {}), - }}).filter(span => span.instrumentationLibrary) - } + spans: trace.spans + .map(span => { + const otelLibraryNameTag = span.tags.find(tag => tag.key === 'otel.library.name'); + const functionTag = span.tags.find(tag => tag.key === 'code.function'); + const namespaceTag = span.tags.find(tag => tag.key === 'code.namespace'); + + return { + id: span.spanID, + name: span.operationName, + instrumentationLibrary: otelLibraryNameTag && otelLibraryNameTag.value, + ...(functionTag ? { function: functionTag.value } : {}), + ...(namespaceTag ? { namespace: namespaceTag.value } : {}), + }; + }) + .filter(span => span.instrumentationLibrary), + }, }); } diff --git a/packages/jaeger-ui/src/components/common/ErrorMessage.css b/packages/jaeger-ui/src/components/common/ErrorMessage.css index 9aac574aff..df9aaea832 100644 --- a/packages/jaeger-ui/src/components/common/ErrorMessage.css +++ b/packages/jaeger-ui/src/components/common/ErrorMessage.css @@ -62,3 +62,62 @@ limitations under the License. padding: 0.5em; white-space: pre; } + +.CustomErrorMessage { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex-grow: 1; +} + +.CustomErrorMessage--iconContainer { + width: 72px; + height: 72px; + border-radius: 50%; + background: #f5f5f5; + display: flex; + align-items: center; + justify-content: center; +} + +.CustomErrorMessage--text { + display: flex; + flex-direction: column; + font-weight: 500; + align-items: center; + margin: 12px 0 14px; +} + +.CustomErrorMessage--title { + font-size: 14px; + line-height: 17px; + color: #49494d; + margin-bottom: 4px; +} + +.CustomErrorMessage--description { + font-size: 12px; + line-height: 14px; + color: #828797; + text-align: center; +} + +.CustomErrorMessage--link { + font-weight: 400; + font-size: 12px; + line-height: 14px; + text-decoration: underline; + color: #426dda; +} + +.CustomErrorMessage--slackLink { + display: flex; + gap: 4px; + font-weight: 400; + font-size: 12px; + line-height: 14px; + text-transform: capitalize; + text-decoration: underline; + color: #426dda; +} diff --git a/packages/jaeger-ui/src/components/common/ErrorMessage.tsx b/packages/jaeger-ui/src/components/common/ErrorMessage.tsx index e48a72bca7..59b5e38e56 100644 --- a/packages/jaeger-ui/src/components/common/ErrorMessage.tsx +++ b/packages/jaeger-ui/src/components/common/ErrorMessage.tsx @@ -17,6 +17,13 @@ import * as React from 'react'; import { ApiError } from '../../types/api-error'; import './ErrorMessage.css'; +import { CrossedCrosshairCircleIcon } from './icons/CrossedCrosshairCircleIcon'; +import { SlackLogoIcon } from './icons/SlackLogoIcon'; +import { BrokenLinkCircleIcon } from './icons/BrokenLinkCircleIcon'; +import { isString } from '../../utils/ts/typeGuards/isString'; + +const SLACK_CHANNEL_URL = + 'https://join.slack.com/t/continuous-feedback/shared_invite/zt-1hk5rbjow-yXOIxyyYOLSXpCZ4RXstgA'; type ErrorMessageProps = { className?: string; @@ -32,6 +39,12 @@ type SubPartProps = { wrapperClassName?: string; }; +type DigmaErrorMessageProps = { + icon: React.ReactNode; + title: string; + content: React.ReactNode; +}; + function ErrorAttr({ name, value }: { name: string; value: any }) { return ( @@ -95,6 +108,25 @@ Details.defaultProps = { wrapperClassName: undefined, }; +export const DigmaErrorMessage = (props: DigmaErrorMessageProps) => ( +
+ {props.icon} +
+ {props.title} + {props.content} +
+ + + Join our slack channel for support + +
+); + export default function ErrorMessage({ className, detailClassName, @@ -107,6 +139,75 @@ export default function ErrorMessage({ if (typeof error === 'string') { return ; } + + if (error.message.includes('trace not found')) { + return ( + } + title="We Cannot Find the Trace You're Looking For..." + content={ + <> + Our bad, the trace might be old or we may have simply missed it somehow. +
+ No need to worry! Please run some more actions and check again +
+
+ If you're using your your own Jaeger instance, please check that Digma knows to send traces + to it as well. +
+ Check the "Jaeger Query URL" parameter in the Digma plugin settings and make sure it + matches your Jaeger address + + } + /> + ); + } + + if (error.message.includes('Failed to fetch')) { + const isUserDefinedJaegerQueryURL = window.isUserDefinedJaegerQueryURL === true; + return ( + } + title="Jaeger Not Available" + content={ + isUserDefinedJaegerQueryURL ? ( + <> + The Jaeger link + {isString(window.apiBaseUrl) && ( + <> + {' '} + + {window.apiBaseUrl} + + + )}{' '} + is not available. +
+ Please makes sure the link you specified in the +
+ Digma plugin settings is correct. + + ) : ( + <> + Something is wrong and we are unable to communicate +
+ with the Digma Jaeger instance. +
+ Please make sure Digma is fully up and running +
+ and try updating to the latest version. + + ) + } + /> + ); + } + return (
diff --git a/packages/jaeger-ui/src/components/common/InsightIcon/index.tsx b/packages/jaeger-ui/src/components/common/InsightIcon/index.tsx new file mode 100644 index 0000000000..58326954b7 --- /dev/null +++ b/packages/jaeger-ui/src/components/common/InsightIcon/index.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import { IInsightIconProps } from './types'; +import { getInsightImportanceColor, getInsightTypeInfo } from './utils'; + +export const InsightIcon = (props: IInsightIconProps) => { + const insightTypeInfo = getInsightTypeInfo(props.insight.type); + return insightTypeInfo ? ( + + ) : null; +}; diff --git a/packages/jaeger-ui/src/components/common/InsightIcon/types.ts b/packages/jaeger-ui/src/components/common/InsightIcon/types.ts new file mode 100644 index 0000000000..d153e213fc --- /dev/null +++ b/packages/jaeger-ui/src/components/common/InsightIcon/types.ts @@ -0,0 +1,28 @@ +export interface IInsightIconProps { + insight: { + type: InsightType; + importance: number; + }; + size?: number; +} + +export enum InsightType { + TopErrorFlows = 'TopErrorFlows', + SpanDurationChange = 'SpanDurationChange', + SpanUsageStatus = 'SpanUsageStatus', + HotSpot = 'HotSpot', + Errors = 'Errors', + SlowEndpoint = 'SlowEndpoint', + LowUsage = 'LowUsage', + NormalUsage = 'NormalUsage', + HighUsage = 'HighUsage', + SlowestSpans = 'SlowestSpans', + EndpointSpanNPlusOne = 'EndpointSpaNPlusOne', + SpanUsages = 'SpanUsages', + SpanNPlusOne = 'SpaNPlusOne', + SpanEndpointBottleneck = 'SpanEndpointBottleneck', + SpanDurations = 'SpanDurations', + SpanScaling = 'SpanScaling', + SpanScalingRootCause = 'SpanScalingRootCause', + SpanDurationBreakdown = 'SpanDurationBreakdown', +} diff --git a/packages/jaeger-ui/src/components/common/InsightIcon/utils.ts b/packages/jaeger-ui/src/components/common/InsightIcon/utils.ts new file mode 100644 index 0000000000..a4ec2b86aa --- /dev/null +++ b/packages/jaeger-ui/src/components/common/InsightIcon/utils.ts @@ -0,0 +1,111 @@ +import * as React from 'react'; +import { AlarmClockIcon } from '../icons/AlarmClockIcon'; +import { BottleneckIcon } from '../icons/BottleneckIcon'; +import { MeterHighIcon } from '../icons/MeterHighIcon'; +import { MeterLowIcon } from '../icons/MeterLowIcon'; +import { MeterMediumIcon } from '../icons/MeterMediumIcon'; +import { SQLDatabaseIcon } from '../icons/SQLDatabaseIcon'; +import { ScalesIcon } from '../icons/ScalesIcon'; +import { SineIcon } from '../icons/SineIcon'; +import { SnailIcon } from '../icons/SnailIcon'; +import { SpotIcon } from '../icons/SpotIcon'; +import { WarningCircleIcon } from '../icons/WarningCircleIcon'; +import { IIconProps } from '../icons/types'; +import { InsightType } from './types'; + +export const getInsightTypeInfo = ( + type: InsightType +): + | { + icon: React.MemoExoticComponent<(props: IIconProps) => React.ReactElement>; + label: string; + } + | undefined => { + const insightInfoMap: Record< + string, + { + icon: React.MemoExoticComponent<(props: IIconProps) => React.ReactElement>; + label: string; + } + > = { + [InsightType.Errors]: { + icon: WarningCircleIcon, + label: 'Errors', + }, + [InsightType.HotSpot]: { + icon: SpotIcon, + label: 'Error Hotspot', + }, + [InsightType.SlowEndpoint]: { + icon: SnailIcon, + label: 'Slow Endpoint', + }, + [InsightType.LowUsage]: { + icon: MeterLowIcon, + label: 'Endpoint Low Traffic', + }, + [InsightType.NormalUsage]: { + icon: MeterMediumIcon, + label: 'Endpoint Normal Level of Traffic', + }, + [InsightType.HighUsage]: { + icon: MeterHighIcon, + label: 'Endpoint High Traffic', + }, + [InsightType.SlowestSpans]: { + icon: BottleneckIcon, + label: 'Span Bottleneck', + }, + [InsightType.EndpointSpanNPlusOne]: { + icon: SQLDatabaseIcon, + label: 'Suspected N-Plus-1', + }, + [InsightType.SpanNPlusOne]: { + icon: SQLDatabaseIcon, + label: 'Suspected N-Plus-1', + }, + [InsightType.SpanEndpointBottleneck]: { + icon: BottleneckIcon, + label: 'Bottleneck', + }, + [InsightType.SpanScaling]: { + icon: ScalesIcon, + label: 'Scaling Issue Found', + }, + [InsightType.SpanScalingRootCause]: { + icon: ScalesIcon, + label: 'Scaling Issue Root Cause Found', + }, + [InsightType.SpanUsages]: { + icon: SineIcon, + label: 'Top Usage', + }, + [InsightType.SpanDurations]: { + icon: AlarmClockIcon, + label: 'Duration', + }, + [InsightType.SpanDurationBreakdown]: { + icon: AlarmClockIcon, + label: 'Duration Breakdown', + }, + }; + + return insightInfoMap[type]; +}; + +export const getInsightImportanceColor = (importance: number): string | undefined => { + if (importance === 0) { + return undefined; + } + if (importance < 3) { + return '#f93967'; + } + if (importance < 5) { + return '#e06c00'; + } + if (importance < 7) { + return '#e8b500'; + } + + return '#1dc693'; +}; diff --git a/packages/jaeger-ui/src/components/common/icons/AlarmClockIcon.tsx b/packages/jaeger-ui/src/components/common/icons/AlarmClockIcon.tsx new file mode 100644 index 0000000000..fb49cbfd9b --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/AlarmClockIcon.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const AlarmClockIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const AlarmClockIcon = React.memo(AlarmClockIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/BottleneckIcon.tsx b/packages/jaeger-ui/src/components/common/icons/BottleneckIcon.tsx new file mode 100644 index 0000000000..3b41f28ce4 --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/BottleneckIcon.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const BottleneckIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const BottleneckIcon = React.memo(BottleneckIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/BrokenLinkCircleIcon.tsx b/packages/jaeger-ui/src/components/common/icons/BrokenLinkCircleIcon.tsx new file mode 100644 index 0000000000..c3f0a3f627 --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/BrokenLinkCircleIcon.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const BrokenLinkCircleIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + ); +}; + +export const BrokenLinkCircleIcon = React.memo(BrokenLinkCircleIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/CrossedCrosshairCircleIcon.tsx b/packages/jaeger-ui/src/components/common/icons/CrossedCrosshairCircleIcon.tsx new file mode 100644 index 0000000000..c23dd3011e --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/CrossedCrosshairCircleIcon.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const CrossedCrosshairCircleIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + ); +}; + +export const CrossedCrosshairCircleIcon = React.memo(CrossedCrosshairCircleIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/CrosshairIcon.tsx b/packages/jaeger-ui/src/components/common/icons/CrosshairIcon.tsx new file mode 100644 index 0000000000..c7a6839910 --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/CrosshairIcon.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const CrosshairIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const CrosshairIcon = React.memo(CrosshairIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/LightBulbIcon.tsx b/packages/jaeger-ui/src/components/common/icons/LightBulbIcon.tsx new file mode 100644 index 0000000000..4bbb889beb --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/LightBulbIcon.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const LightBulbIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + ); +}; + +export const LightBulbIcon = React.memo(LightBulbIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/MeterHighIcon.tsx b/packages/jaeger-ui/src/components/common/icons/MeterHighIcon.tsx new file mode 100644 index 0000000000..5e90f7503e --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/MeterHighIcon.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const MeterHighIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + ); +}; + +export const MeterHighIcon = React.memo(MeterHighIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/MeterLowIcon.tsx b/packages/jaeger-ui/src/components/common/icons/MeterLowIcon.tsx new file mode 100644 index 0000000000..a54d05f59f --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/MeterLowIcon.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const MeterLowIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + ); +}; + +export const MeterLowIcon = React.memo(MeterLowIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/MeterMediumIcon.tsx b/packages/jaeger-ui/src/components/common/icons/MeterMediumIcon.tsx new file mode 100644 index 0000000000..7b834d1f9e --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/MeterMediumIcon.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const MeterMediumIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + ); +}; + +export const MeterMediumIcon = React.memo(MeterMediumIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/SQLDatabaseIcon.tsx b/packages/jaeger-ui/src/components/common/icons/SQLDatabaseIcon.tsx new file mode 100644 index 0000000000..de362b7ccf --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/SQLDatabaseIcon.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const SQLDatabaseIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const SQLDatabaseIcon = React.memo(SQLDatabaseIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/ScalesIcon.tsx b/packages/jaeger-ui/src/components/common/icons/ScalesIcon.tsx new file mode 100644 index 0000000000..2dc643798e --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/ScalesIcon.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const ScalesIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const ScalesIcon = React.memo(ScalesIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/SineIcon.tsx b/packages/jaeger-ui/src/components/common/icons/SineIcon.tsx new file mode 100644 index 0000000000..03a3c7a4cb --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/SineIcon.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const SineIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const SineIcon = React.memo(SineIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/SlackLogoIcon.tsx b/packages/jaeger-ui/src/components/common/icons/SlackLogoIcon.tsx new file mode 100644 index 0000000000..0b98b3b60c --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/SlackLogoIcon.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const SlackLogoIconComponent = (props: IIconProps) => { + const { size } = useIconProps(props); + + return ( + + + + + + + + + + + + + + ); +}; + +export const SlackLogoIcon = React.memo(SlackLogoIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/SnailIcon.tsx b/packages/jaeger-ui/src/components/common/icons/SnailIcon.tsx new file mode 100644 index 0000000000..39f372f032 --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/SnailIcon.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const SnailIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + ); +}; + +export const SnailIcon = React.memo(SnailIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/SpotIcon.tsx b/packages/jaeger-ui/src/components/common/icons/SpotIcon.tsx new file mode 100644 index 0000000000..201e7e0d5a --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/SpotIcon.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const SpotIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const SpotIcon = React.memo(SpotIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/WarningCircleIcon.tsx b/packages/jaeger-ui/src/components/common/icons/WarningCircleIcon.tsx new file mode 100644 index 0000000000..3f75c2a1ca --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/WarningCircleIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { useIconProps } from './hooks'; +import { IIconProps } from './types'; + +const WarningCircleIconComponent = (props: IIconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const WarningCircleIcon = React.memo(WarningCircleIconComponent); diff --git a/packages/jaeger-ui/src/components/common/icons/hooks.ts b/packages/jaeger-ui/src/components/common/icons/hooks.ts new file mode 100644 index 0000000000..321bbcb8ac --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/hooks.ts @@ -0,0 +1,11 @@ +import { useMemo } from 'react'; +import { IIconProps } from './types'; + +const DEFAULT_ICON_COLOR = '#828797'; +export const DEFAULT_ICON_SIZE = 12; + +export const useIconProps = (props: IIconProps): IIconProps => { + const color: string = useMemo(() => props.color || DEFAULT_ICON_COLOR, [props.color]); + const size: number = useMemo(() => props.size || DEFAULT_ICON_SIZE, [props.size]); + return { color, size }; +}; diff --git a/packages/jaeger-ui/src/components/common/icons/types.ts b/packages/jaeger-ui/src/components/common/icons/types.ts new file mode 100644 index 0000000000..0d3c127b02 --- /dev/null +++ b/packages/jaeger-ui/src/components/common/icons/types.ts @@ -0,0 +1,5 @@ +export interface IIconProps { + color?: string; + size?: number; + className?: string; +} diff --git a/packages/jaeger-ui/typings/custom.d.ts b/packages/jaeger-ui/typings/custom.d.ts index 292a3ff98b..17340d95cf 100644 --- a/packages/jaeger-ui/typings/custom.d.ts +++ b/packages/jaeger-ui/typings/custom.d.ts @@ -37,6 +37,7 @@ declare interface Window { apiBaseUrl?: unknown; initialRoutePath?: unknown; embeddedMode?: unknown; + isUserDefinedJaegerQueryURL?: unknown; staticPath?: unknown; }