Skip to content

Commit 3ba6cc0

Browse files
sejliabbyhu2000opensearch-changeset-bot[bot]
authored
Fix open link in new tab from table field (opensearch-project#11458)
* open tab in new window Signed-off-by: abbyhu2000 <abigailhu2000@gmail.com> * Fix open new tab from table Signed-off-by: Sean Li <lnse@amazon.com> * Changeset file for PR opensearch-project#11458 created/updated * Revert "open tab in new window" This reverts commit bb9060e. --------- Signed-off-by: abbyhu2000 <abigailhu2000@gmail.com> Signed-off-by: Sean Li <lnse@amazon.com> Co-authored-by: abbyhu2000 <abigailhu2000@gmail.com> Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com>
1 parent 60165e9 commit 3ba6cc0

3 files changed

Lines changed: 91 additions & 0 deletions

File tree

changelogs/fragments/11458.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fix:
2+
- Fix open link in new tab from table field ([#11458](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/11458))

src/plugins/vis_type_table/public/components/table_vis_dynamic_table.test.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,4 +372,77 @@ describe('TableVisDynamicTable', () => {
372372
}),
373373
});
374374
});
375+
376+
it('should sanitize HTML content using dompurify', () => {
377+
const tableWithLink: FormattedTableContext = {
378+
...mockTable,
379+
rows: [{ col1: 'link', col2: 10 }],
380+
formattedColumns: [
381+
{
382+
id: 'col1',
383+
title: 'Column 1',
384+
formatter: {
385+
convert: () => '<a href="http://example.com" target="_blank">Link</a>',
386+
} as any,
387+
filterable: false,
388+
},
389+
{
390+
id: 'col2',
391+
title: 'Column 2',
392+
formatter: { convert: (v: any) => v } as any,
393+
filterable: false,
394+
},
395+
],
396+
};
397+
398+
const { container } = render(
399+
<TableVisDynamicTable
400+
table={tableWithLink}
401+
visConfig={mockVisConfig}
402+
event={mockHandlers.event}
403+
uiState={mockUiState}
404+
/>
405+
);
406+
407+
const anchorElement = container.querySelector('a');
408+
expect(anchorElement).toHaveAttribute('href', 'http://example.com');
409+
expect(anchorElement).toHaveAttribute('target', '_blank');
410+
expect(anchorElement).toHaveAttribute('rel', 'noopener noreferrer');
411+
});
412+
413+
it('should handle unsafe HTML content gracefully', () => {
414+
const tableWithUnsafeHtml: FormattedTableContext = {
415+
...mockTable,
416+
rows: [{ col1: 'unsafe', col2: 10 }],
417+
formattedColumns: [
418+
{
419+
id: 'col1',
420+
title: 'Column 1',
421+
formatter: {
422+
convert: () => '<img src="x" onerror="alert(1)">',
423+
} as any,
424+
filterable: false,
425+
},
426+
{
427+
id: 'col2',
428+
title: 'Column 2',
429+
formatter: { convert: (v: any) => v } as any,
430+
filterable: false,
431+
},
432+
],
433+
};
434+
435+
const { container } = render(
436+
<TableVisDynamicTable
437+
table={tableWithUnsafeHtml}
438+
visConfig={mockVisConfig}
439+
event={mockHandlers.event}
440+
uiState={mockUiState}
441+
/>
442+
);
443+
444+
const imgElement = container.querySelector('img');
445+
expect(imgElement).toHaveAttribute('src', 'x');
446+
expect(imgElement).not.toHaveAttribute('onerror');
447+
});
375448
});

src/plugins/vis_type_table/public/components/table_vis_dynamic_table.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,22 @@ import {
2020
} from '@elastic/eui';
2121
import dompurify from 'dompurify';
2222
import { orderBy } from 'lodash';
23+
24+
dompurify.addHook('uponSanitizeAttribute', (node, event) => {
25+
if (event.attrName === 'target') {
26+
event.forceKeepAttr = true;
27+
}
28+
});
29+
30+
dompurify.addHook('afterSanitizeElements', (node) => {
31+
if (node instanceof Element && node.tagName?.toUpperCase() === 'A') {
32+
const target = node.getAttribute('target');
33+
if (target && target !== '_self') {
34+
node.setAttribute('rel', 'noopener noreferrer');
35+
}
36+
}
37+
});
38+
2339
import { i18n } from '@osd/i18n';
2440
import './table_vis_dynamic_table.scss';
2541
import { FormattedTableContext } from '../table_vis_response_handler';

0 commit comments

Comments
 (0)