diff --git a/CHANGELOG.md b/CHANGELOG.md index f8179ee..f56498e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 5.1.0 (IN PROGRESS) + +### Features / Enhancements + +- Update before render code async and pass markdown instance (#322) + ## 5.0.0 (2024-06-06) ### Breaking changes diff --git a/package.json b/package.json index 7c7d393..d2f26e6 100644 --- a/package.json +++ b/package.json @@ -83,5 +83,5 @@ "test:e2e": "npx playwright test", "upgrade": "npm upgrade --save" }, - "version": "5.0.0" + "version": "5.1.0" } diff --git a/provisioning/dashboards/plugins.json b/provisioning/dashboards/plugins.json new file mode 100644 index 0000000..1de21b1 --- /dev/null +++ b/provisioning/dashboards/plugins.json @@ -0,0 +1,116 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 7, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "marcusolsson-static-datasource", + "uid": "U0HP2Rv4z" + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "afterRender": "", + "content": "- [x] task 1\n- [ ] task 2\n- [x] task 3", + "defaultContent": "The query didn't return any results.", + "editor": { + "format": "auto", + "language": "markdown" + }, + "editors": [ + "helpers", + "styles" + ], + "externalStyles": [], + "helpers": "return import('https://cdn.jsdelivr.net/npm/@mdit/plugin-tasklist').then(({ tasklist }) => {\n context.markdown.use(tasklist, {\n containerClass: 'tasklist',\n itemClass: 'tasklist-item'\n });\n\n return () => {\n console.log('unsubscribe');\n }\n})", + "renderMode": "everyRow", + "styles": ".tasklist {\n list-style: none;\n padding: 0;\n}\n\n.tasklist-item {\n margin: 0;\n display: flex;\n gap: 4px;\n}", + "wrap": true + }, + "pluginVersion": "5.0.0", + "targets": [ + { + "datasource": { + "type": "marcusolsson-static-datasource", + "uid": "U0HP2Rv4z" + }, + "frame": { + "fields": [ + { + "config": {}, + "name": "Field 1", + "type": "string", + "values": [ + "" + ] + } + ], + "meta": {} + }, + "refId": "A" + } + ], + "title": "Tasklist", + "type": "marcusolsson-dynamictext-panel" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Plugins", + "uid": "b7b805e3-5d83-486d-9a58-e10f7dfe2b09", + "version": 12, + "weekStart": "" +} diff --git a/src/components/Row/Row.tsx b/src/components/Row/Row.tsx index 309d5e1..c73fbf4 100644 --- a/src/components/Row/Row.tsx +++ b/src/components/Row/Row.tsx @@ -14,7 +14,7 @@ import React, { useCallback, useEffect, useRef } from 'react'; import { RowItem } from 'types'; import { TEST_IDS } from '../../constants'; -import { afterRenderCodeParameters, createExecutionCode } from '../../helpers'; +import { afterRenderCodeParameters, createExecutionCode } from '../../utils'; /** * Properties diff --git a/src/components/Text/Text.test.tsx b/src/components/Text/Text.test.tsx index 0514e5c..3e1e86a 100644 --- a/src/components/Text/Text.test.tsx +++ b/src/components/Text/Text.test.tsx @@ -1,5 +1,5 @@ import { AppEvents, FieldType, toDataFrame } from '@grafana/data'; -import { render, screen } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import React from 'react'; import { DEFAULT_OPTIONS, TEST_IDS } from '../../constants'; @@ -50,7 +50,7 @@ describe('Text', () => { eventBus: {} as any, }; - render(); + await act(async () => render()); expect(screen.getByTestId(TEST_IDS.text.content)).toBeInTheDocument(); expect(screen.getByTestId(TEST_IDS.text.content)).toHaveTextContent('Test default content'); @@ -79,7 +79,7 @@ describe('Text', () => { eventBus: eventBus as any, }; - render(); + await act(async () => render()); expect(eventBus.publish).toHaveBeenCalledWith('ready', expect.any(HTMLDivElement)); }); @@ -113,7 +113,7 @@ describe('Text', () => { eventBus: eventBus as any, }; - render(); + await act(async () => render()); expect(publish).toHaveBeenCalledTimes(2); expect(publish).toHaveBeenCalledWith({ @@ -148,12 +148,12 @@ describe('Text', () => { eventBus: eventBus as any, }; - const { rerender } = render(); + const { rerender } = await act(async () => render()); /** * Re-render with updated props */ - rerender(); + await act(async () => rerender()); expect(eventBus.publish).toHaveBeenCalledWith('destroy'); }); @@ -189,7 +189,7 @@ describe('Text', () => { eventBus: eventBus as any, }; - render(); + await act(async () => render()); expect(publish).toHaveBeenCalledTimes(2); expect(publish).toHaveBeenCalledWith({ @@ -232,7 +232,7 @@ describe('Text', () => { eventBus: {} as any, }; - render(); + await act(async () => render()); const statuses = screen.getAllByTestId('status'); expect(statuses[0]).toHaveStyle({ backgroundColor: 'green' }); @@ -280,7 +280,7 @@ describe('Text', () => { eventBus: {} as any, }; - render(); + await act(async () => render()); const statuses = screen.getAllByTestId('status'); @@ -339,7 +339,7 @@ describe('Text', () => { eventBus: {} as any, }; - render(); + await act(async () => render()); expect(screen.getAllByTestId(TEST_IDS.text.content)[0]).toBeInTheDocument(); expect(screen.getAllByTestId(TEST_IDS.text.content)[0]).toHaveTextContent('Value 1 1.05 MB'); }); @@ -377,7 +377,7 @@ describe('Text', () => { eventBus: {} as any, }; - render(); + await act(async () => render()); expect(screen.getAllByTestId(TEST_IDS.text.content)[0]).toBeInTheDocument(); expect(screen.getAllByTestId(TEST_IDS.text.content)[0]).toHaveTextContent('Value 1 1048576'); }); @@ -418,7 +418,7 @@ describe('Text', () => { eventBus: {} as any, }; - render(); + await act(async () => render()); expect(screen.getAllByTestId(TEST_IDS.text.content)[0]).toBeInTheDocument(); expect(screen.getAllByTestId(TEST_IDS.text.content)[0]).toHaveTextContent('Test content'); @@ -448,7 +448,7 @@ describe('Text', () => { eventBus: {} as any, }; - render(); + await act(async () => render()); expect(screen.getAllByText('Test content')).toHaveLength(1); }); @@ -487,7 +487,7 @@ describe('Text', () => { eventBus: {} as any, }; - render(); + await act(async () => render()); expect(screen.getAllByText('Test content')).toHaveLength(1); }); @@ -537,7 +537,7 @@ describe('Text', () => { eventBus: {} as any, }; - render(); + await act(async () => render()); expect(screen.getAllByRole('row')[1]).toHaveTextContent('Erik'); expect(screen.getAllByRole('row')[2]).toHaveTextContent('Natasha'); @@ -599,7 +599,7 @@ describe('Text', () => { eventBus: {} as any, }; - render(); + await act(async () => render()); expect(screen.getAllByRole('row')[1]).toHaveTextContent('Erik'); expect(screen.getAllByRole('row')[2]).toHaveTextContent('Natasha'); diff --git a/src/components/Text/Text.tsx b/src/components/Text/Text.tsx index e464a11..a49ff69 100644 --- a/src/components/Text/Text.tsx +++ b/src/components/Text/Text.tsx @@ -16,8 +16,8 @@ import { Alert, useStyles2, useTheme2 } from '@grafana/ui'; import React, { useCallback, useEffect, useState } from 'react'; import { TEST_IDS } from '../../constants'; -import { generateHtml } from '../../helpers'; import { PanelOptions, RenderMode, RowItem } from '../../types'; +import { generateHtml } from '../../utils'; import { Row } from '../Row'; import { getStyles } from './Text.styles'; @@ -127,9 +127,9 @@ export const Text: React.FC = ({ * HTML */ const getHtml = useCallback( - (htmlData: Record, content: string) => { + async (htmlData: Record, content: string) => { return { - ...generateHtml({ + ...(await generateHtml({ data: htmlData, content, helpers: options.helpers, @@ -143,7 +143,7 @@ export const Text: React.FC = ({ notifySuccess, notifyError, theme, - }), + })), data: htmlData, }; }, @@ -153,101 +153,106 @@ export const Text: React.FC = ({ useEffect(() => { let unsubscribeFn: undefined | unknown; - /** - * Reset error before html generation - */ - setError(null); - - try { - if (!frame?.length) { - /** - * For empty frame - */ - const { html, unsubscribe } = getHtml({}, options.defaultContent); - - setRows([ - { - html, - data: {}, - panelData, - dataFrame: frame, - }, - ]); - unsubscribeFn = unsubscribe; - } else { - /** - * Frame returned - */ - const frames = options.renderMode === RenderMode.DATA ? panelData.series : [frame]; - const templateData = frames.map((frame) => - frame.fields.reduce( - (acc, { config, name, values, display }) => { - values.forEach((value, i) => { - /** - * Formatted Value - */ - const formattedValue = display?.(value); - - /** - * Status Color - */ - const statusColor = options.status === name ? formattedValue?.color : acc[i]?.statusColor; - - /** - * Set Value and Status Color - */ - acc[i] = { - ...acc[i], - [config.displayName || name]: - config.unit && formattedValue ? formattedValueToString(formattedValue) : value, - statusColor, - }; - }); - - return acc; - }, - [] as Array> - ) - ); + const run = async () => { + /** + * Reset error before html generation + */ + setError(null); - if (options.renderMode === RenderMode.EVERY_ROW) { + try { + if (!frame?.length) { /** - * For every row in data frame + * For empty frame */ - const rows = templateData[0].map((row) => getHtml(row, options.content)); - setRows( - rows.map(({ html, data }) => ({ + const { html, unsubscribe } = await getHtml({}, options.defaultContent); + + setRows([ + { html, - data, + data: {}, panelData, dataFrame: frame, - })) - ); - - /** - * Call unsubscribe for all rows - */ - unsubscribeFn = () => { - rows.forEach(({ unsubscribe }) => { - if (unsubscribe && typeof unsubscribe === 'function') { - unsubscribe(); - } - }); - }; + }, + ]); + unsubscribeFn = unsubscribe; } else { /** - * For whole data frame + * Frame returned */ - const data = options.renderMode === RenderMode.DATA ? templateData : templateData[0]; - const { html, unsubscribe } = getHtml({ data }, options.content); - setRows([{ html, data, panelData, dataFrame: frame }]); + const frames = options.renderMode === RenderMode.DATA ? panelData.series : [frame]; + const templateData = frames.map((frame) => + frame.fields.reduce( + (acc, { config, name, values, display }) => { + values.forEach((value, i) => { + /** + * Formatted Value + */ + const formattedValue = display?.(value); - unsubscribeFn = unsubscribe; + /** + * Status Color + */ + const statusColor = options.status === name ? formattedValue?.color : acc[i]?.statusColor; + + /** + * Set Value and Status Color + */ + acc[i] = { + ...acc[i], + [config.displayName || name]: + config.unit && formattedValue ? formattedValueToString(formattedValue) : value, + statusColor, + }; + }); + + return acc; + }, + [] as Array> + ) + ); + + if (options.renderMode === RenderMode.EVERY_ROW) { + /** + * For every row in data frame + */ + const rows = await Promise.all(templateData[0].map((row) => getHtml(row, options.content))); + + setRows( + rows.map(({ html, data }) => ({ + html, + data, + panelData, + dataFrame: frame, + })) + ); + + /** + * Call unsubscribe for all rows + */ + unsubscribeFn = () => { + rows.forEach(({ unsubscribe }) => { + if (unsubscribe && typeof unsubscribe === 'function') { + unsubscribe(); + } + }); + }; + } else { + /** + * For whole data frame + */ + const data = options.renderMode === RenderMode.DATA ? templateData : templateData[0]; + const { html, unsubscribe } = await getHtml({ data }, options.content); + setRows([{ html, data, panelData, dataFrame: frame }]); + + unsubscribeFn = unsubscribe; + } } + } catch (e) { + setError(e); } - } catch (e) { - setError(e); - } + }; + + run(); return () => { if (unsubscribeFn && typeof unsubscribeFn === 'function') { diff --git a/src/components/TextPanel/TextPanel.test.tsx b/src/components/TextPanel/TextPanel.test.tsx index 6a7d6d0..8a98656 100644 --- a/src/components/TextPanel/TextPanel.test.tsx +++ b/src/components/TextPanel/TextPanel.test.tsx @@ -222,17 +222,19 @@ describe('Panel', () => { } `; - it('Should execute code for empty data frame', () => { - render( - getComponent({ - options: { - ...defaultOptions, - defaultContent: 'hello', - helpers, - }, - replaceVariables: (str: string) => str, - eventBus, - }) + it('Should execute code for empty data frame', async () => { + await act(async () => + render( + getComponent({ + options: { + ...defaultOptions, + defaultContent: 'hello', + helpers, + }, + replaceVariables: (str: string) => str, + eventBus, + }) + ) ); /** @@ -247,17 +249,19 @@ describe('Panel', () => { expect(unsubscribe).not.toHaveBeenCalled(); }); - it('Should call unsubscribe function on component update', () => { - const { rerender } = render( - getComponent({ - options: { - ...defaultOptions, - defaultContent: 'hello', - helpers, - }, - replaceVariables: (str: string) => str, - eventBus, - }) + it('Should call unsubscribe function on component update', async () => { + const { rerender } = await act(async () => + render( + getComponent({ + options: { + ...defaultOptions, + defaultContent: 'hello', + helpers, + }, + replaceVariables: (str: string) => str, + eventBus, + }) + ) ); /** @@ -265,16 +269,18 @@ describe('Panel', () => { */ expect(unsubscribe).not.toHaveBeenCalled(); - rerender( - getComponent({ - options: { - ...defaultOptions, - defaultContent: 'hello', - helpers, - }, - replaceVariables: (str: string) => str, - eventBus, - }) + await act(async () => + rerender( + getComponent({ + options: { + ...defaultOptions, + defaultContent: 'hello', + helpers, + }, + replaceVariables: (str: string) => str, + eventBus, + }) + ) ); /** @@ -283,7 +289,7 @@ describe('Panel', () => { expect(unsubscribe).toHaveBeenCalledTimes(1); }); - it('Should call unsubscribe function for every row', () => { + it('Should call unsubscribe function for every row', async () => { const values = ['111', '222']; const data: any = { series: [ @@ -299,18 +305,20 @@ describe('Panel', () => { }), ], }; - const { rerender } = render( - getComponent({ - options: { - ...defaultOptions, - content: 'hello', - helpers, - renderMode: RenderMode.EVERY_ROW, - }, - replaceVariables: (str: string) => str, - data, - eventBus, - }) + const { rerender } = await act(async () => + render( + getComponent({ + options: { + ...defaultOptions, + content: 'hello', + helpers, + renderMode: RenderMode.EVERY_ROW, + }, + replaceVariables: (str: string) => str, + data, + eventBus, + }) + ) ); /** @@ -319,18 +327,20 @@ describe('Panel', () => { expect(unsubscribe).not.toHaveBeenCalled(); expect(subscribe).toHaveBeenCalledTimes(values.length); - rerender( - getComponent({ - options: { - ...defaultOptions, - content: 'hello', - helpers, - renderMode: RenderMode.EVERY_ROW, - }, - replaceVariables: (str: string) => str, - data, - eventBus, - }) + await act(async () => + rerender( + getComponent({ + options: { + ...defaultOptions, + content: 'hello', + helpers, + renderMode: RenderMode.EVERY_ROW, + }, + replaceVariables: (str: string) => str, + data, + eventBus, + }) + ) ); /** @@ -339,7 +349,7 @@ describe('Panel', () => { expect(unsubscribe).toHaveBeenCalledTimes(values.length); }); - it('Should call unsubscribe function for data frame', () => { + it('Should call unsubscribe function for data frame', async () => { const values = ['111', '222']; const data: any = { series: [ @@ -355,18 +365,20 @@ describe('Panel', () => { }), ], }; - const { rerender } = render( - getComponent({ - options: { - ...defaultOptions, - content: 'hello', - helpers, - renderMode: RenderMode.ALL_ROWS, - }, - replaceVariables: (str: string) => str, - data, - eventBus, - }) + const { rerender } = await act(async () => + render( + getComponent({ + options: { + ...defaultOptions, + content: 'hello', + helpers, + renderMode: RenderMode.ALL_ROWS, + }, + replaceVariables: (str: string) => str, + data, + eventBus, + }) + ) ); /** @@ -375,18 +387,20 @@ describe('Panel', () => { expect(unsubscribe).not.toHaveBeenCalled(); expect(subscribe).toHaveBeenCalledTimes(1); - rerender( - getComponent({ - options: { - ...defaultOptions, - content: 'hello', - helpers, - renderMode: RenderMode.EVERY_ROW, - }, - replaceVariables: (str: string) => str, - data, - eventBus, - }) + await act(async () => + rerender( + getComponent({ + options: { + ...defaultOptions, + content: 'hello', + helpers, + renderMode: RenderMode.EVERY_ROW, + }, + replaceVariables: (str: string) => str, + data, + eventBus, + }) + ) ); /** @@ -395,20 +409,22 @@ describe('Panel', () => { expect(unsubscribe).toHaveBeenCalledTimes(1); }); - it('Should show execution error', () => { + it('Should show execution error', async () => { /** * Render with invalid helpers function */ - const { rerender } = render( - getComponent({ - options: { - ...defaultOptions, - defaultContent: 'hello', - helpers: `abc()`, - }, - replaceVariables: (str: string) => str, - eventBus, - }) + const { rerender } = await act(async () => + render( + getComponent({ + options: { + ...defaultOptions, + defaultContent: 'hello', + helpers: `abc()`, + }, + replaceVariables: (str: string) => str, + eventBus, + }) + ) ); /** @@ -421,16 +437,18 @@ describe('Panel', () => { /** * Render without errors */ - rerender( - getComponent({ - options: { - ...defaultOptions, - defaultContent: 'hello', - helpers, - }, - replaceVariables: (str: string) => str, - eventBus, - }) + await act(async () => + rerender( + getComponent({ + options: { + ...defaultOptions, + defaultContent: 'hello', + helpers, + }, + replaceVariables: (str: string) => str, + eventBus, + }) + ) ); /** @@ -444,20 +462,22 @@ describe('Panel', () => { expect(screen.getByTestId(TEST_IDS.text.content)).toBeInTheDocument(); }); - it('Should show custom execution error', () => { + it('Should show custom execution error', async () => { /** * Render with invalid helpers function */ - render( - getComponent({ - options: { - ...defaultOptions, - defaultContent: 'hello', - helpers: `throw 'abc'`, - }, - replaceVariables: (str: string) => str, - eventBus, - }) + await act(async () => + render( + getComponent({ + options: { + ...defaultOptions, + defaultContent: 'hello', + helpers: `throw 'abc'`, + }, + replaceVariables: (str: string) => str, + eventBus, + }) + ) ); /** @@ -470,22 +490,24 @@ describe('Panel', () => { }); describe('Frames', () => { - it('Should show field frame if frames are several', () => { - render( - getComponent({ - options: { ...defaultOptions, defaultContent: 'hello' }, - replaceVariables: (str: string) => str, - data: { - series: [ - { - refId: 'A', - }, - { - refId: 'B', - }, - ], - } as any, - }) + it('Should show field frame if frames are several', async () => { + await act(async () => + render( + getComponent({ + options: { ...defaultOptions, defaultContent: 'hello' }, + replaceVariables: (str: string) => str, + data: { + series: [ + { + refId: 'A', + }, + { + refId: 'B', + }, + ], + } as any, + }) + ) ); const fieldFrame = screen.getByTestId(TEST_IDS.panel.fieldFrame); @@ -493,27 +515,29 @@ describe('Panel', () => { expect(fieldFrame).toHaveValue('A'); }); - it('Should change frame', () => { - render( - getComponent({ - options: { ...defaultOptions, defaultContent: 'hello' }, - replaceVariables: (str: string) => str, - data: { - series: [ - { - refId: 'A', - }, - { - refId: 'B', - }, - ], - } as any, - }) + it('Should change frame', async () => { + await act(async () => + render( + getComponent({ + options: { ...defaultOptions, defaultContent: 'hello' }, + replaceVariables: (str: string) => str, + data: { + series: [ + { + refId: 'A', + }, + { + refId: 'B', + }, + ], + } as any, + }) + ) ); const fieldFrame = screen.getByTestId(TEST_IDS.panel.fieldFrame); - fireEvent.change(fieldFrame, { target: { value: 'B' } }); + await act(async () => fireEvent.change(fieldFrame, { target: { value: 'B' } })); expect(fieldFrame).toHaveValue('B'); }); diff --git a/src/constants/editor.ts b/src/constants/editor.ts index b07bc77..441511d 100644 --- a/src/constants/editor.ts +++ b/src/constants/editor.ts @@ -1,6 +1,6 @@ import { CodeEditorSuggestionItem } from '@grafana/ui'; -import { afterRenderCodeParameters, beforeRenderCodeParameters } from '../helpers'; +import { afterRenderCodeParameters, beforeRenderCodeParameters } from '../utils'; /** * Supported Languages diff --git a/src/hooks/useExternalResources.ts b/src/hooks/useExternalResources.ts index d1f97bf..8e1a836 100644 --- a/src/hooks/useExternalResources.ts +++ b/src/hooks/useExternalResources.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; -import { createResourcesManager } from '../helpers'; import { Resource, ResourceType } from '../types'; +import { createResourcesManager } from '../utils'; /** * External Scripts Manager diff --git a/src/helpers/code-parameters.ts b/src/utils/code-parameters.ts similarity index 93% rename from src/helpers/code-parameters.ts rename to src/utils/code-parameters.ts index f27136a..0a6cffe 100644 --- a/src/helpers/code-parameters.ts +++ b/src/utils/code-parameters.ts @@ -13,6 +13,8 @@ import { TimeZone } from '@grafana/schema'; import { CodeEditorSuggestionItemKind } from '@grafana/ui'; import { CodeParameterItem, CodeParametersBuilder } from '@volkovlabs/components'; import handlebars from 'handlebars'; +// eslint-disable-next-line @typescript-eslint/naming-convention +import MarkdownIt from 'markdown-it'; /** * Render Code Parameters @@ -57,6 +59,7 @@ export const beforeRenderCodeParameters = new CodeParametersBuilder({ items: { ...renderCodeParameters.items, handlebars: new CodeParameterItem('Handlebars library.'), + markdown: new CodeParameterItem('Markdown-it instance.'), }, }); diff --git a/src/helpers/code.test.ts b/src/utils/code.test.ts similarity index 100% rename from src/helpers/code.test.ts rename to src/utils/code.test.ts diff --git a/src/helpers/code.ts b/src/utils/code.ts similarity index 100% rename from src/helpers/code.ts rename to src/utils/code.ts diff --git a/src/helpers/externalResource.ts b/src/utils/external-resources.ts similarity index 100% rename from src/helpers/externalResource.ts rename to src/utils/external-resources.ts diff --git a/src/helpers/handlebars.test.ts b/src/utils/handlebars.test.ts similarity index 100% rename from src/helpers/handlebars.test.ts rename to src/utils/handlebars.test.ts diff --git a/src/helpers/handlebars.ts b/src/utils/handlebars.ts similarity index 100% rename from src/helpers/handlebars.ts rename to src/utils/handlebars.ts diff --git a/src/helpers/html.test.tsx b/src/utils/html.test.tsx similarity index 84% rename from src/helpers/html.test.tsx rename to src/utils/html.test.tsx index d49a065..f2fe541 100644 --- a/src/helpers/html.test.tsx +++ b/src/utils/html.test.tsx @@ -58,14 +58,14 @@ describe('HTML helpers', () => { expect(textUtil.sanitize).toHaveBeenCalledWith(content); }); - it('Should wrap lines', () => { + it('Should wrap lines', async () => { const content = ` line 1 line 2 `; - const { html } = generateHtml({ + const { html } = await generateHtml({ content, options, } as any); @@ -75,14 +75,14 @@ describe('HTML helpers', () => { expect(screen.getByTestId('root').querySelector('pre')).toBeInTheDocument(); }); - it('Should wrap lines', () => { + it('Should not wrap lines', async () => { const content = ` line 1 line 2 `; - const { html } = generateHtml({ + const { html } = await generateHtml({ content, options: { ...options, @@ -142,4 +142,20 @@ describe('HTML helpers', () => { expect(variableHandler('varName')).toEqual([]); expect(variableValueHandler('varName')).toEqual('varName'); }); + + it('Should wait until promise in code resolved', async () => { + const content = ''; + + const { unsubscribe } = await generateHtml({ + content, + options, + helpers: ` + return Promise.resolve(() => 123) + `, + } as any); + + expect(unsubscribe).toBeDefined(); + expect(unsubscribe).toBeInstanceOf(Function); + expect((unsubscribe as Function)()).toEqual(123); + }); }); diff --git a/src/helpers/html.ts b/src/utils/html.ts similarity index 92% rename from src/helpers/html.ts rename to src/utils/html.ts index d217978..e4a79d3 100644 --- a/src/helpers/html.ts +++ b/src/utils/html.ts @@ -31,7 +31,7 @@ registerHelpers(handlebars); /** * Generate HTML */ -export const generateHtml = ({ +export const generateHtml = async ({ data, content, helpers, @@ -59,7 +59,7 @@ export const generateHtml = ({ notifySuccess: (payload: AlertPayload) => void; notifyError: (payload: AlertErrorPayload) => void; theme: GrafanaTheme2; -}): { html: string; unsubscribe?: unknown } => { +}): Promise<{ html: string; unsubscribe?: unknown }> => { /** * Variable */ @@ -74,6 +74,20 @@ export const generateHtml = ({ return replaceVariables(`${name}`); }); + /** + * Create Markdown with Syntax Highlighting + */ + const md = new MarkdownIt({ + html: true, + highlight: (str, lang) => { + if (lang && hljs.getLanguage(lang)) { + return hljs.highlight(str, { language: lang }).value; + } + + return ''; + }, + }); + /** * Unsubscribe */ @@ -88,10 +102,11 @@ export const generateHtml = ({ /** * Unsubscribe */ - unsubscribe = func( + const result = func( beforeRenderCodeParameters.create({ data, handlebars: handlebars, + markdown: md, panelData, dataFrame, grafana: { @@ -108,6 +123,12 @@ export const generateHtml = ({ }), helpers ); + + if (result instanceof Promise) { + unsubscribe = await result; + } else { + unsubscribe = result; + } } /** @@ -116,20 +137,6 @@ export const generateHtml = ({ const template = handlebars.compile(content); const markdown = template(data); - /** - * Create Markdown with Syntax Highlighting - */ - const md = new MarkdownIt({ - html: true, - highlight: (str, lang) => { - if (lang && hljs.getLanguage(lang)) { - return hljs.highlight(str, { language: lang }).value; - } - - return ''; - }, - }); - /** * Render Markdown */ diff --git a/src/helpers/index.ts b/src/utils/index.ts similarity index 78% rename from src/helpers/index.ts rename to src/utils/index.ts index b27b9e7..1d2b19c 100644 --- a/src/helpers/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,6 @@ export * from './code'; export * from './code-parameters'; -export * from './externalResource'; +export * from './external-resources'; export * from './handlebars'; export * from './html'; export * from './variable'; diff --git a/src/helpers/variable.test.ts b/src/utils/variable.test.ts similarity index 100% rename from src/helpers/variable.test.ts rename to src/utils/variable.test.ts diff --git a/src/helpers/variable.ts b/src/utils/variable.ts similarity index 100% rename from src/helpers/variable.ts rename to src/utils/variable.ts