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