Skip to content

Commit

Permalink
Kibana 7.0.0 URL field formatter doesn't render relative hyperlinks p…
Browse files Browse the repository at this point in the history
…roperly
  • Loading branch information
alexwizp committed Dec 18, 2019
1 parent 63379ac commit 1ed5526
Show file tree
Hide file tree
Showing 17 changed files with 160 additions and 144 deletions.
11 changes: 3 additions & 8 deletions src/fixtures/stubbed_logstash_index_pattern.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import StubIndexPattern from 'test_utils/stub_index_pattern';
import stubbedLogstashFields from 'fixtures/logstash_fields';

import { getKbnFieldType } from '../plugins/data/common';
import { mockUiSettings } from '../legacy/ui/public/new_platform/new_platform.karma_mock';
import { npSetup } from '../legacy/ui/public/new_platform/new_platform.karma_mock';

export default function stubbedLogstashIndexPatternService() {
const mockLogstashFields = stubbedLogstashFields();
Expand All @@ -41,13 +41,8 @@ export default function stubbedLogstashIndexPatternService() {
};
});

const indexPattern = new StubIndexPattern(
'logstash-*',
cfg => cfg,
'time',
fields,
mockUiSettings
);
const indexPattern = new StubIndexPattern('logstash-*', cfg => cfg, 'time', fields, npSetup.core);

indexPattern.id = 'logstash-*';
indexPattern.isTimeNanosBased = () => false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ describe('Table Vis - Controller', () => {
(cfg: any) => cfg,
'time',
stubFields,
npStart.core.uiSettings
npStart.core
);
});

Expand Down
9 changes: 1 addition & 8 deletions src/legacy/ui/public/agg_types/buckets/terms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
* under the License.
*/

import chrome from 'ui/chrome';
import { noop } from 'lodash';
import { i18n } from '@kbn/i18n';
import { SearchSource, getRequestInspectorStats, getResponseInspectorStats } from '../../courier';
Expand Down Expand Up @@ -80,14 +79,8 @@ export const termsBucketAgg = new BucketAggType({
if (val === '__missing__') {
return bucket.params.missingBucketLabel;
}
const parsedUrl = {
origin: window.location.origin,
pathname: window.location.pathname,
basePath: chrome.getBasePath(),
};
const converter = bucket.params.field.format.getConverterFor(type);

return converter(val, undefined, undefined, parsedUrl);
return bucket.params.field.format.convert(val, type);
};
},
} as FieldFormat;
Expand Down
19 changes: 13 additions & 6 deletions src/legacy/ui/public/new_platform/new_platform.karma_mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,18 @@ export const mockUiSettings = {
'format:defaultTypeMap': {},
};

export const npSetup = {
core: {
chrome: {},
uiSettings: mockUiSettings,
const mockCore = {
chrome: {},
uiSettings: mockUiSettings,
http: {
basePath: {
get: sinon.fake.returns(''),
},
},
};

export const npSetup = {
core: mockCore,
plugins: {
usageCollection: {
allowTrackUserAgent: sinon.fake(),
Expand Down Expand Up @@ -95,7 +102,7 @@ export const npSetup = {
getSavedQueryCount: sinon.fake(),
},
},
fieldFormats: getFieldFormatsRegistry(mockUiSettings),
fieldFormats: getFieldFormatsRegistry(mockCore),
},
share: {
register: () => {},
Expand Down Expand Up @@ -224,7 +231,7 @@ export const npStart = {
history: sinon.fake(),
},
},
fieldFormats: getFieldFormatsRegistry(mockUiSettings),
fieldFormats: getFieldFormatsRegistry(mockCore),
},
share: {
toggleShareContextMenu: () => {},
Expand Down
6 changes: 3 additions & 3 deletions src/legacy/ui/public/saved_objects/__tests__/saved_object.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { createSavedObjectClass } from '../saved_object';
import StubIndexPattern from 'test_utils/stub_index_pattern';
import { npStart } from 'ui/new_platform';
import { InvalidJSONProperty } from '../../../../../plugins/kibana_utils/public';
import { mockUiSettings } from '../../new_platform/new_platform.karma_mock';
import { npSetup } from '../../new_platform/new_platform.karma_mock';

const getConfig = cfg => cfg;

Expand Down Expand Up @@ -310,7 +310,7 @@ describe('Saved Object', function() {
getConfig,
null,
[],
mockUiSettings
npSetup.core
);
indexPattern.title = indexPattern.id;
savedObject.searchSource.setField('index', indexPattern);
Expand Down Expand Up @@ -700,7 +700,7 @@ describe('Saved Object', function() {
getConfig,
null,
[],
mockUiSettings
npSetup.core
);
indexPattern.title = indexPattern.id;
savedObject.searchSource.setField('index', indexPattern);
Expand Down
53 changes: 17 additions & 36 deletions src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { AggConfig, Vis } from 'ui/vis';
import { npStart } from 'ui/new_platform';
import { SerializedFieldFormat } from 'src/plugins/expressions/public';

import { IFieldFormatId, FieldFormat } from '../../../../../../plugins/data/public';
import { IFieldFormatId, FieldFormat, ContentType } from '../../../../../../plugins/data/public';

import { tabifyGetColumns } from '../../../agg_response/tabify/_get_columns';
import { DateRangeKey, convertDateRangeToString } from '../../../agg_types/buckets/date_range';
Expand Down Expand Up @@ -129,42 +129,23 @@ export const getFormat: FormatFactory = mapping => {
});
return new IpRangeFormat();
} else if (isTermsFieldFormat(mapping) && mapping.params) {
const params = mapping.params;
const { params } = mapping;
const convert = (val: string, type: ContentType) => {
const format = getFieldFormat(params.id, mapping.params);

if (val === '__other__') {
return params.otherBucketLabel;
}
if (val === '__missing__') {
return params.missingBucketLabel;
}

return format.convert(val, type);
};

return {
getConverterFor: (type: string) => {
const format = getFieldFormat(params.id, mapping.params);
return (val: string) => {
if (val === '__other__') {
return params.otherBucketLabel;
}
if (val === '__missing__') {
return params.missingBucketLabel;
}
const parsedUrl = {
origin: window.location.origin,
pathname: window.location.pathname,
basePath: npStart.core.http.basePath,
};
// @ts-ignore
return format.convert(val, undefined, undefined, parsedUrl);
};
},
convert: (val: string, type: string) => {
const format = getFieldFormat(params.id, mapping.params);
if (val === '__other__') {
return params.otherBucketLabel;
}
if (val === '__missing__') {
return params.missingBucketLabel;
}
const parsedUrl = {
origin: window.location.origin,
pathname: window.location.pathname,
basePath: npStart.core.http.basePath,
};
// @ts-ignore
return format.convert(val, type, undefined, parsedUrl);
},
convert,
getConverterFor: (type: ContentType) => (val: string) => convert(val, type),
} as FieldFormat;
} else {
return getFieldFormat(id, mapping.params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ const getConvertFn = (
format: IFieldFormat,
convert?: HtmlContextTypeConvert
): HtmlContextTypeConvert => {
const fallbackHtml: HtmlContextTypeConvert = (value, field, hit) => {
const fallbackHtml: HtmlContextTypeConvert = (value, options = {}) => {
const { field, hit } = options;
const formatted = escape(format.convert(value, 'text'));

return !field || !hit || !hit.highlight || !hit.highlight[field.name]
Expand All @@ -43,27 +44,23 @@ export const setup = (
): HtmlContextTypeConvert => {
const convert = getConvertFn(format, htmlContextTypeConvert);

const recurse: HtmlContextTypeConvert = (value, field, hit, meta) => {
const recurse: HtmlContextTypeConvert = (value, options = {}) => {
if (value == null) {
return asPrettyString(value);
}

if (!value || !isFunction(value.map)) {
return convert.call(format, value, field, hit, meta);
return convert.call(format, value, options);
}

const subValues = value.map((v: any) => {
return recurse(v, field, hit, meta);
});
const useMultiLine = subValues.some((sub: string) => {
return sub.indexOf('\n') > -1;
});
const subValues = value.map((v: any) => recurse(v, options));
const useMultiLine = subValues.some((sub: string) => sub.indexOf('\n') > -1);

return subValues.join(',' + (useMultiLine ? '\n' : ' '));
};

const wrap: HtmlContextTypeConvert = (value, field, hit, meta) => {
return `<span ng-non-bindable>${recurse(value, field, hit, meta)}</span>`;
const wrap: HtmlContextTypeConvert = (value, options) => {
return `<span ng-non-bindable>${recurse(value, options)}</span>`;
};

return wrap;
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/data/common/field_formats/converters/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export class SourceFormat extends FieldFormat {

textConvert: TextContextTypeConvert = value => JSON.stringify(value);

htmlConvert: HtmlContextTypeConvert = (value, field, hit) => {
htmlConvert: HtmlContextTypeConvert = (value, options = {}) => {
const { field, hit } = options;

if (!field) {
const converter = this.getConverterFor('text') as Function;

Expand Down
46 changes: 22 additions & 24 deletions src/plugins/data/common/field_formats/converters/url.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,64 +169,64 @@ describe('UrlFormat', () => {

describe('whitelist', () => {
test('should assume a relative url if the value is not in the whitelist without a base path', () => {
const url = new UrlFormat({});
const parsedUrl = {
origin: 'http://kibana',
basePath: '',
};
const url = new UrlFormat({ parsedUrl });
const converter = url.getConverterFor(HTML_CONTEXT_TYPE) as Function;

expect(converter('www.elastic.co', null, null, parsedUrl)).toBe(
expect(converter('www.elastic.co')).toBe(
'<span ng-non-bindable><a href="http://kibana/app/www.elastic.co" target="_blank" rel="noopener noreferrer">www.elastic.co</a></span>'
);

expect(converter('elastic.co', null, null, parsedUrl)).toBe(
expect(converter('elastic.co')).toBe(
'<span ng-non-bindable><a href="http://kibana/app/elastic.co" target="_blank" rel="noopener noreferrer">elastic.co</a></span>'
);

expect(converter('elastic', null, null, parsedUrl)).toBe(
expect(converter('elastic')).toBe(
'<span ng-non-bindable><a href="http://kibana/app/elastic" target="_blank" rel="noopener noreferrer">elastic</a></span>'
);

expect(converter('ftp://elastic.co', null, null, parsedUrl)).toBe(
expect(converter('ftp://elastic.co')).toBe(
'<span ng-non-bindable><a href="http://kibana/app/ftp://elastic.co" target="_blank" rel="noopener noreferrer">ftp://elastic.co</a></span>'
);
});

test('should assume a relative url if the value is not in the whitelist with a basepath', () => {
const url = new UrlFormat({});
const parsedUrl = {
origin: 'http://kibana',
basePath: '/xyz',
};
const url = new UrlFormat({ parsedUrl });
const converter = url.getConverterFor(HTML_CONTEXT_TYPE) as Function;

expect(converter('www.elastic.co', null, null, parsedUrl)).toBe(
expect(converter('www.elastic.co')).toBe(
'<span ng-non-bindable><a href="http://kibana/xyz/app/www.elastic.co" target="_blank" rel="noopener noreferrer">www.elastic.co</a></span>'
);

expect(converter('elastic.co', null, null, parsedUrl)).toBe(
expect(converter('elastic.co')).toBe(
'<span ng-non-bindable><a href="http://kibana/xyz/app/elastic.co" target="_blank" rel="noopener noreferrer">elastic.co</a></span>'
);

expect(converter('elastic', null, null, parsedUrl)).toBe(
expect(converter('elastic')).toBe(
'<span ng-non-bindable><a href="http://kibana/xyz/app/elastic" target="_blank" rel="noopener noreferrer">elastic</a></span>'
);

expect(converter('ftp://elastic.co', null, null, parsedUrl)).toBe(
expect(converter('ftp://elastic.co')).toBe(
'<span ng-non-bindable><a href="http://kibana/xyz/app/ftp://elastic.co" target="_blank" rel="noopener noreferrer">ftp://elastic.co</a></span>'
);
});

test('should rely on parsedUrl', () => {
const url = new UrlFormat({});
const parsedUrl = {
origin: 'http://kibana.host.com',
basePath: '/abc',
};
const url = new UrlFormat({ parsedUrl });
const converter = url.getConverterFor(HTML_CONTEXT_TYPE) as Function;

expect(converter('../app/kibana', null, null, parsedUrl)).toBe(
expect(converter('../app/kibana')).toBe(
'<span ng-non-bindable><a href="http://kibana.host.com/abc/app/../app/kibana" target="_blank" rel="noopener noreferrer">../app/kibana</a></span>'
);
});
Expand All @@ -244,54 +244,52 @@ describe('UrlFormat', () => {
});

test('should support multiple types of relative urls', () => {
const url = new UrlFormat({});
const parsedUrl = {
origin: 'http://kibana.host.com',
pathname: '/nbc/app/kibana#/discover',
basePath: '/nbc',
};
const url = new UrlFormat({ parsedUrl });
const converter = url.getConverterFor(HTML_CONTEXT_TYPE) as Function;

expect(converter('#/foo', null, null, parsedUrl)).toBe(
expect(converter('#/foo')).toBe(
'<span ng-non-bindable><a href="http://kibana.host.com/nbc/app/kibana#/discover#/foo" target="_blank" rel="noopener noreferrer">#/foo</a></span>'
);

expect(converter('/nbc/app/kibana#/discover', null, null, parsedUrl)).toBe(
expect(converter('/nbc/app/kibana#/discover')).toBe(
'<span ng-non-bindable><a href="http://kibana.host.com/nbc/app/kibana#/discover" target="_blank" rel="noopener noreferrer">/nbc/app/kibana#/discover</a></span>'
);

expect(converter('../foo/bar', null, null, parsedUrl)).toBe(
expect(converter('../foo/bar')).toBe(
'<span ng-non-bindable><a href="http://kibana.host.com/nbc/app/../foo/bar" target="_blank" rel="noopener noreferrer">../foo/bar</a></span>'
);
});

test('should support multiple types of urls w/o basePath', () => {
const url = new UrlFormat({});
const parsedUrl = {
origin: 'http://kibana.host.com',
pathname: '/app/kibana',
};
const url = new UrlFormat({ parsedUrl });
const converter = url.getConverterFor(HTML_CONTEXT_TYPE) as Function;

expect(converter('10.22.55.66', null, null, parsedUrl)).toBe(
expect(converter('10.22.55.66')).toBe(
'<span ng-non-bindable><a href="http://kibana.host.com/app/10.22.55.66" target="_blank" rel="noopener noreferrer">10.22.55.66</a></span>'
);

expect(
converter('http://www.domain.name/app/kibana#/dashboard/', null, null, parsedUrl)
).toBe(
expect(converter('http://www.domain.name/app/kibana#/dashboard/')).toBe(
'<span ng-non-bindable><a href="http://www.domain.name/app/kibana#/dashboard/" target="_blank" rel="noopener noreferrer">http://www.domain.name/app/kibana#/dashboard/</a></span>'
);

expect(converter('/app/kibana', null, null, parsedUrl)).toBe(
expect(converter('/app/kibana')).toBe(
'<span ng-non-bindable><a href="http://kibana.host.com/app/kibana" target="_blank" rel="noopener noreferrer">/app/kibana</a></span>'
);

expect(converter('kibana#/dashboard/', null, null, parsedUrl)).toBe(
expect(converter('kibana#/dashboard/')).toBe(
'<span ng-non-bindable><a href="http://kibana.host.com/app/kibana#/dashboard/" target="_blank" rel="noopener noreferrer">kibana#/dashboard/</a></span>'
);

expect(converter('#/dashboard/', null, null, parsedUrl)).toBe(
expect(converter('#/dashboard/')).toBe(
'<span ng-non-bindable><a href="http://kibana.host.com/app/kibana#/dashboard/" target="_blank" rel="noopener noreferrer">#/dashboard/</a></span>'
);
});
Expand Down
Loading

0 comments on commit 1ed5526

Please sign in to comment.