diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5aada9e..4f7f7c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
### Features / Enhancements
- Updated Autosize Code Editor toolbar (#362)
+- Added helper statusColor from specific field (#375)
## 5.4.0 (2024-09-12)
diff --git a/provisioning/dashboards/panels.json b/provisioning/dashboards/panels.json
index 0b1b2eb..305409f 100644
--- a/provisioning/dashboards/panels.json
+++ b/provisioning/dashboards/panels.json
@@ -24,8 +24,8 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
+ "id": 6,
"links": [],
- "liveNow": false,
"panels": [
{
"datasource": {
@@ -56,7 +56,7 @@
},
"gridPos": {
"h": 5,
- "w": 8,
+ "w": 24,
"x": 0,
"y": 0
},
@@ -80,7 +80,6 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.3.0",
"repeat": "test",
"repeatDirection": "h",
"targets": [
@@ -143,7 +142,6 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.3.0",
"targets": [
{
"datasource": {
@@ -221,7 +219,6 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.3.0",
"targets": [
{
"datasource": {
@@ -294,7 +291,6 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.3.0",
"targets": [
{
"datasource": {
@@ -355,7 +351,6 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.3.0",
"targets": [
{
"datasource": {
@@ -448,7 +443,6 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.3.0",
"targets": [
{
"datasource": {
@@ -541,7 +535,6 @@
"styles": "& {\n padding: 0;\n margin: 0;\n}",
"wrap": true
},
- "pluginVersion": "5.3.0",
"targets": [
{
"datasource": {
@@ -551,6 +544,7 @@
"refId": "A"
}
],
+ "title": "",
"type": "marcusolsson-dynamictext-panel"
},
{
@@ -601,7 +595,6 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.3.0",
"targets": [
{
"datasource": {
@@ -676,7 +669,6 @@
"styles": "td.name {\n border: 0;\n background-color: #5d3fc4;\n color: white;\n}\n\nb.name {\n font-family: silom;\n font-size:20px;\n}\n\ntd.photo {\n border:2px solid #5D3FC4;\n text-align:center;\n}\n\ntd.desc {\n text-align:right;\n border:0;\n background-color:#f6f2ff;\n color:#5F3DC4;\n}",
"wrap": true
},
- "pluginVersion": "5.3.0",
"targets": [
{
"datasource": {
@@ -802,7 +794,6 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.3.0",
"targets": [
{
"datasource": {
@@ -896,7 +887,6 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.3.0",
"targets": [
{
"datasource": {
@@ -976,7 +966,6 @@
"styles": "",
"wrap": true
},
- "pluginVersion": "5.3.0",
"targets": [
{
"datasource": {
@@ -1055,7 +1044,6 @@
"styles": ".button {\n background-color: ${theme.colors.primary.main};\n border: none;\n color: ${theme.colors.primary.contrastText};\n padding: 4px 8px;\n border-radius: ${theme.shape.radius.default};\n}",
"wrap": true
},
- "pluginVersion": "5.3.0",
"targets": [
{
"datasource": {
@@ -1078,21 +1066,205 @@
],
"title": "Theme",
"type": "marcusolsson-dynamictext-panel"
+ },
+ {
+ "datasource": {
+ "type": "marcusolsson-static-datasource",
+ "uid": "U0HP2Rv4z"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "value"
+ },
+ "properties": [
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "yellow",
+ "value": null
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 46
+ },
+ "id": 26,
+ "options": {
+ "afterRender": "",
+ "content": "
\n{{#each data}}\n
Color from value field
\n
Status color from Time
\n {{time}} -- {{value}} \n{{/each}}\n
",
+ "contentPartials": [],
+ "defaultContent": "The query didn't return any results.",
+ "editor": {
+ "format": "auto",
+ "language": "markdown"
+ },
+ "editors": [],
+ "externalStyles": [],
+ "helpers": "",
+ "renderMode": "allRows",
+ "status": "time",
+ "styles": "",
+ "wrap": true
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "marcusolsson-static-datasource",
+ "uid": "U0HP2Rv4z"
+ },
+ "frame": {
+ "fields": [
+ {
+ "config": {},
+ "name": "value",
+ "type": "string",
+ "values": ["1", "2", "3", "4", "5", "6", "7"]
+ },
+ {
+ "config": {},
+ "name": "time",
+ "type": "number",
+ "values": [10, 20, 30, 40, 50, 60, 70]
+ }
+ ],
+ "meta": {}
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "All rows color from specific field",
+ "type": "marcusolsson-dynamictext-panel"
+ },
+ {
+ "datasource": {
+ "type": "marcusolsson-static-datasource",
+ "uid": "U0HP2Rv4z"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "value"
+ },
+ "properties": [
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "yellow",
+ "value": null
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 46
+ },
+ "id": 27,
+ "options": {
+ "afterRender": "",
+ "content": "\n\n```json\n{{{json @root}}}\n```\n \n
",
+ "contentPartials": [],
+ "defaultContent": "The query didn't return any results.",
+ "editor": {
+ "format": "auto",
+ "language": "markdown"
+ },
+ "editors": [],
+ "externalStyles": [],
+ "helpers": "",
+ "renderMode": "everyRow",
+ "status": "time",
+ "styles": "",
+ "wrap": true
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "marcusolsson-static-datasource",
+ "uid": "U0HP2Rv4z"
+ },
+ "frame": {
+ "fields": [
+ {
+ "config": {},
+ "name": "value",
+ "type": "string",
+ "values": ["1", "2", "3", "4", "5", "6", "7"]
+ },
+ {
+ "config": {},
+ "name": "time",
+ "type": "number",
+ "values": [10, 20, 30, 40, 50, 60, 70]
+ }
+ ],
+ "meta": {}
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Every row color from specific field",
+ "type": "marcusolsson-dynamictext-panel"
}
],
+ "preload": false,
"refresh": "",
- "revision": 1,
- "schemaVersion": 39,
+ "schemaVersion": 40,
"tags": [],
"templating": {
"list": [
{
"current": {
- "selected": true,
"text": ["All"],
"value": ["$__all"]
},
- "hide": 0,
"includeAll": true,
"multi": true,
"name": "test",
@@ -1119,8 +1291,6 @@
}
],
"query": "test1,test2,test3",
- "queryValue": "",
- "skipUrlSync": false,
"type": "custom"
}
]
diff --git a/src/components/Text/Text.test.tsx b/src/components/Text/Text.test.tsx
index 3e1e86a..07fd66e 100644
--- a/src/components/Text/Text.test.tsx
+++ b/src/components/Text/Text.test.tsx
@@ -287,6 +287,250 @@ describe('Text', () => {
expect(statuses[0]).toHaveStyle({ backgroundColor: 'red' });
});
+ it('Should apply status from specific field', async () => {
+ const replaceVariables = jest.fn((str: string) => str);
+ const dataFrame = toDataFrame({
+ fields: [
+ {
+ name: 'test',
+ type: FieldType.number,
+ display: (value: number) => ({ color: value > 80 ? 'red' : 'green' }),
+ values: [70, 75],
+ },
+ {
+ name: 'value',
+ type: FieldType.number,
+ display: (value: number) => ({ color: value > 80 ? 'red' : 'green' }),
+ values: [60, 65],
+ },
+ {
+ name: 'number',
+ type: FieldType.number,
+ display: (value: number) => ({ color: 'yellow' }),
+ values: [90, 95],
+ },
+ ],
+ });
+
+ const props: Props = {
+ data: {} as any,
+ frame: dataFrame,
+ options: {
+ ...DEFAULT_OPTIONS,
+ status: 'value',
+ content:
+ '{{statusColor}}
',
+ defaultContent: 'Test default content',
+ renderMode: RenderMode.EVERY_ROW,
+ },
+ timeRange: {} as any,
+ timeZone: '',
+ replaceVariables,
+ eventBus: {} as any,
+ };
+
+ await act(async () => render());
+
+ const statuses = screen.getAllByTestId('status-color');
+
+ expect(statuses[0]).toHaveStyle({ backgroundColor: 'yellow' });
+ expect(statuses[0]).toHaveTextContent('green');
+ });
+
+ it('Should apply status from specific field and value', async () => {
+ const replaceVariables = jest.fn((str: string) => str);
+ const dataFrame = toDataFrame({
+ fields: [
+ {
+ name: 'test',
+ type: FieldType.number,
+ display: (value: number) => ({ color: value > 80 ? 'red' : 'green' }),
+ values: [70, 75],
+ },
+ {
+ name: 'value',
+ type: FieldType.number,
+ display: (value: number) => ({ color: value > 80 ? 'red' : 'green' }),
+ values: [60, 65],
+ },
+ {
+ name: 'number',
+ type: FieldType.number,
+ display: (value: number) => ({ color: value >= 95 ? 'yellow' : 'dark-yellow' }),
+ values: [95, 90],
+ },
+ ],
+ });
+
+ const props: Props = {
+ data: {} as any,
+ frame: dataFrame,
+ options: {
+ ...DEFAULT_OPTIONS,
+ status: 'value',
+ content:
+ '{{number}}
',
+ defaultContent: 'Test default content',
+ renderMode: RenderMode.EVERY_ROW,
+ },
+ timeRange: {} as any,
+ timeZone: '',
+ replaceVariables,
+ eventBus: {} as any,
+ };
+
+ await act(async () => render());
+
+ const statuses = screen.getAllByTestId('status-color');
+
+ expect(statuses[0]).toHaveStyle({ backgroundColor: 'dark-yellow' });
+ });
+
+ it('Should apply status from specific field for all rows', async () => {
+ const replaceVariables = jest.fn((str: string) => str);
+ const dataFrame = toDataFrame({
+ fields: [
+ {
+ name: 'test',
+ type: FieldType.number,
+ display: (value: number) => ({ color: value > 80 ? 'red' : 'green' }),
+ values: [70, 75],
+ },
+ {
+ name: 'value',
+ type: FieldType.number,
+ display: (value: number) => ({ color: value > 80 ? 'red' : 'green' }),
+ values: [60, 65],
+ },
+ {
+ name: 'number',
+ type: FieldType.number,
+ display: (value: number) => ({ color: 'yellow' }),
+ values: [90, 95],
+ },
+ ],
+ });
+
+ const props: Props = {
+ data: {} as any,
+ frame: dataFrame,
+ options: {
+ ...DEFAULT_OPTIONS,
+ status: 'value',
+ content: `
+ {{#each data}}
+
Specific field
+
Status color
+ {{time}}{{series}}
+ {{/each}}
+
`,
+ defaultContent: 'Test default content',
+ renderMode: RenderMode.ALL_ROWS,
+ },
+ timeRange: {} as any,
+ timeZone: '',
+ replaceVariables,
+ eventBus: {} as any,
+ };
+
+ await act(async () => render());
+
+ const fieldStatuses = screen.getAllByTestId('status-color');
+ const statuses = screen.getAllByTestId('color');
+
+ expect(fieldStatuses[0]).toHaveStyle({ backgroundColor: 'yellow' });
+ expect(statuses[0]).toHaveStyle({ backgroundColor: 'green' });
+ });
+
+ it('Should return empty color apply if field not specified for data frame', async () => {
+ const replaceVariables = jest.fn((str: string) => str);
+ const dataFrame = toDataFrame({
+ fields: [
+ {
+ name: 'test',
+ type: FieldType.number,
+ display: (value: number) => ({ color: value > 80 ? 'red' : 'green' }),
+ values: [70, 75],
+ },
+ {
+ name: 'value',
+ type: FieldType.number,
+ display: (value: number) => ({ color: value > 80 ? 'red' : 'green' }),
+ values: [60, 65],
+ },
+ {
+ name: 'number',
+ type: FieldType.number,
+ display: (value: number) => ({ color: 'yellow' }),
+ values: [90, 95],
+ },
+ ],
+ });
+
+ const props: Props = {
+ data: {} as any,
+ frame: dataFrame,
+ options: {
+ ...DEFAULT_OPTIONS,
+ status: 'value',
+ content:
+ '{{number}}
',
+ defaultContent: 'Test default content',
+ renderMode: RenderMode.EVERY_ROW,
+ },
+ timeRange: {} as any,
+ timeZone: '',
+ replaceVariables,
+ eventBus: {} as any,
+ };
+
+ await act(async () => render());
+
+ const statuses = screen.getAllByTestId('status-color');
+
+ expect(statuses[0]).toHaveStyle({ backgroundColor: '' });
+ });
+
+ it('Should return empty color apply if field not specified for all data mode', async () => {
+ const props: Props = {
+ data: {
+ series: [
+ toDataFrame({
+ fields: [
+ {
+ type: FieldType.string,
+ name: 'text',
+ display: (value: number) => ({ color: 'yellow' }),
+ values: ['hello', 'hello2'],
+ },
+ ],
+ }),
+ ],
+ } as any,
+ frame: {
+ fields: [],
+ length: 2,
+ },
+ options: {
+ ...DEFAULT_OPTIONS,
+ content:
+ 'Test content
',
+ defaultContent: 'Test default content',
+ renderMode: RenderMode.DATA,
+ },
+ timeRange: {} as any,
+ timeZone: '',
+ replaceVariables: (str: string) => str,
+ eventBus: {} as any,
+ };
+
+ await act(async () => render());
+ const statuses = screen.getAllByTestId('status-color');
+
+ expect(statuses[0]).toHaveStyle({ backgroundColor: '' });
+ expect(screen.getAllByText('Test content')).toHaveLength(1);
+ });
+
it('Should apply formatted value', async () => {
const replaceVariables = jest.fn((str: string) => str);
const dataFrame = toDataFrame({
diff --git a/src/utils/html.ts b/src/utils/html.ts
index d224a1f..6b89bfe 100644
--- a/src/utils/html.ts
+++ b/src/utils/html.ts
@@ -17,7 +17,7 @@ import hljs from 'highlight.js';
// eslint-disable-next-line @typescript-eslint/naming-convention
import MarkdownIt from 'markdown-it';
-import { PanelOptions, PartialItemConfig } from '../types';
+import { PanelOptions, PartialItemConfig, RenderMode } from '../types';
import { createExecutionCode } from './code';
import { beforeRenderCodeParameters } from './code-parameters';
import { registerHelpers } from './handlebars';
@@ -70,6 +70,28 @@ export const generateHtml = async ({
return replaceVariablesHelper(name, replaceVariables);
});
+ /**
+ * Field status color Helper
+ */
+ handlebars.registerHelper('fieldStatusColor', (fieldName: string, valueIndex?: number) => {
+ if (options.renderMode === RenderMode.DATA) {
+ return '';
+ }
+
+ const field = dataFrame?.fields.find((field) => field.name === fieldName);
+
+ if (field) {
+ /**
+ * Formatted Value
+ */
+ const value = field.values[valueIndex || 0];
+ const formattedValue = field.display?.(value);
+ return formattedValue?.color;
+ }
+
+ return '';
+ });
+
/**
* Variable value
*/