Replace "marked" with "markdown-it" (#13623) (#13667)

* add markdown functional test

* update markdown vis to use markdown-it

* migrate markdown angular filter to markdown-it

* place other uses of marked and remove dependency

* update breaking changes documenation and set linkify to true
This commit is contained in:
Nathan Reese 2017-08-24 07:45:59 -06:00 committed by GitHub
parent 4885ca1743
commit 37a63e5462
14 changed files with 134 additions and 27 deletions

View file

@ -72,3 +72,10 @@ This is no longer the case. Now, only commas are a valid query separator: e.g. `
*Details:* Since 4.3, index patterns could be configured to do a pre-flight field_stats request before a search in order to determine exact indices that could contain matching documents. Elasticsearch now optimizes searches internally in a similar way and has also removed the field_stats API, so this option was removed from Kibana entirely.
*Impact:* No change is required for existing Kibana index patterns. Those previously configured with this option will gracefully use the new Elasticsearch optimizations instead, as will all new index patterns.
[float]
=== Replace markdown parser `marked` with `markdown-it`
*Details:* Starting in 6.0.0, Kibana will use `markdown-it` to parse markdown text. Kibana switched to `markdown-it` because `marked` is no longer actively maintained. Markdown-it supports CommonMark and GFM (GitHub Flavored Markdown) Tables and Strikethrough.
*Impact:* There may be slight changes in parsed markdown. Review markdown as needed.

View file

@ -149,7 +149,7 @@
"less": "2.7.1",
"less-loader": "2.2.3",
"lodash": "3.10.1",
"marked": "0.3.6",
"markdown-it": "8.3.2",
"minimatch": "2.0.10",
"mkdirp": "0.5.1",
"moment": "2.13.0",

View file

@ -53,6 +53,7 @@
ng-model="conf.unsavedValue"
ng-keyup="maybeCancel($event, conf)"
elastic-textarea
data-test-subj="unsavedValueMarkdownTextArea"
></textarea>
<textarea
@ -170,6 +171,7 @@
ng-hide="isDefaultValue(conf)"
aria-label="Clear {{conf.ariaName}}"
class="kuiMenuButton kuiMenuButton--danger kuiMenuButton--iconText"
data-test-subj="advancedSetting-{{conf.name}}-clearButton"
>
<span
aria-hidden="true"

View file

@ -1,3 +1,8 @@
<div ng-controller="KbnMarkdownVisController" class="markdown-vis">
<div ng-bind-html="html" class="markdown-body" ng-style="{'font-size': vis.params.fontSize + 'pt'}" ></div>
<div
ng-bind-html="html"
class="markdown-body"
ng-style="{'font-size': vis.params.fontSize + 'pt'}"
data-test-subj="markdownBody"
></div>
</div>

View file

@ -1,18 +1,17 @@
import marked from 'marked';
import MarkdownIt from 'markdown-it';
import { uiModules } from 'ui/modules';
import 'angular-sanitize';
marked.setOptions({
gfm: true, // Github-flavored markdown
sanitize: true // Sanitize HTML tags
const markdownIt = new MarkdownIt({
html: false,
linkify: true
});
const module = uiModules.get('kibana/markdown_vis', ['kibana', 'ngSanitize']);
module.controller('KbnMarkdownVisController', function ($scope) {
$scope.$watch('vis.params.markdown', function (html) {
if (html) {
$scope.html = marked(html);
$scope.html = markdownIt.render(html);
}
$scope.renderComplete();
});

View file

@ -11,5 +11,11 @@
<input id="markdownVisFontSize" type="range" ng-model="vis.params.fontSize" class="form-control" min="8" max="36" />
</div>
</div>
<textarea id="markdownVisInput" ng-model="vis.params.markdown" class="form-control" rows="20"></textarea>
<textarea
id="markdownVisInput"
ng-model="vis.params.markdown"
class="form-control"
rows="20"
data-test-subj="markdownTextarea"
></textarea>
</div>

View file

@ -1,14 +1,14 @@
import marked from 'marked';
import MarkdownIt from 'markdown-it';
import { uiModules } from 'ui/modules';
import 'angular-sanitize';
marked.setOptions({
gfm: true, // GitHub-flavored markdown
sanitize: true // Sanitize HTML tags
const markdownIt = new MarkdownIt({
html: false,
linkify: true
});
uiModules
.get('kibana', ['ngSanitize'])
.filter('markdown', function ($sanitize) {
return md => md ? $sanitize(marked(md)) : '';
return md => md ? $sanitize(markdownIt.render(md)) : '';
});

View file

@ -1,17 +1,18 @@
import { uiModules } from 'ui/modules';
import _ from 'lodash';
import marked from 'marked';
import MarkdownIt from 'markdown-it';
import { modifyUrl } from 'ui/url';
marked.setOptions({
gfm: true, // Github-flavored markdown
sanitize: true // Sanitize HTML tags
const markdownIt = new MarkdownIt({
html: false,
linkify: true
});
uiModules.get('kibana')
.service('serviceSettings', function ($http, $sanitize, mapConfig, tilemapsConfig, kbnVersion) {
const attributionFromConfig = $sanitize(marked(tilemapsConfig.deprecated.config.options.attribution || ''));
const attributionFromConfig = $sanitize(markdownIt.render(tilemapsConfig.deprecated.config.options.attribution || ''));
const tmsOptionsFromConfig = _.assign({}, tilemapsConfig.deprecated.config.options, { attribution: attributionFromConfig });
const extendUrl = (url, props) => (
@ -69,7 +70,7 @@ uiModules.get('kibana')
const layers = manifest.data.layers.filter(layer => layer.format === 'geojson');
layers.forEach((layer) => {
layer.url = this._extendUrlWithParams(layer.url);
layer.attribution = $sanitize(marked(layer.attribution));
layer.attribution = $sanitize(markdownIt.render(layer.attribution));
});
return layers;
});
@ -93,7 +94,7 @@ uiModules.get('kibana')
}
firstService.attribution = $sanitize(marked(firstService.attribution));
firstService.attribution = $sanitize(markdownIt.render(firstService.attribution));
firstService.subdomains = firstService.subdomains || [];
firstService.url = this._extendUrlWithParams(firstService.url);
return firstService;

View file

@ -1,7 +1,7 @@
import d3 from 'd3';
import _ from 'lodash';
import $ from 'jquery';
import marked from 'marked';
import MarkdownIt from 'markdown-it';
import { NoResults } from 'ui/errors';
import { Binder } from 'ui/binder';
import { VislibLibLayoutLayoutProvider } from './layout/layout';
@ -11,6 +11,11 @@ import { VislibLibAxisProvider } from './axis/axis';
import { VislibGridProvider } from './chart_grid';
import { VislibVisualizationsVisTypesProvider } from '../visualizations/vis_types';
const markdownIt = new MarkdownIt({
html: false,
linkify: true
});
export function VisHandlerProvider(Private) {
const chartTypes = Private(VislibVisualizationsVisTypesProvider);
const Layout = Private(VislibLibLayoutLayoutProvider);
@ -204,7 +209,7 @@ export function VisHandlerProvider(Private) {
div.append('div').attr('class', 'item bottom');
} else {
div.append('h4').text(marked.inlineLexer(message, []));
div.append('h4').text(markdownIt.renderInline(message));
}
$(this.el).trigger('renderComplete');

View file

@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }) {
it('should allow setting advanced settings', async function () {
await PageObjects.settings.clickKibanaSettings();
await PageObjects.settings.setAdvancedSettings('dateFormat:tz', 'America/Phoenix');
await PageObjects.settings.setAdvancedSettingsSelect('dateFormat:tz', 'America/Phoenix');
const advancedSetting = await PageObjects.settings.getAdvancedSettings('dateFormat:tz');
expect(advancedSetting).to.be('America/Phoenix');
});
@ -80,9 +80,24 @@ export default function ({ getService, getPageObjects }) {
});
});
describe('notifications:banner', () => {
it('Should convert notification banner markdown into HTML', async function () {
await PageObjects.settings.clickKibanaSettings();
await PageObjects.settings.setAdvancedSettingsInput('notifications:banner', '# Welcome to Kibana', 'unsavedValueMarkdownTextArea');
const bannerValue = await PageObjects.settings.getAdvancedSettings('notifications:banner');
expect(bannerValue).to.equal('Welcome to Kibana');
});
after('navigate to settings page and clear notifications:banner', async () => {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaSettings();
await PageObjects.settings.clearAdvancedSettings('notifications:banner');
});
});
after(async function () {
await PageObjects.settings.clickKibanaSettings();
await PageObjects.settings.setAdvancedSettings('dateFormat:tz', 'UTC');
await PageObjects.settings.setAdvancedSettingsSelect('dateFormat:tz', 'UTC');
});
});
}

View file

@ -0,0 +1,34 @@
import expect from 'expect.js';
export default function ({ getPageObjects }) {
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
const markdown = `
# Heading 1
<h3>Inline HTML that should not be rendered as html</h3>
`;
describe('visualize app', async () => {
before(async function () {
await PageObjects.common.navigateToUrl('visualize', 'new');
await PageObjects.visualize.clickMarkdownWidget();
await PageObjects.visualize.setMarkdownTxt(markdown);
await PageObjects.visualize.clickGo();
await PageObjects.header.waitUntilLoadingHasFinished();
});
describe('markdown vis', async () => {
it('should render markdown as html', async function () {
const h1Txt = await PageObjects.visualize.getMarkdownBodyDescendentText('h1');
expect(h1Txt).to.equal('Heading 1');
});
it('should not render html in markdown as html', async function () {
const expected = 'Heading 1\n<h3>Inline HTML that should not be rendered as html</h3>';
const actual = await PageObjects.visualize.getMarkdownText();
expect(actual).to.equal(expected);
});
});
});
}

View file

@ -27,6 +27,7 @@ export default function ({ getService, loadTestFile }) {
loadTestFile(require.resolve('./_vertical_bar_chart'));
loadTestFile(require.resolve('./_heatmap_chart'));
loadTestFile(require.resolve('./_point_series_options'));
loadTestFile(require.resolve('./_markdown_vis'));
loadTestFile(require.resolve('./_shared_item'));
});
}

View file

@ -33,11 +33,16 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
}
async getAdvancedSettings(propertyName) {
log.debug('in setAdvancedSettings');
log.debug('in getAdvancedSettings');
return await testSubjects.getVisibleText(`advancedSetting-${propertyName}-currentValue`);
}
async setAdvancedSettings(propertyName, propertyValue) {
async clearAdvancedSettings(propertyName) {
await testSubjects.click(`advancedSetting-${propertyName}-clearButton`);
await PageObjects.header.waitUntilLoadingHasFinished();
}
async setAdvancedSettingsSelect(propertyName, propertyValue) {
await testSubjects.click(`advancedSetting-${propertyName}-editButton`);
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.common.sleep(1000);
@ -48,6 +53,16 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
await PageObjects.header.waitUntilLoadingHasFinished();
}
async setAdvancedSettingsInput(propertyName, propertyValue, inputSelector) {
await testSubjects.click(`advancedSetting-${propertyName}-editButton`);
await PageObjects.header.waitUntilLoadingHasFinished();
const input = await testSubjects.find(inputSelector);
await input.clearValue();
await input.type(propertyValue);
await testSubjects.click(`advancedSetting-${propertyName}-saveButton`);
await PageObjects.header.waitUntilLoadingHasFinished();
}
async toggleAdvancedSettingCheckbox(propertyName) {
await testSubjects.click(`advancedSetting-${propertyName}-editButton`);
await PageObjects.header.waitUntilLoadingHasFinished();

View file

@ -112,6 +112,23 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
defaultFindTimeout * 2);
}
async setMarkdownTxt(markdownTxt) {
const input = await testSubjects.find('markdownTextarea');
await input.clearValue();
await input.type(markdownTxt);
}
async getMarkdownText() {
const markdownContainer = await testSubjects.find('markdownBody');
return markdownContainer.getVisibleText();
}
async getMarkdownBodyDescendentText(selector) {
const markdownContainer = await testSubjects.find('markdownBody');
const element = await find.descendantDisplayedByCssSelector(selector, markdownContainer);
return element.getVisibleText();
}
async setFromTime(timeString) {
const input = await find.byCssSelector('input[ng-model="absolute.from"]', defaultFindTimeout * 2);
await input.clearValue();