|
1 | 1 | import * as vscode from "vscode"; |
| 2 | +import { AnalyticsProvider } from "../../../services/analyticsProvider"; |
| 3 | +import { EditorHelper } from "../../../services/EditorHelper"; |
| 4 | +import { SpanSearch } from "../InsightListView/Common/SpanSearch"; |
| 5 | + |
| 6 | +interface Message { |
| 7 | + command: string; |
| 8 | + // TODO: change to unknown and use typeGuards |
| 9 | + data: any; |
| 10 | +} |
| 11 | + |
| 12 | +interface GoToSpanLocationMessage extends Message { |
| 13 | + data: { |
| 14 | + name: string; |
| 15 | + instrumentationLibrary: string; |
| 16 | + }; |
| 17 | +} |
| 18 | + |
| 19 | +interface GetTraceSpansLocationsMessage extends Message { |
| 20 | + data: { |
| 21 | + id: string; |
| 22 | + name: string; |
| 23 | + instrumentationLibrary: string; |
| 24 | + }[]; |
| 25 | +} |
| 26 | + |
| 27 | +type SetSpansWithResolvedLocationMessageData = Record< |
| 28 | + string, |
| 29 | + { importance?: number } |
| 30 | +>; |
2 | 31 |
|
3 | 32 | export class JaegerPanel { |
4 | | - constructor() {} |
| 33 | + private _panel: vscode.WebviewPanel; |
| 34 | + private _spanSearch: SpanSearch; |
| 35 | + private _editorHelper: EditorHelper; |
| 36 | + private _analyticsProvider: AnalyticsProvider; |
| 37 | + constructor( |
| 38 | + panel: vscode.WebviewPanel, |
| 39 | + spanSearch: SpanSearch, |
| 40 | + editorHelper: EditorHelper, |
| 41 | + analyticsProvider: AnalyticsProvider |
| 42 | + ) { |
| 43 | + this._panel = panel; |
| 44 | + this._spanSearch = spanSearch; |
| 45 | + this._editorHelper = editorHelper; |
| 46 | + this._analyticsProvider = analyticsProvider; |
| 47 | + |
| 48 | + this._panel.webview.onDidReceiveMessage(async (message: Message) => { |
| 49 | + switch (message.command) { |
| 50 | + case "goToSpanLocation": |
| 51 | + await this.onGoToSpanLocation(message); |
| 52 | + break; |
| 53 | + case "getTraceSpansLocations": |
| 54 | + await this.onGetTraceSpansLocations(message); |
| 55 | + break; |
| 56 | + } |
| 57 | + }); |
| 58 | + } |
| 59 | + |
| 60 | + private async onGoToSpanLocation(message: GoToSpanLocationMessage) { |
| 61 | + const span = message.data; |
| 62 | + const spanLocation = await this._spanSearch.searchForSpans([ |
| 63 | + { |
| 64 | + name: span.name, |
| 65 | + instrumentationLibrary: span.instrumentationLibrary, |
| 66 | + }, |
| 67 | + ]); |
| 68 | + |
| 69 | + if (spanLocation[0]) { |
| 70 | + const codeUri = spanLocation[0].documentUri; |
| 71 | + const lineNumber = spanLocation[0].range.end.line + 1; |
| 72 | + |
| 73 | + if (codeUri && lineNumber) { |
| 74 | + const doc = await this._editorHelper.openTextDocumentFromUri( |
| 75 | + vscode.Uri.parse(codeUri.toString()) |
| 76 | + ); |
| 77 | + this._editorHelper.openFileAndLine(doc, lineNumber); |
| 78 | + } |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + private async onGetTraceSpansLocations( |
| 83 | + message: GetTraceSpansLocationsMessage |
| 84 | + ) { |
| 85 | + console.log("onGetTraceSpansLocations received"); |
| 86 | + const spans = message.data; |
| 87 | + const spanLocations = await this._spanSearch.searchForSpans(spans); |
| 88 | + const spanWithLocations = spans.filter((span, i) => spanLocations[i]); |
| 89 | + const spanCodeObjectIds = spanWithLocations.map( |
| 90 | + (span) => `span:${span.instrumentationLibrary}$_$${span.name}` |
| 91 | + ); |
| 92 | + |
| 93 | + const insights = await this._analyticsProvider.getInsights( |
| 94 | + spanCodeObjectIds, |
| 95 | + true |
| 96 | + ); |
| 97 | + const insightGroups = insights.groupBy((x) => x.codeObjectId); |
| 98 | + |
| 99 | + const spansInfo = spanWithLocations.reduce( |
| 100 | + (acc: SetSpansWithResolvedLocationMessageData, span) => { |
| 101 | + const insightGroup = |
| 102 | + insightGroups[`${span.instrumentationLibrary}$_$${span.name}`]; |
5 | 103 |
|
6 | | - public getHtml(traceIds: string[], traceIdLabels :string[]|undefined, span: string, jaegerAddress: string, jaegerUri: vscode.Uri): string { |
| 104 | + let importance; |
| 105 | + if (insightGroup) { |
| 106 | + const importanceArray: number[] = insightGroup.map( |
| 107 | + (insight) => insight.importance |
| 108 | + ); |
| 109 | + importance = Math.min(...importanceArray); |
| 110 | + } |
| 111 | + |
| 112 | + acc[span.id] = { |
| 113 | + importance, |
| 114 | + }; |
| 115 | + |
| 116 | + return acc; |
| 117 | + }, |
| 118 | + {} |
| 119 | + ); |
| 120 | + |
| 121 | + this._panel.webview.postMessage({ |
| 122 | + command: "setSpansWithResolvedLocation", |
| 123 | + data: spansInfo, |
| 124 | + }); |
| 125 | + } |
| 126 | + |
| 127 | + public getHtml( |
| 128 | + traceIds: string[], |
| 129 | + traceIdLabels: string[] | undefined, |
| 130 | + span: string, |
| 131 | + jaegerAddress: string, |
| 132 | + jaegerUri: vscode.Uri |
| 133 | + ): string { |
| 134 | + const webview = this._panel.webview; |
7 | 135 | const staticPath = `${jaegerUri}/static`; |
8 | 136 |
|
9 | 137 | let startPath = ""; |
10 | 138 | if (traceIds.length === 1) { |
11 | 139 | startPath = `/trace/${traceIds[0]}`; |
12 | 140 | } else if (traceIds.length === 2 && traceIdLabels != null) { |
13 | | - const trace1 = traceIds[0].toLocaleLowerCase(); |
14 | | - const trace2 = traceIds[1].toLocaleLowerCase(); |
| 141 | + const trace1 = traceIds[0].toLocaleLowerCase(); |
| 142 | + const trace2 = traceIds[1].toLocaleLowerCase(); |
15 | 143 | startPath = `/trace/${trace1}...${trace2}?cohort=${trace1}&cohort=${trace2}`; |
16 | 144 | } |
17 | 145 |
|
18 | | - const html = `<!doctype html> |
19 | | - <html lang="en"> |
20 | | -
|
21 | | - <head> |
22 | | - <meta charset="utf-8"> |
23 | | - <meta name="viewport" content="width=device-width,initial-scale=1"> |
24 | | - <base href="/" data-inject-target="BASE_URL" /> |
25 | | - <title>Jaeger UI</title> |
26 | | - <script>// Jaeger UI config data is embedded by the query-service via search-replace. |
27 | | - // This is later merged with defaults into the redux \`state.config\` via |
28 | | - // src/utils/config/get-config.js. |
29 | | - // JAEGER_CONFIG_JS |
30 | | - // the line above may be replaced by user-provided JS file that should define a UIConfig function. |
31 | | - function getJaegerUiConfig() { |
32 | | - if (typeof window.UIConfig === 'function') { |
33 | | - return UIConfig(); |
34 | | - } |
35 | | - const DEFAULT_CONFIG = null; |
36 | | - const JAEGER_CONFIG = DEFAULT_CONFIG; |
37 | | - return JAEGER_CONFIG; |
38 | | - } |
39 | | - // Jaeger version data is embedded by the query-service via search/replace. |
40 | | - function getJaegerVersion() { |
41 | | - const DEFAULT_VERSION = { "gitCommit": "", "gitVersion": "", "buildDate": "" }; |
42 | | - const JAEGER_VERSION = DEFAULT_VERSION; |
43 | | - return JAEGER_VERSION; |
44 | | - } |
45 | | - </script> |
46 | | - <script> |
47 | | - // Variables for injection by VS Code extension |
48 | | - var VS_CODE_SETTINGS = { |
49 | | - apiBaseUrl: "${jaegerAddress}", |
50 | | - startPath: "${startPath}", |
51 | | - staticPath: "${jaegerUri}/", |
52 | | - embeddedMode: true |
53 | | - }; |
| 146 | + const nonce = getNonce(); |
54 | 147 |
|
55 | | - // Store list of trace spans with resolved location globally |
56 | | - var spansWithResolvedLocation = {}; |
57 | | - var pendingOperationsCount = 0; |
58 | | -
|
59 | | - window.addEventListener("message", (e) => { |
60 | | - const message = e.data; |
61 | | - switch (message.command) { |
62 | | - case "setSpansWithResolvedLocation": |
63 | | - spansWithResolvedLocation = message.data; |
64 | | - pendingOperationsCount--; |
65 | | - } |
66 | | - }); |
67 | | -
|
68 | | - var vscode = acquireVsCodeApi(); |
69 | | - </script> |
70 | | - <!-- Reset default styles set by VS Code --> |
71 | | - <style> |
72 | | - body { |
73 | | - padding: 0; |
74 | | - } |
75 | | - </style> |
76 | | - <link href="${staticPath}/css/1.b0b1393a.chunk.css" rel="stylesheet"> |
77 | | - <link href="${staticPath}/css/main.27c048eb.chunk.css" rel="stylesheet"> |
78 | | - </head> |
| 148 | + const html = ` |
| 149 | + <!doctype html> |
| 150 | + <html lang="en"> |
| 151 | +
|
| 152 | + <head> |
| 153 | + <meta charset="utf-8"> |
| 154 | + <meta name="viewport" content="width=device-width,initial-scale=1"> |
| 155 | + <meta http-equiv="Content-Security-Policy" |
| 156 | + content=" |
| 157 | + default-src 'none'; |
| 158 | + style-src ${webview.cspSource} 'unsafe-inline'; |
| 159 | + img-src ${webview.cspSource}; |
| 160 | + script-src ${webview.cspSource} 'nonce-${nonce}'; |
| 161 | + connect-src ${jaegerAddress}; |
| 162 | + " |
| 163 | + > |
| 164 | + <base href="/" data-inject-target="BASE_URL" /> |
| 165 | + <title>Jaeger UI</title> |
| 166 | + <script nonce="${nonce}">// Jaeger UI config data is embedded by the query-service via search-replace. |
| 167 | + // This is later merged with defaults into the redux \`state.config\` via |
| 168 | + // src/utils/config/get-config.js. |
| 169 | + // JAEGER_CONFIG_JS |
| 170 | + // the line above may be replaced by user-provided JS file that should define a UIConfig function. |
| 171 | + function getJaegerUiConfig() { |
| 172 | + if (typeof window.UIConfig === 'function') { |
| 173 | + return UIConfig(); |
| 174 | + } |
| 175 | + const DEFAULT_CONFIG = null; |
| 176 | + const JAEGER_CONFIG = DEFAULT_CONFIG; |
| 177 | + return JAEGER_CONFIG; |
| 178 | + } |
| 179 | + // Jaeger version data is embedded by the query-service via search/replace. |
| 180 | + function getJaegerVersion() { |
| 181 | + const DEFAULT_VERSION = { "gitCommit": "", "gitVersion": "", "buildDate": "" }; |
| 182 | + const JAEGER_VERSION = DEFAULT_VERSION; |
| 183 | + return JAEGER_VERSION; |
| 184 | + } |
| 185 | + </script> |
| 186 | + <script nonce="${nonce}"> |
| 187 | + // Variables for injection by VS Code extension |
| 188 | + var VS_CODE_SETTINGS = { |
| 189 | + apiBaseUrl: "${jaegerAddress}", |
| 190 | + startPath: "${startPath}", |
| 191 | + staticPath: "${jaegerUri}/", |
| 192 | + embeddedMode: true |
| 193 | + }; |
| 194 | + // Store list of trace spans with resolved location globally |
| 195 | + var spansWithResolvedLocation = {}; |
| 196 | + var pendingOperationsCount = 0; |
79 | 197 |
|
80 | | - <body> |
81 | | - <!-- TODO: Remove these images after resolving the HTTP 404 problem with dynamically loaded assets --> |
82 | | - <img src="${staticPath}/media/jaeger-logo.a7093b12.svg" style="display: none;"> |
83 | | - <img src="${staticPath}/media/monitor.c9164c96.png" style="display: none;"> |
84 | | - <img src="${staticPath}/media/code.351c1388.svg" style="display: none;"> |
| 198 | + window.addEventListener("message", (e) => { |
| 199 | + const message = e.data; |
| 200 | + switch (message.command) { |
| 201 | + case "setSpansWithResolvedLocation": |
| 202 | + spansWithResolvedLocation = message.data; |
| 203 | + pendingOperationsCount--; |
| 204 | + } |
| 205 | + }); |
| 206 | + </script> |
| 207 | + <!-- Reset default styles set by VS Code --> |
| 208 | + <style> |
| 209 | + body { |
| 210 | + padding: 0; |
| 211 | + } |
| 212 | + </style> |
| 213 | + <link href="${staticPath}/css/1.b0b1393a.chunk.css" rel="stylesheet"> |
| 214 | + <link href="${staticPath}/css/main.27c048eb.chunk.css" rel="stylesheet"> |
| 215 | + </head> |
85 | 216 |
|
86 | | - <div id="jaeger-ui-root"></div> |
87 | | - <script>!function (l) { function e(e) { for (var r, t, n = e[0], o = e[1], u = e[2], f = 0, i = []; f < n.length; f++)t = n[f], p[t] && i.push(p[t][0]), p[t] = 0; for (r in o) Object.prototype.hasOwnProperty.call(o, r) && (l[r] = o[r]); for (s && s(e); i.length;)i.shift()(); return c.push.apply(c, u || []), a() } function a() { for (var e, r = 0; r < c.length; r++) { for (var t = c[r], n = !0, o = 1; o < t.length; o++) { var u = t[o]; 0 !== p[u] && (n = !1) } n && (c.splice(r--, 1), e = f(f.s = t[0])) } return e } var t = {}, p = { 2: 0 }, c = []; function f(e) { if (t[e]) return t[e].exports; var r = t[e] = { i: e, l: !1, exports: {} }; return l[e].call(r.exports, r, r.exports, f), r.l = !0, r.exports } f.m = l, f.c = t, f.d = function (e, r, t) { f.o(e, r) || Object.defineProperty(e, r, { enumerable: !0, get: t }) }, f.r = function (e) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(e, "__esModule", { value: !0 }) }, f.t = function (r, e) { if (1 & e && (r = f(r)), 8 & e) return r; if (4 & e && "object" == typeof r && r && r.__esModule) return r; var t = Object.create(null); if (f.r(t), Object.defineProperty(t, "default", { enumerable: !0, value: r }), 2 & e && "string" != typeof r) for (var n in r) f.d(t, n, function (e) { return r[e] }.bind(null, n)); return t }, f.n = function (e) { var r = e && e.__esModule ? function () { return e.default } : function () { return e }; return f.d(r, "a", r), r }, f.o = function (e, r) { return Object.prototype.hasOwnProperty.call(e, r) }, f.p = "./"; var r = window.webpackJsonp = window.webpackJsonp || [], n = r.push.bind(r); r.push = e, r = r.slice(); for (var o = 0; o < r.length; o++)e(r[o]); var s = n; a() }([])</script> |
88 | | - <script src="${staticPath}/js/1.fa29228e.chunk.js"></script> |
89 | | - <script src="${staticPath}/js/main.cd5ed381.chunk.js"></script> |
90 | | - </body> |
| 217 | + <body> |
| 218 | + <!-- TODO: Remove these images after resolving the HTTP 404 problem with dynamically loaded assets --> |
| 219 | + <img src="${staticPath}/media/jaeger-logo.a7093b12.svg" style="display: none;"> |
| 220 | + <img src="${staticPath}/media/monitor.c9164c96.png" style="display: none;"> |
| 221 | + <img src="${staticPath}/media/code.351c1388.svg" style="display: none;"> |
91 | 222 |
|
92 | | - </html>`; |
| 223 | + <div id="jaeger-ui-root"></div> |
| 224 | + <script src="${staticPath}/js/vscode.js"></script> |
| 225 | + <script nonce="${nonce}">!function (l) { function e(e) { for (var r, t, n = e[0], o = e[1], u = e[2], f = 0, i = []; f < n.length; f++)t = n[f], p[t] && i.push(p[t][0]), p[t] = 0; for (r in o) Object.prototype.hasOwnProperty.call(o, r) && (l[r] = o[r]); for (s && s(e); i.length;)i.shift()(); return c.push.apply(c, u || []), a() } function a() { for (var e, r = 0; r < c.length; r++) { for (var t = c[r], n = !0, o = 1; o < t.length; o++) { var u = t[o]; 0 !== p[u] && (n = !1) } n && (c.splice(r--, 1), e = f(f.s = t[0])) } return e } var t = {}, p = { 2: 0 }, c = []; function f(e) { if (t[e]) return t[e].exports; var r = t[e] = { i: e, l: !1, exports: {} }; return l[e].call(r.exports, r, r.exports, f), r.l = !0, r.exports } f.m = l, f.c = t, f.d = function (e, r, t) { f.o(e, r) || Object.defineProperty(e, r, { enumerable: !0, get: t }) }, f.r = function (e) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(e, "__esModule", { value: !0 }) }, f.t = function (r, e) { if (1 & e && (r = f(r)), 8 & e) return r; if (4 & e && "object" == typeof r && r && r.__esModule) return r; var t = Object.create(null); if (f.r(t), Object.defineProperty(t, "default", { enumerable: !0, value: r }), 2 & e && "string" != typeof r) for (var n in r) f.d(t, n, function (e) { return r[e] }.bind(null, n)); return t }, f.n = function (e) { var r = e && e.__esModule ? function () { return e.default } : function () { return e }; return f.d(r, "a", r), r }, f.o = function (e, r) { return Object.prototype.hasOwnProperty.call(e, r) }, f.p = "./"; var r = window.webpackJsonp = window.webpackJsonp || [], n = r.push.bind(r); r.push = e, r = r.slice(); for (var o = 0; o < r.length; o++)e(r[o]); var s = n; a() }([])</script> |
| 226 | + <script src="${staticPath}/js/1.65ef2ae1.chunk.js"></script> |
| 227 | + <script src="${staticPath}/js/main.7706e4a8.chunk.js"></script> |
| 228 | + </body> |
| 229 | +
|
| 230 | + </html> |
| 231 | + `; |
93 | 232 |
|
94 | 233 | return html; |
95 | 234 | } |
96 | 235 | } |
| 236 | + |
| 237 | +function getNonce() { |
| 238 | + let text = ""; |
| 239 | + const possible = |
| 240 | + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; |
| 241 | + for (let i = 0; i < 32; i++) { |
| 242 | + text += possible.charAt(Math.floor(Math.random() * possible.length)); |
| 243 | + } |
| 244 | + return text; |
| 245 | +} |
0 commit comments