Copilot flagged two stragglers on editors.md where the previous file-by-file conversion stopped halfway. Sweeping for the same pattern across the active content tree found 76 bare relative internal links total — 14 in this PR's already-modified files (Copilot's two plus twelve more) and 62 in unchanged files. Why the build doesn't catch this ───────────────────────────────── `onBrokenLinks: 'throw'` (set in this PR) only validates *file-based* markdown references — links whose URL ends in `.md` / `.mdx`. Those go through Docusaurus's file resolver, which can prove the target exists. Bare relative URL paths like `[Foo](../foo)` skip that resolver entirely; Docusaurus emits them as raw hrefs. The browser then resolves them against the *current* page URL, and for trailing-slash routes that almost always lands in the wrong directory. Page navigates client-side and 404s. The linkinator job in CI *can* catch these, but it's `continue-on-error: true` so findings are advisory. What this commit does ────────────────────── 1. Fix all 76 bare relative internal links across the active docs tree by appending `.md` to each one (preserving anchors / query strings). All 76 targets resolved to real files; no link targets changed, only the form of the reference. 2. Fix the component-page generator. 54 of the 76 bare links lived in two auto-generated index files (`components/ui/index.mdx` and `components/design-system/index.mdx`). The next regeneration would have undone the manual fixes without this. The two emission sites in `generate-superset-components.mjs` now emit `.md`-suffixed links; comment at the call site explains why. 3. Add `docs/scripts/lint-docs-links.mjs` — fast source-level linter that scans `.md`/`.mdx` files under the active content trees (skipping `versioned_docs/` snapshots) and fails if it finds any markdown link whose URL starts with `./` or `../` and does not end in `.md`/`.mdx`. Excludes asset paths (.png, .json, etc.) and ignores fenced code blocks. Wired up as `yarn lint:docs-links`. 4. Add a `Lint docs links` step to `superset-docs-verify.yml`, running before the build step so PRs that introduce the pattern fail in seconds rather than at build-time / not at all. Blocking, not advisory — exactly the gap linkinator's `continue-on-error` leaves open. Verified ──────── - `yarn lint:docs-links` exits 0 on the cleaned tree - Re-introducing one bare link makes the linter report the exact file:line with the offending URL, exit code 1 - All 76 originally-flagged targets resolved to real `.md` / `.mdx` files; only the form of the reference changed
6.9 KiB
title, sidebar_position
| title | sidebar_position |
|---|---|
| Editors | 2 |
Editor Contributions
Extensions can replace Superset's default text editors with custom implementations. This allows you to provide enhanced editing experiences using alternative editor frameworks like Monaco, CodeMirror, or custom solutions.
Overview
Superset uses text editors in various places throughout the application:
| Language | Locations |
|---|---|
sql |
SQL Lab, Metric/Filter Popovers |
json |
Dashboard Properties, Annotation Modal, Theme Modal |
css |
Dashboard Properties, CSS Template Modal |
markdown |
Dashboard Markdown component |
yaml |
Template Params Editor |
javascript |
Custom JavaScript editor contexts |
python |
Custom Python editor contexts |
text |
Plain text editor contexts |
By registering an editor for a language, your extension replaces the default Ace editor in all locations that use that language.
Implementing an Editor
Your editor component must implement the EditorProps interface and expose an EditorHandle via forwardRef. For the complete interface definitions, see @apache-superset/core/api/editors.ts.
Key EditorProps
interface EditorProps {
/** Controlled value */
value: string;
/** Content change handler */
onChange: (value: string) => void;
/** Language mode for syntax highlighting */
language: EditorLanguage;
/** Keyboard shortcuts to register */
hotkeys?: EditorHotkey[];
/** Callback when editor is ready with imperative handle */
onReady?: (handle: EditorHandle) => void;
/** Host-specific context (e.g., database info from SQL Lab) */
metadata?: Record<string, unknown>;
// ... additional props for styling, annotations, etc.
}
Key EditorHandle Methods
interface EditorHandle {
/** Focus the editor */
focus(): void;
/** Get the current editor content */
getValue(): string;
/** Get the current cursor position */
getCursorPosition(): Position;
/** Move the cursor to a specific position */
moveCursorToPosition(position: Position): void;
/** Set the selection range */
setSelection(selection: Range): void;
/** Scroll to a specific line */
scrollToLine(line: number): void;
// ... additional methods for text manipulation, annotations, etc.
}
Example Implementation
Here's an example of a Monaco-based SQL editor implementing the key interfaces shown above:
MonacoSQLEditor.tsx
import { forwardRef, useRef, useImperativeHandle, useEffect } from 'react';
import * as monaco from 'monaco-editor';
import type { editors } from '@apache-superset/core';
const MonacoSQLEditor = forwardRef<editors.EditorHandle, editors.EditorProps>(
(props, ref) => {
const { value, onChange, hotkeys, onReady } = props;
const containerRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
// Implement EditorHandle interface
const handle: editors.EditorHandle = {
focus: () => editorRef.current?.focus(),
getValue: () => editorRef.current?.getValue() ?? '',
getCursorPosition: () => {
const pos = editorRef.current?.getPosition();
return { line: (pos?.lineNumber ?? 1) - 1, column: (pos?.column ?? 1) - 1 };
},
// ... implement remaining methods
};
useImperativeHandle(ref, () => handle, []);
useEffect(() => {
if (!containerRef.current) return;
const editor = monaco.editor.create(containerRef.current, { value, language: 'sql' });
editorRef.current = editor;
editor.onDidChangeModelContent(() => onChange(editor.getValue()));
// Register hotkeys
hotkeys?.forEach(hotkey => {
editor.addAction({
id: hotkey.name,
label: hotkey.name,
run: () => hotkey.exec(handle),
});
});
onReady?.(handle);
return () => editor.dispose();
}, []);
return <div ref={containerRef} style={{ height: '100%', width: '100%' }} />;
},
);
export default MonacoSQLEditor;
index.tsx
Register the editor at module load time from your extension's entry point:
import { editors } from '@apache-superset/core';
import MonacoSQLEditor from './MonacoSQLEditor';
editors.registerEditor(
{
id: 'my-extension.monaco-sql',
name: 'Monaco SQL Editor',
languages: ['sql'],
},
MonacoSQLEditor,
);
Handling Hotkeys
Superset passes keyboard shortcuts via the hotkeys prop. Each hotkey includes an exec function that receives the EditorHandle:
interface EditorHotkey {
name: string;
key: string; // e.g., "Ctrl-Enter", "Alt-Shift-F"
description?: string;
exec: (handle: EditorHandle) => void;
}
Your editor must register these hotkeys with your editor framework and call exec(handle) when triggered.
Keywords
Superset passes static autocomplete suggestions via the keywords prop. These include table names, column names, and SQL functions based on the current database context:
interface EditorKeyword {
name: string;
value?: string; // Text to insert (defaults to name)
meta?: string; // Category like "table", "column", "function"
score?: number; // Sorting priority
}
Your editor should convert these to your framework's completion format and register them for autocomplete.
Completion Providers
For dynamic autocomplete (e.g., fetching suggestions as the user types), implement and register a CompletionProvider via the EditorHandle:
const provider: CompletionProvider = {
id: 'my-sql-completions',
triggerCharacters: ['.', ' '],
provideCompletions: async (content, position, context) => {
// Use context.metadata for database info
// Return array of CompletionItem
return [
{ label: 'SELECT', insertText: 'SELECT', kind: 'keyword' },
// ...
];
},
};
// Register during editor initialization
const disposable = handle.registerCompletionProvider(provider);
Next Steps
- SQL Lab Extension Points - Learn about other SQL Lab customizations
- Contribution Types - Explore other contribution types
- Development - Set up your development environment