diff --git a/packages/jaeger-ui/public/index.html b/packages/jaeger-ui/public/index.html
index 9a0f706b81..979216fda0 100644
--- a/packages/jaeger-ui/public/index.html
+++ b/packages/jaeger-ui/public/index.html
@@ -35,6 +35,26 @@
return JAEGER_VERSION;
}
+
diff --git a/packages/jaeger-ui/src/api/jaeger.js b/packages/jaeger-ui/src/api/jaeger.js
index 0283e4179e..eb42982308 100644
--- a/packages/jaeger-ui/src/api/jaeger.js
+++ b/packages/jaeger-ui/src/api/jaeger.js
@@ -76,7 +76,7 @@ function getJSON(url, options = {}) {
});
}
-export const DEFAULT_API_ROOT = prefixUrl('/api/');
+export const DEFAULT_API_ROOT = window.VS_CODE_SETTINGS.apiBaseUrl ? `${window.VS_CODE_SETTINGS.apiBaseUrl}/api/` : prefixUrl('/api/');
export const ANALYTICS_ROOT = prefixUrl('/analytics/');
export const DEFAULT_DEPENDENCY_LOOKBACK = moment.duration(1, 'weeks').asMilliseconds();
diff --git a/packages/jaeger-ui/src/components/App/index.js b/packages/jaeger-ui/src/components/App/index.js
index 32da62c2fa..9fecf249c3 100644
--- a/packages/jaeger-ui/src/components/App/index.js
+++ b/packages/jaeger-ui/src/components/App/index.js
@@ -54,6 +54,13 @@ export default class JaegerUIApp extends Component {
}
render() {
+ // Navigate to URL provided by VS Code
+ if (window.VS_CODE_SETTINGS.startPath) {
+ const urlToNavigate = window.VS_CODE_SETTINGS.startPath;
+ window.VS_CODE_SETTINGS.startPath = "";
+ history.push(urlToNavigate);
+ }
+
return (
diff --git a/packages/jaeger-ui/src/components/Monitor/EmptyState/index.tsx b/packages/jaeger-ui/src/components/Monitor/EmptyState/index.tsx
index 46b6e81a0e..adc6af1087 100644
--- a/packages/jaeger-ui/src/components/Monitor/EmptyState/index.tsx
+++ b/packages/jaeger-ui/src/components/Monitor/EmptyState/index.tsx
@@ -29,6 +29,8 @@ export default class MonitorATMEmptyState extends React.PureComponent {
}
render() {
+ const monitorImgUrl = window.VS_CODE_SETTINGS.staticPath ? new URL(monitorImg, window.VS_CODE_SETTINGS.staticPath).href : monitorImg;
+
return (
@@ -36,7 +38,7 @@ export default class MonitorATMEmptyState extends React.PureComponent {
diff --git a/packages/jaeger-ui/src/components/SearchTracePage/index.js b/packages/jaeger-ui/src/components/SearchTracePage/index.js
index df2c1811e5..cb34f1b90a 100644
--- a/packages/jaeger-ui/src/components/SearchTracePage/index.js
+++ b/packages/jaeger-ui/src/components/SearchTracePage/index.js
@@ -41,6 +41,29 @@ import JaegerLogo from '../../img/jaeger-logo.svg';
const TabPane = Tabs.TabPane;
+// Sanitize query params to filter out ones provided by VS Code
+const sanitizeQueryParams = (params) => {
+ const VS_CODE_PARAMS = [
+ "id",
+ "origin",
+ "swVersion",
+ "extensionId",
+ "platform",
+ "vscode-resource-base-authority",
+ "parentOrigin"
+ ];
+
+ const filteredParams = {};
+
+ Object.keys(params).forEach((key) => {
+ if (!VS_CODE_PARAMS.includes(key)) {
+ filteredParams[key] = params[key]
+ }
+ })
+
+ return filteredParams;
+};
+
// export for tests
export class SearchTracePageImpl extends Component {
componentDidMount() {
@@ -97,6 +120,7 @@ export class SearchTracePageImpl extends Component {
const hasTraceResults = traceResults && traceResults.length > 0;
const showErrors = errors && !loadingTraces;
const showLogo = isHomepage && !hasTraceResults && !loadingTraces && !errors;
+ const logoUrl = window.VS_CODE_SETTINGS.staticPath ? new URL(JaegerLogo, window.VS_CODE_SETTINGS.staticPath).href : JaegerLogo;
return (
{!embedded && (
@@ -147,7 +171,7 @@ export class SearchTracePageImpl extends Component {
)}
@@ -241,6 +265,7 @@ const stateServicesXformer = memoizeOne(stateServices => {
export function mapStateToProps(state) {
const { embedded, router, services: stServices, traceDiff } = state;
const query = getUrlState(router.location.search);
+ const sanitizedQuery = sanitizeQueryParams(query);
const isHomepage = !Object.keys(query).length;
const { query: queryOfResults, traces, maxDuration, traceError, loadingTraces } = stateTraceXformer(
state.trace
@@ -268,7 +293,7 @@ export function mapStateToProps(state) {
errors: errors.length ? errors : null,
maxTraceDuration: maxDuration,
sortTracesBy: sortBy,
- urlQueryParams: Object.keys(query).length > 0 ? query : null,
+ urlQueryParams: Object.keys(sanitizedQuery).length > 0 ? sanitizedQuery : null,
};
}
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.css b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.css
index 0fef447ef0..d9fecb971b 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.css
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.css
@@ -81,7 +81,7 @@ limitations under the License.
outline: none;
overflow: hidden;
padding-left: 4px;
- padding-right: 0.25em;
+ padding-right: 40px;
position: relative;
text-overflow: ellipsis;
}
@@ -123,6 +123,20 @@ limitations under the License.
color: #000;
}
+.importance-icon {
+ position: absolute;
+ height: 60%;
+ top: 20%;
+ right: 25px;
+}
+
+.code-location-icon {
+ position: absolute;
+ height: 70%;
+ top: 15%;
+ right: 5px;
+}
+
.span-svc-name {
padding: 0 0.25rem 0 0.5rem;
font-size: 1.05em;
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx
index 199fd0abf3..f666e9fb6d 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx
@@ -27,8 +27,16 @@ import Ticks from './Ticks';
import { TNil } from '../../../types';
import { Span } from '../../../types/trace';
+import codeIcon from '../../../img/code.svg';
+import exclamationMarkIcon from '../../../img/exclamation-mark.svg';
+
import './SpanBarRow.css';
+type SpanInfo = {
+ hasResolvedLocation: boolean,
+ importance?: number
+}
+
type SpanBarRowProps = {
className?: string;
color: string;
@@ -61,6 +69,11 @@ type SpanBarRowProps = {
focusSpan: (spanID: string) => void;
};
+type SpanBarRowState = {
+ hasResolvedLocation: boolean;
+ importance?: number;
+}
+
/**
* This was originally a stateless function, but changing to a PureComponent
* reduced the render time of expanding a span row detail by ~50%. This is
@@ -69,7 +82,16 @@ type SpanBarRowProps = {
* handlers to the onClick props. E.g. for now, the PureComponent is more
* performance than the stateless function.
*/
-export default class SpanBarRow extends React.PureComponent {
+export default class SpanBarRow extends React.PureComponent {
+ constructor(props: SpanBarRowProps) {
+ super(props);
+ const span = window.spansWithResolvedLocation[props.span.spanID]
+ this.state = {
+ hasResolvedLocation: Boolean(span) && span.hasResolvedLocation,
+ importance: span && span.importance,
+ }
+ }
+
static defaultProps = {
className: '',
rpc: null,
@@ -83,6 +105,36 @@ export default class SpanBarRow extends React.PureComponent {
this.props.onChildrenToggled(this.props.span.spanID);
};
+ updateResolvedLocation = (e:{ data: { command: string, data: Record }}) => {
+ const message = e.data;
+ if (message.command === "setSpansWithResolvedLocation") {
+ const span = message.data[this.props.span.spanID];
+ this.setState({
+ hasResolvedLocation: span.hasResolvedLocation,
+ importance: span.importance
+ });
+ }
+ }
+
+ componentDidMount(): void {
+ window.addEventListener('message', this.updateResolvedLocation);
+ }
+
+ componentWillUnmount(): void {
+ window.removeEventListener('message', this.updateResolvedLocation);
+ }
+
+ getImportanceAltText(importance?: number): string {
+ switch (importance) {
+ case 1:
+ return "Showstopper";
+ case 2:
+ return "Critical";
+ default:
+ return "";
+ }
+ }
+
render() {
const {
className,
@@ -122,6 +174,9 @@ export default class SpanBarRow extends React.PureComponent {
hintSide = 'right';
}
+ const codeIconUrl = window.VS_CODE_SETTINGS.staticPath ? new URL(codeIcon, window.VS_CODE_SETTINGS.staticPath).href : codeIcon;
+ const exclamationMarkIconUrl = window.VS_CODE_SETTINGS.staticPath ? new URL(exclamationMarkIcon, window.VS_CODE_SETTINGS.staticPath).href : exclamationMarkIcon;
+
return (
{
)}
{rpc ? rpc.operationName : operationName}
+ {
+ typeof this.state.importance === "number" && [1,2].includes(this.state.importance) && this.state.hasResolvedLocation &&
+
+ }
+ {this.state.hasResolvedLocation &&
}
{span.references && span.references.length > 1 && (
void;
};
-export default function SpanDetail(props: SpanDetailProps) {
- const {
- detailState,
- linksGetter,
- logItemToggle,
- logsToggle,
- processToggle,
- span,
- tagsToggle,
- traceStartTime,
- warningsToggle,
- referencesToggle,
- focusSpan,
- } = props;
- const { isTagsOpen, isProcessOpen, logs: logsState, isWarningsOpen, isReferencesOpen } = detailState;
- const {
- operationName,
- process,
- duration,
- relativeStartTime,
- spanID,
- logs,
- tags,
- warnings,
- references,
- } = span;
- const overviewItems = [
- {
- key: 'svc',
- label: 'Service:',
- value: process.serviceName,
- },
- {
- key: 'duration',
- label: 'Duration:',
- value: formatDuration(duration),
- },
- {
- key: 'start',
- label: 'Start Time:',
- value: formatDuration(relativeStartTime),
- },
- ];
- const deepLinkCopyText = `${window.location.origin}${window.location.pathname}?uiFind=${spanID}`;
+type SpanInfo = {
+ hasResolvedLocation: boolean,
+ importance?: number
+}
- return (
-
-
-
{operationName}
-
-
-
+type SpanDetailState = {
+ hasResolvedLocation: boolean;
+}
+
+export default class SpanDetail extends React.Component
{
+ constructor(props: SpanDetailProps) {
+ super(props);
+ const span = window.spansWithResolvedLocation[props.span.spanID]
+ this.state = {
+ hasResolvedLocation: Boolean(span) && span.hasResolvedLocation
+ }
+ }
+
+ updateResolvedLocation = (e:{ data: { command: string, data: Record }}) => {
+ const message = e.data;
+ if (message.command === "setSpansWithResolvedLocation") {
+ this.setState({ hasResolvedLocation: message.data[this.props.span.spanID].hasResolvedLocation })
+ }
+ }
+
+ componentDidMount(): void {
+ window.addEventListener('message', this.updateResolvedLocation);
+ }
+
+ componentWillUnmount(): void {
+ window.removeEventListener('message', this.updateResolvedLocation);
+ }
+
+ render() {
+ const {
+ detailState,
+ linksGetter,
+ logItemToggle,
+ logsToggle,
+ processToggle,
+ span,
+ tagsToggle,
+ traceStartTime,
+ warningsToggle,
+ referencesToggle,
+ focusSpan,
+ } = this.props;
+ const { isTagsOpen, isProcessOpen, logs: logsState, isWarningsOpen, isReferencesOpen } = detailState;
+ const {
+ operationName,
+ process,
+ duration,
+ relativeStartTime,
+ spanID,
+ logs,
+ tags,
+ warnings,
+ references,
+ } = span;
+ const overviewItems = [
+ {
+ key: 'svc',
+ label: 'Service:',
+ value: process.serviceName,
+ },
+ {
+ key: 'duration',
+ label: 'Duration:',
+ value: formatDuration(duration),
+ },
+ {
+ key: 'start',
+ label: 'Start Time:',
+ value: formatDuration(relativeStartTime),
+ },
+ ];
+ const deepLinkCopyText = `${window.location.origin}${window.location.pathname}?uiFind=${spanID}`;
+
+ const handleGoToCodeLinkClick = (e: React.MouseEvent) => {
+ e.preventDefault();
+ const tag = span.tags.find((tag: any) => tag.key === "otel.library.name");
+ if (tag && window.vscode) {
+ window.vscode.postMessage({
+ command: "goToSpanLocation",
+ data: {
+ name: span.operationName,
+ instrumentationLibrary: tag && tag.value
+ }
+ });
+ }
+ }
+
+ return (
-
-
tagsToggle(spanID)}
+
+ {this.state.hasResolvedLocation ?
+
+ {operationName}
+ :
{operationName}
+ }
+
- {process.tags && (
+
+
+
+
tagsToggle(spanID)}
+ />
+ {process.tags && (
+ processToggle(spanID)}
+ />
+ )}
+
+ {logs && logs.length > 0 && (
+
processToggle(spanID)}
+ logs={logs}
+ isOpen={logsState.isOpen}
+ openedItems={logsState.openedItems}
+ onToggle={() => logsToggle(spanID)}
+ onItemToggle={logItem => logItemToggle(spanID, logItem)}
+ timestamp={traceStartTime}
/>
)}
-
- {logs && logs.length > 0 && (
- logsToggle(spanID)}
- onItemToggle={logItem => logItemToggle(spanID, logItem)}
- timestamp={traceStartTime}
- />
- )}
- {warnings && warnings.length > 0 && (
- Warnings}
- data={warnings}
- isOpen={isWarningsOpen}
- onToggle={() => warningsToggle(spanID)}
- />
- )}
- {references &&
- references.length > 0 &&
- (references.length > 1 || references[0].refType !== 'CHILD_OF') && (
- referencesToggle(spanID)}
- focusSpan={focusSpan}
+ {warnings && warnings.length > 0 && (
+ Warnings}
+ data={warnings}
+ isOpen={isWarningsOpen}
+ onToggle={() => warningsToggle(spanID)}
/>
)}
-
- {spanID}
-
-
+ {references &&
+ references.length > 0 &&
+ (references.length > 1 || references[0].refType !== 'CHILD_OF') && (
+ referencesToggle(spanID)}
+ focusSpan={focusSpan}
+ />
+ )}
+
+ {spanID}
+
+
+
-
- );
+ );
+ }
}
diff --git a/packages/jaeger-ui/src/components/TracePage/index.tsx b/packages/jaeger-ui/src/components/TracePage/index.tsx
index 2101bf4be3..7e2071526d 100644
--- a/packages/jaeger-ui/src/components/TracePage/index.tsx
+++ b/packages/jaeger-ui/src/components/TracePage/index.tsx
@@ -187,6 +187,28 @@ export class TracePageImpl extends React.PureComponent {
componentDidUpdate({ id: prevID }: TProps) {
const { id, trace } = this.props;
+ // Get all the trace spans and send it to VS Code extension
+ // to verify if they have resolved location
+ if (
+ window.vscode &&
+ trace &&
+ trace.data &&
+ trace.state &&
+ trace.state === fetchedState.DONE
+ ) {
+ window.vscode.postMessage({
+ command: "getTraceSpansLocations",
+ data: trace.data.spans.map(span => {
+ const tag = span.tags.find(tag => tag.key === "otel.library.name");
+
+ return {
+ id: span.spanID,
+ name: span.operationName,
+ instrumentationLibrary: tag && tag.value
+ }}).filter(span => span.instrumentationLibrary)
+ });
+ }
+
this._scrollManager.setTrace(trace && trace.data);
this.setHeaderHeight(this._headerElm);
diff --git a/packages/jaeger-ui/src/img/code.svg b/packages/jaeger-ui/src/img/code.svg
new file mode 100644
index 0000000000..657d3b5bd9
--- /dev/null
+++ b/packages/jaeger-ui/src/img/code.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/jaeger-ui/src/img/exclamation-mark.svg b/packages/jaeger-ui/src/img/exclamation-mark.svg
new file mode 100644
index 0000000000..3c258ff749
--- /dev/null
+++ b/packages/jaeger-ui/src/img/exclamation-mark.svg
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/packages/jaeger-ui/src/reducers/embedded.tsx b/packages/jaeger-ui/src/reducers/embedded.tsx
index 7c1d12280d..45054eb30b 100644
--- a/packages/jaeger-ui/src/reducers/embedded.tsx
+++ b/packages/jaeger-ui/src/reducers/embedded.tsx
@@ -15,11 +15,18 @@
import _get from 'lodash/get';
import { EmbeddedState } from '../types/embedded';
-import { getEmbeddedState } from '../utils/embedded-url';
+import { getEmbeddedState, VERSION_0 } from '../utils/embedded-url';
export default function embeddedConfig(state: EmbeddedState | undefined) {
if (state === undefined) {
- const search = _get(window, 'location.search');
+ let search = _get(window, 'location.search');
+
+ let params = new URLSearchParams(search);
+ if (window.VS_CODE_SETTINGS.embeddedMode && !params.get("uiEmbed")) {
+ params.set("uiEmbed", VERSION_0);
+ search = params.toString();
+ }
+
return search ? getEmbeddedState(search) : null;
}
return state;
diff --git a/packages/jaeger-ui/typings/custom.d.ts b/packages/jaeger-ui/typings/custom.d.ts
index f8bd4fe180..ff2f66864e 100644
--- a/packages/jaeger-ui/typings/custom.d.ts
+++ b/packages/jaeger-ui/typings/custom.d.ts
@@ -23,6 +23,14 @@ declare interface Window {
// For getting ui config
getJaegerUiConfig?: () => Record;
getJaegerVersion?: () => Record;
+ vscode?: Record;
+ spansWithResolvedLocation: Record;
+ VS_CODE_SETTINGS: {
+ apiBaseUrl: string;
+ startPath: string;
+ staticPath: string;
+ embeddedMode: boolean;
+ }
}
// For inlined envvars
@@ -47,3 +55,4 @@ declare module 'json-markup';
declare module 'react-vis-force';
declare module 'tween-functions';
declare module '*.png' { export default '' as string; }
+declare module "*.svg" { export default '' as string; }