TextPanel: Support code formats (#53850)

pull/54163/head
Ryan McKinley 3 years ago committed by GitHub
parent 4d4ecd7fec
commit fe61a97c9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 322
      devenv/dev-dashboards/panel-text/text-options.json
  2. 24
      docs/sources/visualizations/text-panel.md
  3. 61
      public/app/plugins/panel/text/TextPanel.tsx
  4. 13
      public/app/plugins/panel/text/models.cue
  5. 28
      public/app/plugins/panel/text/models.gen.ts
  6. 27
      public/app/plugins/panel/text/module.tsx

@ -0,0 +1,322 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 1348,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 0
},
"id": 4,
"options": {
"code": {
"language": "plaintext",
"showLineNumbers": false,
"showMiniMap": false
},
"content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod\n\n---\ntext = $Text",
"mode": "markdown"
},
"pluginVersion": "9.2.0-pre",
"targets": [
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "random_walk"
}
],
"title": "Markdown (with variables)",
"type": "text"
},
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 0
},
"id": 5,
"options": {
"code": {
"language": "json",
"showLineNumbers": true,
"showMiniMap": false
},
"content": "{\n \"datacenter\": $datacenter,\n \"server\": $server,\n \"pod\": $pod\n \"text\": $Text\n}\n",
"mode": "code"
},
"pluginVersion": "9.2.0-pre",
"targets": [
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "random_walk"
}
],
"title": "JSON (with variables)",
"type": "text"
},
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 9
},
"id": 6,
"options": {
"code": {
"language": "plaintext",
"showLineNumbers": false,
"showMiniMap": false
},
"content": "<h3>Data center</h3>\n<p>$datacenter</p>\n\n<h3>server</h3>\n<p>$server</p>\n\n<h3>pod</h3>\n<p>$pod</p>\n\n<h3>Text</h3>\n<p>$Text</p>",
"mode": "html"
},
"pluginVersion": "9.2.0-pre",
"targets": [
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "random_walk"
}
],
"title": "HTML (with variables)",
"type": "text"
},
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 9
},
"id": 7,
"options": {
"code": {
"language": "markdown",
"showLineNumbers": true,
"showMiniMap": true
},
"content": "## Data center\n$datacenter\n\n### server\n$server\n\n#### pod = \n$pod\n",
"mode": "code"
},
"pluginVersion": "9.2.0-pre",
"targets": [
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "random_walk"
}
],
"title": "Markdown (code w/ with variables)",
"type": "text"
}
],
"refresh": false,
"schemaVersion": 37,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": [
"All"
],
"value": [
"$__all"
]
},
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"definition": "*",
"hide": 0,
"includeAll": true,
"multi": true,
"name": "datacenter",
"options": [],
"query": {
"query": "*",
"refId": "gdev-testdata-datacenter-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"current": {
"selected": false,
"text": [
"All"
],
"value": [
"$__all"
]
},
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"definition": "$datacenter.*",
"hide": 0,
"includeAll": true,
"multi": true,
"name": "server",
"options": [],
"query": {
"query": "$datacenter.*",
"refId": "gdev-testdata-server-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"current": {
"selected": false,
"text": [
"AAA",
"ACB"
],
"value": [
"AAA",
"ACB"
]
},
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"definition": "$datacenter.$server.*",
"hide": 0,
"includeAll": true,
"multi": true,
"name": "pod",
"options": [],
"query": {
"query": "$datacenter.$server.*",
"refId": "gdev-testdata-pod-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"current": {
"selected": false,
"text": "temp",
"value": "temp"
},
"hide": 0,
"name": "Text",
"options": [
{
"selected": true,
"text": "temp",
"value": "temp"
}
],
"query": "temp",
"skipUrlSync": false,
"type": "textbox"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "",
"title": "Text options",
"uid": "WZ7AhQiVz",
"version": 1,
"weekStart": ""
}

@ -15,6 +15,26 @@ weight: 1100
# Text
The text panel visualization lets you make information and description panels for your dashboards.
The text panel enables you to directly include text or HTML in your dashboards. This can be used to add contextual information and descriptions or embed complex HTML.
In **Mode**, select whether you want to use markdown or HTML to style your text, then enter content in the box below. Grafana includes a title and paragraph to help you get started, or you can paste content in from another editor.
## Mode
**Mode** determines how embedded content appears.
### Markdown
This option formats the content as [markdown](https://en.wikipedia.org/wiki/Markdown).
### HTML
This setting renders the content as [sanitized](https://github.com/grafana/grafana/blob/code-in-text-panel/packages/grafana-data/src/text/sanitize.ts) HTML. If you require more direct control over the output, you can set the
[disable_sanitize_html]({{< relref "../setup-grafana/configure-grafana/#disable_sanitize_html" >}}) flag which enables you to directly enter HTML.
### Code
This setting renders content inside a read-only code editor. Select an appropriate language to apply syntax highlighting
to the embedded text.
## Variables
[Variables]({{< relref "../variables/syntax/" >}}) in the content will be expanded for display.

@ -4,13 +4,13 @@ import DangerouslySetHtmlContent from 'dangerously-set-html-content';
import { debounce } from 'lodash';
import React, { PureComponent } from 'react';
import { PanelProps, renderTextPanelMarkdown, textUtil } from '@grafana/data';
import { GrafanaTheme2, PanelProps, renderTextPanelMarkdown, textUtil } from '@grafana/data';
// Utils
import { CustomScrollbar, stylesFactory } from '@grafana/ui';
import { CustomScrollbar, CodeEditor, stylesFactory, ThemeContext } from '@grafana/ui';
import config from 'app/core/config';
// Types
import { PanelOptions, TextMode } from './models.gen';
import { defaultCodeOptions, PanelOptions, TextMode } from './models.gen';
export interface Props extends PanelProps<PanelOptions> {}
@ -19,6 +19,8 @@ interface State {
}
export class TextPanel extends PureComponent<Props, State> {
static contextType = ThemeContext;
constructor(props: Props) {
super(props);
@ -63,8 +65,8 @@ export class TextPanel extends PureComponent<Props, State> {
}
interpolateString(content: string): string {
const { replaceVariables } = this.props;
return replaceVariables(content, {}, 'html');
const { replaceVariables, options } = this.props;
return replaceVariables(content, {}, options.code?.language === 'json' ? 'json' : 'html');
}
sanitizeString(content: string): string {
@ -80,6 +82,8 @@ export class TextPanel extends PureComponent<Props, State> {
if (mode === TextMode.HTML) {
return this.prepareHTML(content);
} else if (mode === TextMode.Code) {
return this.interpolateString(content);
}
return this.prepareMarkdown(content);
@ -87,23 +91,46 @@ export class TextPanel extends PureComponent<Props, State> {
render() {
const { html } = this.state;
const styles = getStyles();
const { options } = this.props;
const styles = getStyles(this.context);
if (options.mode === TextMode.Code) {
const { width, height } = this.props;
const code = options.code ?? defaultCodeOptions;
return (
<CodeEditor
key={`${code.showLineNumbers}/${code.showMiniMap}`} // will reinit-on change
value={html}
language={code.language ?? defaultCodeOptions.language!}
width={width}
height={height}
containerStyles={styles.codeEditorContainer}
showMiniMap={code.showMiniMap}
showLineNumbers={code.showLineNumbers}
readOnly={true} // future
/>
);
}
return (
<CustomScrollbar autoHeightMin="100%">
<DangerouslySetHtmlContent
html={html}
className={cx('markdown-html', styles.content)}
data-testid="TextPanel-converted-content"
/>
<DangerouslySetHtmlContent html={html} className={styles.markdown} data-testid="TextPanel-converted-content" />
</CustomScrollbar>
);
}
}
const getStyles = stylesFactory(() => {
return {
content: css`
const getStyles = stylesFactory((theme: GrafanaTheme2) => ({
codeEditorContainer: css`
.monaco-editor .margin,
.monaco-editor-background {
background-color: ${theme.colors.background.primary};
}
`,
markdown: cx(
'markdown-html',
css`
height: 100%;
`,
};
});
`
),
}));

@ -22,9 +22,20 @@ Panel: thema.#Lineage & {
{
schemas: [
{
TextMode: "html" | "markdown" @cuetsy(kind="enum",memberNames="HTML|Markdown")
TextMode: "html" | "markdown" | "code" @cuetsy(kind="enum",memberNames="HTML|Markdown|Code")
CodeLanguage: "json" | "yaml" | "xml" | "typescript" | "sql" | "go" | "markdown" | "html" | *"plaintext" @cuetsy(kind="enum")
CodeOptions: {
// The language passed to monaco code editor
language: CodeLanguage
showLineNumbers: bool | *false
showMiniMap: bool | *false
} @cuetsy(kind="interface")
PanelOptions: {
mode: TextMode | *"markdown"
code?: CodeOptions
content: string | *"""
# Title

@ -9,11 +9,39 @@ export const PanelModelVersion = Object.freeze([0, 0]);
export enum TextMode {
Code = 'code',
HTML = 'html',
Markdown = 'markdown',
}
export enum CodeLanguage {
Go = 'go',
Html = 'html',
Json = 'json',
Markdown = 'markdown',
Plaintext = 'plaintext',
Sql = 'sql',
Typescript = 'typescript',
Xml = 'xml',
Yaml = 'yaml',
}
export const defaultCodeLanguage: CodeLanguage = CodeLanguage.Plaintext;
export interface CodeOptions {
language: CodeLanguage;
showLineNumbers: boolean;
showMiniMap: boolean;
}
export const defaultCodeOptions: Partial<CodeOptions> = {
language: CodeLanguage.Plaintext,
showLineNumbers: false,
showMiniMap: false,
};
export interface PanelOptions {
code?: CodeOptions;
content: string;
mode: TextMode;
}

@ -2,7 +2,7 @@ import { PanelPlugin } from '@grafana/data';
import { TextPanel } from './TextPanel';
import { TextPanelEditor } from './TextPanelEditor';
import { defaultPanelOptions, PanelOptions, TextMode } from './models.gen';
import { CodeLanguage, defaultCodeOptions, defaultPanelOptions, PanelOptions, TextMode } from './models.gen';
import { textPanelMigrationHandler } from './textPanelMigrationHandler';
export const plugin = new PanelPlugin<PanelOptions>(TextPanel)
@ -16,10 +16,35 @@ export const plugin = new PanelPlugin<PanelOptions>(TextPanel)
options: [
{ value: TextMode.Markdown, label: 'Markdown' },
{ value: TextMode.HTML, label: 'HTML' },
{ value: TextMode.Code, label: 'Code' },
],
},
defaultValue: defaultPanelOptions.mode,
})
.addSelect({
path: 'code.language',
name: 'Language',
settings: {
options: Object.values(CodeLanguage).map((v) => ({
value: v,
label: v,
})),
},
defaultValue: defaultCodeOptions.language,
showIf: (v) => v.mode === TextMode.Code,
})
.addBooleanSwitch({
path: 'code.showLineNumbers',
name: 'Show line numbers',
defaultValue: defaultCodeOptions.showLineNumbers,
showIf: (v) => v.mode === TextMode.Code,
})
.addBooleanSwitch({
path: 'code.showMiniMap',
name: 'Show mini map',
defaultValue: defaultCodeOptions.showMiniMap,
showIf: (v) => v.mode === TextMode.Code,
})
.addCustomEditor({
id: 'content',
path: 'content',

Loading…
Cancel
Save