Skip to content

Commit fd396d9

Browse files
Clickable external links to docs from dev console (#242026)
## Summary Several links in the Dev Console are currently shown as plain text and aren’t clickable. Most of these appear in the tutorial journey. This PR makes those links clickable within the Dev Console. https://github.com/user-attachments/assets/24229a49-bf63-4aa6-9e7c-bf1fd642ed32 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [x] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ...
1 parent d470358 commit fd396d9

File tree

4 files changed

+71
-0
lines changed

4 files changed

+71
-0
lines changed

src/platform/packages/shared/kbn-monaco/src/monaco_imports.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import 'monaco-editor/esm/vs/editor/contrib/suggest/browser/suggestController.js
2323
import 'monaco-editor/esm/vs/editor/contrib/hover/browser/hover.js'; // Needed for hover
2424
import 'monaco-editor/esm/vs/editor/contrib/parameterHints/browser/parameterHints.js'; // Needed for signature
2525
import 'monaco-editor/esm/vs/editor/contrib/bracketMatching/browser/bracketMatching.js'; // Needed for brackets matching highlight
26+
import 'monaco-editor/esm/vs/editor/contrib/links/browser/links.js'; // Needed for clickable links with Cmd/Ctrl+Click
2627

2728
import 'monaco-editor/esm/vs/editor/contrib/codeAction/browser/codeAction.js';
2829
import 'monaco-editor/esm/vs/editor/contrib/codeAction/browser/codeActionCommands.js';

src/platform/packages/shared/shared-ux/code_editor/impl/code_editor.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,12 @@ export interface CodeEditorProps {
197197
*/
198198
htmlId?: string;
199199

200+
/**
201+
* Enables clickable links in the editor. URLs will be underlined and can be opened
202+
* in a new tab using Cmd/Ctrl+Click. Disabled by default.
203+
*/
204+
links?: boolean;
205+
200206
/**
201207
* Callbacks for when editor is focused/blurred
202208
*/
@@ -239,6 +245,7 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({
239245
enableCustomContextMenu = false,
240246
customContextMenuActions = [],
241247
htmlId,
248+
links = false,
242249
onFocus,
243250
onBlur,
244251
}) => {
@@ -649,6 +656,8 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({
649656
// @ts-expect-error, see https://github.com/microsoft/monaco-editor/issues/3829
650657
'bracketPairColorization.enabled': false,
651658
...options,
659+
// Explicit links prop always takes precedence over any value passed in options
660+
links,
652661
}}
653662
/>
654663
</UseBug223981FixRepositionSuggestWidget>

src/platform/plugins/shared/console/public/application/containers/editor/monaco_editor.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ export const MonacoEditor = ({
300300
accessibilityOverlayEnabled={settings.isAccessibilityOverlayEnabled}
301301
editorDidMount={editorDidMountCallback}
302302
editorWillUnmount={editorWillUnmountCallback}
303+
links={true}
303304
options={{
304305
fontSize: settings.fontSize,
305306
wordWrap: settings.wrapMode === true ? 'on' : 'off',

src/platform/test/functional/apps/console/_misc_console_behavior.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
1818
const retry = getService('retry');
1919
const browser = getService('browser');
2020
const PageObjects = getPageObjects(['common', 'console', 'header']);
21+
const testSubjects = getService('testSubjects');
2122
const toasts = getService('toasts');
2223

2324
describe('misc console behavior', function testMiscConsoleBehavior() {
@@ -261,6 +262,65 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
261262
});
262263
});
263264

265+
describe('clickable links', () => {
266+
let initialWindowHandles: string[];
267+
268+
beforeEach(async () => {
269+
initialWindowHandles = await browser.getAllWindowHandles();
270+
});
271+
272+
afterEach(async () => {
273+
// Close any new tabs that were opened
274+
const currentHandles = await browser.getAllWindowHandles();
275+
if (currentHandles.length > initialWindowHandles.length) {
276+
// Close all new tabs
277+
for (let i = initialWindowHandles.length; i < currentHandles.length; i++) {
278+
await browser.switchToWindow(currentHandles[i]);
279+
await browser.closeCurrentWindow();
280+
}
281+
// Switch back to the original tab
282+
await browser.switchToWindow(initialWindowHandles[0]);
283+
}
284+
});
285+
286+
it('should open URL in new tab when clicking on a link in the input editor', async () => {
287+
await PageObjects.console.clearEditorText();
288+
await PageObjects.console.enterText('# https://www.elastic.co');
289+
290+
// Wait for Monaco to detect and decorate the link
291+
await retry.waitFor('link to be detected by Monaco', async () => {
292+
const inputEditor = await testSubjects.find('consoleMonacoEditor');
293+
const detectedLinks = await inputEditor.findAllByCssSelector('.detected-link');
294+
return detectedLinks.length > 0;
295+
});
296+
297+
const inputEditor = await testSubjects.find('consoleMonacoEditor');
298+
const detectedLink = await inputEditor.findByCssSelector('.detected-link');
299+
300+
// Perform Cmd/Ctrl+Click on the detected link
301+
const modifierKey = browser.keys[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'];
302+
await browser
303+
.getActions()
304+
.keyDown(modifierKey)
305+
.click(detectedLink._webElement)
306+
.keyUp(modifierKey)
307+
.perform();
308+
309+
// Wait for a new tab to open
310+
await retry.waitFor('new tab to open after clicking link', async () => {
311+
const handles = await browser.getAllWindowHandles();
312+
return handles.length > initialWindowHandles.length;
313+
});
314+
315+
const windowHandles = await browser.getAllWindowHandles();
316+
expect(windowHandles.length).to.be.greaterThan(initialWindowHandles.length);
317+
318+
await browser.switchToWindow(windowHandles[windowHandles.length - 1]);
319+
const currentUrl = await browser.getCurrentUrl();
320+
expect(currentUrl).to.contain('elastic.co');
321+
});
322+
});
323+
264324
it('should work fine with a large content', async () => {
265325
await PageObjects.console.clearEditorText();
266326

0 commit comments

Comments
 (0)