Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b00e508
Add inline API dropdowns to Lens chart examples and advanced scenarios
florent-leborgne Mar 26, 2026
386eaa4
Use :applies_to: directive option syntax in dropdowns
florent-leborgne Mar 26, 2026
b382807
Fix JSON quoting in formula examples
florent-leborgne Mar 26, 2026
eddc2ed
Fix API payloads to pass integration tests
florent-leborgne Mar 26, 2026
e8fa16c
Fix all API payloads: 50/50 pass integration tests
florent-leborgne Mar 26, 2026
5233e60
Fix broken API dropdowns in metric chart examples
florent-leborgne Mar 30, 2026
cef833e
Fix broken dropdowns and add intro text with code callouts
florent-leborgne Mar 30, 2026
88ce187
[Lens API] Update examples for size→limit and alignments→labels renames
florent-leborgne Apr 1, 2026
d843616
fix(lens-api): correct callout numbering and stale field labels acros…
florent-leborgne May 13, 2026
384bbc7
style(lens-api): remove column-80 padding from callout markers
florent-leborgne May 13, 2026
1047d0b
fix(lens-api): move callout markers to block-level openers where appl…
florent-leborgne May 13, 2026
16c6ce9
fix(lens-api): update waffle callout descriptions to match block-leve…
florent-leborgne May 13, 2026
219f045
fix(lens-api): add color mapping to waffle status breakdown and fix c…
florent-leborgne May 13, 2026
d9ccebd
fix(lens-api): move color block after filters array in waffle status …
florent-leborgne May 13, 2026
e7bbab7
Move callout markers from sub-fields to operation lines in waffle and…
florent-leborgne May 13, 2026
f8102f9
Add missing color configurations to 6 chart API examples
florent-leborgne May 13, 2026
1b2cf1e
Move treemap callouts from fields lines to operation lines
florent-leborgne May 13, 2026
8d4f472
Fix treemap Response status per host payload to match UI example
florent-leborgne May 13, 2026
54b8626
Update treemap example UI bullets to reflect other bucket, percentage…
florent-leborgne May 13, 2026
52404b7
Fix Flights by carrier treemap: mode percentage, update UI bullets
florent-leborgne May 19, 2026
72688f8
Fix treemap API payload examples to match UI examples
florent-leborgne May 19, 2026
18441f2
Fix table API examples and remove filter from formula metrics
florent-leborgne May 19, 2026
108584d
Fix region map payloads: add ems boundaries and join field
florent-leborgne May 19, 2026
ee111e7
fix(heat-map): align API examples with actual dashboard export payloads
florent-leborgne May 19, 2026
8b7739e
fix(tag-cloud): align API examples with export payloads
florent-leborgne May 19, 2026
b85aa31
fix(gauge): align API examples with export payloads
florent-leborgne May 19, 2026
ca40efd
fix(gauge): remove Bytes vs quota example (no screenshot), fix server…
florent-leborgne May 19, 2026
46046a6
fix(mosaic): align API examples with export payloads
florent-leborgne May 19, 2026
b1606f7
fix(mosaic): correct group_by/group_breakdown_by axis mapping
florent-leborgne May 19, 2026
dfc8fd4
Fix pie chart API payload examples against validated dashboard export
florent-leborgne May 19, 2026
443a39e
Fix donut chart example to match screenshot (tags.keyword, absolute v…
florent-leborgne May 19, 2026
baa3061
Fix metric chart API payload examples against validated dashboard exp…
florent-leborgne May 19, 2026
128c384
Add payload validator script and fix Vale warnings across chart pages
florent-leborgne May 19, 2026
d5a5f78
test: add Console+curl tabs to first waffle dropdown (validated payload)
florent-leborgne May 21, 2026
cdd0c55
Revert "test: add Console+curl tabs to first waffle dropdown (validat…
florent-leborgne May 21, 2026
807c0a3
feat: add Console tab alongside curl in all API payload dropdowns
florent-leborgne May 21, 2026
c3ba7c8
Merge branch 'main' into lens-api-chart-examples
florent-leborgne May 21, 2026
53ce5ce
refactor(verify): extract from Console tab instead of curl
florent-leborgne May 21, 2026
18de2dd
fix: clean shell escapes in Console tab; 43/43 payloads validated
florent-leborgne May 21, 2026
f46d4d7
Merge branch 'main' into lens-api-chart-examples
florent-leborgne May 21, 2026
3904c5b
Merge branch 'main' into lens-api-chart-examples
florent-leborgne May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 242 additions & 0 deletions .github/scripts/add-console-tabs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
#!/usr/bin/env python3
"""
Transform API dropdown blocks to include Console + curl tabs.

Each block of the form:
:::...{dropdown} Create this chart using the API
:applies_to: ...

<intro>

```bash
curl ... -d '{
<JSON with callouts>
}'
```

1. callout 1
2. callout 2

For more information, refer to the [Visualizations API](...).
:::...

Becomes:
:::::::{dropdown} Create this chart using the API
:applies_to: ...

<intro>

:::::{tab-set}

:::{tab-item} Console
:sync: api-console
```console
POST kbn://api/visualizations
{
<JSON with callouts>
}
```

1. callout 1
2. callout 2
:::

:::{tab-item} curl
:sync: api-curl
```bash
curl ... -d '{
<JSON with callouts>
}'
```

1. callout 1
2. callout 2
:::

:::::

For more information, refer to the [Visualizations API](...).
:::::::
"""

import re
import sys
from pathlib import Path


DROPDOWN_TITLE = "Create this chart using the API"

# Matches the opening fence: one or more colons followed by {dropdown}
OPEN_RE = re.compile(r"^(:{3,})\{dropdown\} " + re.escape(DROPDOWN_TITLE) + r"\s*$")


def transform_file(path: Path) -> str:
text = path.read_text()
lines = text.splitlines(keepends=True)

out = []
i = 0
while i < len(lines):
line = lines[i]
m = OPEN_RE.match(line)
if not m:
out.append(line)
i += 1
continue

# Found a dropdown opening — collect the entire block
open_colons = m.group(1) # e.g. ":::" or "::::" or ":::::"
close_fence = open_colons + "\n"

block_lines = [line]
i += 1
while i < len(lines):
block_lines.append(lines[i])
if lines[i] == close_fence:
i += 1
break
i += 1

transformed = transform_block(block_lines)
out.append(transformed)

return "".join(out)


def transform_block(block_lines: list[str]) -> str:
"""Transform a single dropdown block."""
raw = "".join(block_lines)

# ---- Extract the :applies_to: option line (may be absent) ----
applies_re = re.compile(r"^(:applies_to:.*)\n", re.MULTILINE)
applies_m = applies_re.search(raw)
applies_line = (applies_m.group(0) if applies_m else "")

# ---- Split the block body into: intro | bash block | callout list | api link ----
# Pattern: optional intro text, ```bash ... ```, numbered list, "For more..."
bash_block_re = re.compile(
r"(```bash\n.*?```\n)",
re.DOTALL,
)
bash_m = bash_block_re.search(raw)
if not bash_m:
# No bash block found — return unchanged
print(f" WARNING: no bash block found, skipping block", file=sys.stderr)
return raw

bash_block = bash_m.group(1)

# ---- Extract JSON from curl -d '{ ... }' ----
# The payload sits between -d '{\n and \n}' (end of block)
json_body = extract_json_from_curl(bash_block)
if json_body is None:
print(f" WARNING: could not extract JSON from curl block, skipping", file=sys.stderr)
return raw

# ---- Extract numbered callout list ----
# Everything between end of bash block and "For more information"
after_bash = raw[bash_m.end():]
for_more_re = re.compile(r"(For more information.*?\n)", re.DOTALL)
for_more_m = for_more_re.search(after_bash)

if for_more_m:
callout_section = after_bash[: for_more_m.start()]
for_more_line = for_more_m.group(1)
else:
# No "For more" line — treat remaining content (before closing fence) as callouts
callout_section = after_bash
for_more_line = ""

# ---- Extract the intro text (between :applies_to: / opening line and ```bash) ----
# intro = everything after the opening line (and optional :applies_to:) up to ```bash
header_end = applies_m.end() if applies_m else raw.index("\n") + 1
intro = raw[header_end : bash_m.start()]

# ---- Build the Console JSON block ----
console_block = f"```console\nPOST kbn://api/visualizations\n{{\n{json_body}\n}}\n```\n"

# ---- Assemble the new block ----
new_open = ":::::::" + "{dropdown} " + DROPDOWN_TITLE + "\n"
new_close = ":::::::\n"
tab_set_open = "\n:::::{tab-set}\n"
tab_set_close = "\n:::::\n"
# 4-colon tab-items so that any :::{note} (3 colons) inside content stays nested
console_tab_open = "\n::::{tab-item} Console\n:sync: api-console\n"
console_tab_close = "::::\n"
curl_tab_open = "\n::::{tab-item} curl\n:sync: api-curl\n"
curl_tab_close = "::::\n"

result = (
new_open
+ applies_line
+ intro
+ tab_set_open
+ console_tab_open
+ console_block
+ callout_section
+ console_tab_close
+ curl_tab_open
+ bash_block
+ callout_section
+ curl_tab_close
+ tab_set_close
+ "\n"
+ for_more_line
+ new_close
)
return result


def extract_json_from_curl(bash_block: str) -> str | None:
"""
Extract the JSON body from a curl -d '{ ... }' block.
Returns the lines between -d '{ and }' (exclusive), preserving indentation.
"""
# Find -d '{\n
start_re = re.compile(r" -d '\{\n")
start_m = start_re.search(bash_block)
if not start_m:
return None

rest = bash_block[start_m.end():]

# The JSON ends at a line that is exactly "}'\n" or "}'\n```"
end_re = re.compile(r"\n\}'(\n|$)")
end_m = end_re.search(rest)
if not end_m:
return None

json_body = rest[: end_m.start()]
# Convert shell single-quote escapes to literal characters so the Console
# tab contains valid JSON (e.g. shift='\''1w'\'' → shift='1w').
json_body = json_body.replace("'\\''", "'")
json_body = json_body.replace("'''", "")
return json_body


def main():
import argparse

parser = argparse.ArgumentParser(description="Add Console tabs to API dropdowns")
parser.add_argument("files", nargs="+", help="Markdown files to transform")
parser.add_argument("--dry-run", action="store_true", help="Print output, don't write")
args = parser.parse_args()

for filepath in args.files:
path = Path(filepath)
if not path.exists():
print(f"File not found: {filepath}", file=sys.stderr)
continue

print(f"Processing {path.name}...", file=sys.stderr)
result = transform_file(path)

if args.dry_run:
print(result)
else:
path.write_text(result)
print(f" Written: {filepath}", file=sys.stderr)


if __name__ == "__main__":
main()
Loading
Loading