blob: a1e9c6618997fe29dc7cea0e0c4de0afb43a8fb3 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { styled, t } from '@superset-ui/core';
import { SafeMarkdown } from '@superset-ui/core/components';
import { extendedDayjs as dayjs } from '@superset-ui/core/utils/dates';
import Handlebars from 'handlebars';
import { useMemo, useState } from 'react';
import { isPlainObject } from 'lodash';
import Helpers from 'just-handlebars-helpers';
import HandlebarsGroupBy from 'handlebars-group-by';
export interface HandlebarsViewerProps {
templateSource: string;
data: any;
}
export const HandlebarsViewer = ({
templateSource,
data,
}: HandlebarsViewerProps) => {
const [renderedTemplate, setRenderedTemplate] = useState('');
const [error, setError] = useState('');
const appContainer = document.getElementById('app');
const { common } = JSON.parse(
appContainer?.getAttribute('data-bootstrap') || '{}',
);
const htmlSanitization = common?.conf?.HTML_SANITIZATION ?? true;
const htmlSchemaOverrides =
common?.conf?.HTML_SANITIZATION_SCHEMA_EXTENSIONS || {};
useMemo(() => {
try {
const template = Handlebars.compile(templateSource);
const result = template(data);
setRenderedTemplate(result);
setError('');
} catch (error) {
setRenderedTemplate('');
setError(error.message);
}
}, [templateSource, data]);
const Error = styled.pre`
white-space: pre-wrap;
`;
if (error) {
return <Error>{error}</Error>;
}
if (renderedTemplate) {
return (
<SafeMarkdown
source={renderedTemplate}
htmlSanitization={htmlSanitization}
htmlSchemaOverrides={htmlSchemaOverrides}
/>
);
}
return <p>{t('Loading...')}</p>;
};
// usage: {{ dateFormat my_date format="MMMM YYYY" }}
Handlebars.registerHelper('dateFormat', function (context, block) {
const f = block.hash.format || 'YYYY-MM-DD';
return dayjs(context).format(f);
});
// usage: {{ }}
Handlebars.registerHelper('stringify', (obj: any, obj2: any) => {
// calling without an argument
if (obj2 === undefined)
throw Error('Please call with an object. Example: `stringify myObj`');
return isPlainObject(obj) ? JSON.stringify(obj) : String(obj);
});
Handlebars.registerHelper(
'formatNumber',
function (number: any, locale = 'en-US') {
if (typeof number !== 'number') {
return number;
}
return number.toLocaleString(locale);
},
);
// usage: {{parseJson jsonString}}
Handlebars.registerHelper('parseJson', (jsonString: string) => {
try {
return JSON.parse(jsonString);
} catch (error) {
if (error instanceof Error) {
error.message = `Invalid JSON string: ${error.message}`;
throw error;
}
throw new Error(`Invalid JSON string: ${String(error)}`);
}
});
Helpers.registerHelpers(Handlebars);
HandlebarsGroupBy.register(Handlebars);