diff --git a/.betterer.results b/.betterer.results index ad95b3295d2..e5b875089e7 100644 --- a/.betterer.results +++ b/.betterer.results @@ -137,7 +137,7 @@ exports[`better eslint`] = { [12, 17, 10, "Do not use any type assertions.", "1579919174"], [12, 24, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "e2e/dashboards-suite/dashboard-templating.spec.ts:3846708644": [ + "e2e/dashboards-suite/dashboard-templating.spec.ts:1854610528": [ [12, 17, 3, "Unexpected any. Specify a different type.", "193409811"] ], "e2e/dashboards-suite/textbox-variables.spec.ts:2500589821": [ diff --git a/devenv/dev-dashboards/feature-templating/global-variables-and-interpolation.json b/devenv/dev-dashboards/feature-templating/global-variables-and-interpolation.json index 0ef23ebc1c5..944ca21a841 100644 --- a/devenv/dev-dashboards/feature-templating/global-variables-and-interpolation.json +++ b/devenv/dev-dashboards/feature-templating/global-variables-and-interpolation.json @@ -34,7 +34,7 @@ }, "id": 11, "options": { - "content": "## Global variables\n\n* `__dashboard` = `${__dashboard}`\n* `__dashboard.name` = `${__dashboard.name}`\n* `__dashboard.uid` = `${__dashboard.uid}`\n* `__org.name` = `${__org.name}`\n* `__org.id` = `${__org.id}`\n* `__user.id` = `${__user.id}`\n* `__user.login` = `${__user.login}`\n* `__user.email` = `${__user.email}`\n \n## Formats\n\n* `Server:raw` = `${Server:raw}`\n* `Server:regex` = `${Server:regex}`\n* `Server:lucene` = `${Server:lucene}`\n* `Server:glob` = `${Server:glob}`\n* `Server:pipe` = `${Server:pipe}`\n* `Server:distributed` = `${Server:distributed}`\n* `Server:csv` = `${Server:csv}`\n* `Server:html` = `${Server:html}`\n* `Server:json` = `${Server:json}`\n* `Server:percentencode` = `${Server:percentencode}`\n* `Server:singlequote` = `${Server:singlequote}`\n* `Server:doublequote` = `${Server:doublequote}`\n* `Server:sqlstring` = `${Server:sqlstring}`\n* `Server:date` = `${Server:date}`\n* `Server:text` = `${Server:text}`\n* `Server:queryparam` = `${Server:queryparam}`\n\n", + "content": "## Global variables\n\n* `__dashboard` = `${__dashboard}`\n* `__dashboard.name` = `${__dashboard.name}`\n* `__dashboard.uid` = `${__dashboard.uid}`\n* `__org.name` = `${__org.name}`\n* `__org.id` = `${__org.id}`\n* `__user.id` = `${__user.id}`\n* `__user.login` = `${__user.login}`\n* `__user.email` = `${__user.email}`\n \n## Formats\n\n* `Server:raw` = `${Server:raw}`\n* `Server:regex` = `${Server:regex}`\n* `Server:lucene` = `${Server:lucene}`\n* `Server:glob` = `${Server:glob}`\n* `Server:pipe` = `${Server:pipe}`\n* `Server:distributed` = `${Server:distributed}`\n* `Server:csv` = `${Server:csv}`\n* `Server:html` = `${Server:html}`\n* `Server:json` = `${Server:json}`\n* `Server:percentencode` = `${Server:percentencode}`\n* `Server:singlequote` = `${Server:singlequote}`\n* `Server:doublequote` = `${Server:doublequote}`\n* `Server:sqlstring` = `${Server:sqlstring}`\n* `Server:date` = `${Server:date}`\n* `Server:text` = `${Server:text}`\n* `Server:queryparam` = `${Server:queryparam}`\n\n## Sanitization\n\n * `1 < 2`\n\n## Link interpolation\n\n* [Example: ${__url_time_range}](https://example.com/?${__url_time_range})", "mode": "markdown" }, "pluginVersion": "7.1.0", diff --git a/e2e/dashboards-suite/dashboard-templating.spec.ts b/e2e/dashboards-suite/dashboard-templating.spec.ts index 4a6187e4be1..8d3d10770e5 100644 --- a/e2e/dashboards-suite/dashboard-templating.spec.ts +++ b/e2e/dashboards-suite/dashboard-templating.spec.ts @@ -27,7 +27,7 @@ e2e.scenario({ `Server:pipe = A'A"A|BB\\B|CCC`, `Server:distributed = A'A"A,Server=BB\\B,Server=CCC`, `Server:csv = A'A"A,BB\\B,CCC`, - `Server:html = A'A"A, BB\\B, CCC`, + `Server:html = A'A"A, BB\\B, CCC`, `Server:json = ["A'A\\"A","BB\\\\B","CCC"]`, `Server:percentencode = %7BA%27A%22A%2CBB%5CB%2CCCC%7D`, `Server:singlequote = 'A\\'A"A','BB\\B','CCC'`, @@ -36,11 +36,13 @@ e2e.scenario({ `Server:date = null`, `Server:text = All`, `Server:queryparam = var-Server=All`, + `1 < 2`, + `Example: from=now-6h&to=now`, ]; e2e() .get('.markdown-html li') - .should('have.length', 24) + .should('have.length', 26) .each((element) => { items.push(element.text()); }) @@ -49,5 +51,10 @@ e2e.scenario({ expect(items[index]).to.equal(expected); }); }); + + // Check link interpolation is working correctly + e2e() + .contains('a', 'Example: from=now-6h&to=now') + .should('have.attr', 'href', 'https://example.com/?from=now-6h&to=now'); }, }); diff --git a/public/app/plugins/panel/text/TextPanel.tsx b/public/app/plugins/panel/text/TextPanel.tsx index f2808f00cf8..c30ba779ec9 100644 --- a/public/app/plugins/panel/text/TextPanel.tsx +++ b/public/app/plugins/panel/text/TextPanel.tsx @@ -41,24 +41,34 @@ export class TextPanel extends PureComponent { } prepareHTML(html: string): string { - return this.interpolateAndSanitizeString(html); + const result = this.interpolateString(html); + return config.disableSanitizeHtml ? result : this.sanitizeString(result); } prepareMarkdown(content: string): string { - // Sanitize is disabled here as we handle that after variable interpolation - return this.interpolateAndSanitizeString( - renderTextPanelMarkdown(content, { - noSanitize: config.disableSanitizeHtml, - }) - ); + // Always interpolate variables before converting to markdown + // because `marked` replaces '{' and '}' in URLs with '%7B' and '%7D' + // See https://marked.js.org/demo + let result = this.interpolateString(content); + + if (config.disableSanitizeHtml) { + result = renderTextPanelMarkdown(result, { + noSanitize: true, + }); + return result; + } + + result = renderTextPanelMarkdown(result); + return this.sanitizeString(result); } - interpolateAndSanitizeString(content: string): string { + interpolateString(content: string): string { const { replaceVariables } = this.props; + return replaceVariables(content, {}, 'html'); + } - content = replaceVariables(content, {}, 'html'); - - return config.disableSanitizeHtml ? content : textUtil.sanitizeTextPanelContent(content); + sanitizeString(content: string): string { + return textUtil.sanitizeTextPanelContent(content); } processContent(options: PanelOptions): string {