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 */