Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .github/workflows/docs_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ on:
paths:
- 'docs/**'
- 'packages/reflex-components-core/src/reflex_components_core/core/upload.py'
- 'packages/reflex-site-shared/**'
- 'packages/integrations-docs/**'
- '.github/workflows/docs_tests.yml'
push:
branches: ["main"]
paths:
- 'docs/**'
- 'packages/reflex-components-core/src/reflex_components_core/core/upload.py'
- 'packages/reflex-site-shared/**'
- 'packages/integrations-docs/**'
- '.github/workflows/docs_tests.yml'

permissions:
Expand Down
2 changes: 2 additions & 0 deletions docs/app/reflex_docs/docgen_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
text_comp,
)
from reflex_site_shared.constants import REFLEX_ASSETS_CDN
from reflex_site_shared.integrations import rewrite_integration_doc_images_in_source

# ---------------------------------------------------------------------------
# Exec environment — mirrors reflex_docgen's module-based exec mechanism
Expand Down Expand Up @@ -847,6 +848,7 @@ def render_docgen_document(
``None`` if no FAQ block is found.
"""
source = Path(actual_filepath).read_text(encoding="utf-8")
source = rewrite_integration_doc_images_in_source(source)
source, faq_script = _extract_faqs_jsonld(source)
transformer = ReflexDocTransformer(
virtual_filepath=str(virtual_filepath), filename=str(actual_filepath)
Expand Down
71 changes: 71 additions & 0 deletions docs/app/tests/test_integrations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Tests for reflex_site_shared.integrations URL helpers."""

import re
from pathlib import Path

import integrations_docs
import pytest
from reflex_site_shared.integrations import (
RAW_DOC_IMAGES_PREFIX,
_integrations_doc_images_url,
rewrite_integration_doc_images_in_source,
)

DOC_IMAGES_DIR = Path(integrations_docs.__file__).parent / "images" / "docs"


@pytest.fixture(autouse=True)
def _backend_only(monkeypatch: pytest.MonkeyPatch) -> None:
"""Skip the symlink step so the helpers run without filesystem side effects."""
monkeypatch.setenv("REFLEX_BACKEND_ONLY", "true")


def test_rewrite_source_replaces_all_doc_image_urls() -> None:
"""Every raw GitHub doc-image URL in the source is rewritten to its local asset URL."""
source = (
f"![one]({RAW_DOC_IMAGES_PREFIX}okta_auth_1.png)\n"
f"text\n"
f"![two]({RAW_DOC_IMAGES_PREFIX}descope.webp)\n"
)
rewritten = rewrite_integration_doc_images_in_source(source)
local_prefix = _integrations_doc_images_url()
assert RAW_DOC_IMAGES_PREFIX not in rewritten
assert f"({local_prefix}okta_auth_1.png)" in rewritten
assert f"({local_prefix}descope.webp)" in rewritten


def test_rewrite_source_without_doc_images_unchanged() -> None:
"""Source without any raw GitHub doc-image URL is returned unchanged."""
source = (
"# Title\n\n"
"![logo](https://example.com/logo.svg)\n"
"![aws](https://raw.githubusercontent.com/reflex-dev/integrations-docs/refs/heads/main/images/logos/light/aws.svg)\n"
)
assert rewrite_integration_doc_images_in_source(source) == source


def test_doc_image_references_exist_locally() -> None:
"""Every screenshot URL in the docs must resolve to a local image, since the rewrite serves it locally."""
missing = [
f"{md.name}: {image_name}"
for md in sorted(integrations_docs.DOCS_DIR.glob("*.md"))
for image_name in re.findall(
re.escape(RAW_DOC_IMAGES_PREFIX) + r"([^)\s]+)", md.read_text()
)
if not (DOC_IMAGES_DIR / image_name).is_file()
]
assert not missing, f"docs reference images missing from images/docs/: {missing}"


def test_doc_image_references_use_image_syntax() -> None:
"""Screenshot URLs must use ![alt](url) image syntax, not a [!alt](url) link typo."""
malformed = [
md.name
for md in sorted(integrations_docs.DOCS_DIR.glob("*.md"))
if re.search(
r"(?<!!)\[[^\]]*\]\(" + re.escape(RAW_DOC_IMAGES_PREFIX), md.read_text()
)
]
assert not malformed, (
f"docs embed screenshots as links instead of images: {malformed}"
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ The **HubSpot Integration** allows your app to use [HubSpot](https://www.hubspot
4. Copy the **Access Token** from the Auth tab.
Comment thread
carlosabadia marked this conversation as resolved.
* **Example:** `pat-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`

[!HubSpot](https://raw.githubusercontent.com/reflex-dev/integrations-docs/refs/heads/main/images/docs/hubspot.webp)

## Step 2: Configure the Integration in Your App

1. Go to **Integrations → Add HubSpot** in your app settings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Video and streaming inputs are not yet supported.
3. Click the `deploy` button, select `Images` and then select `Integrate with my app or website`.
4. Copy the `workspace_name` and `workflow_id` from the provided code snippet.

[!Roboflow](https://raw.githubusercontent.com/reflex-dev/integrations-docs/refs/heads/main/images/docs/roboflow.webp)
![Roboflow](https://raw.githubusercontent.com/reflex-dev/integrations-docs/refs/heads/main/images/docs/roboflow.webp)

## Step 3: Configure the Integration in Your App

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Helpers for resolving integration logo asset URLs from the local integrations_docs package."""
"""Helpers for resolving integration asset URLs from the local integrations_docs package."""

from pathlib import Path
from typing import Literal
Expand All @@ -9,29 +9,36 @@
from reflex_base.environment import EnvironmentVariables
from reflex_base.utils.decorator import once

RAW_DOC_IMAGES_PREFIX = (
"https://raw.githubusercontent.com/reflex-dev/integrations-docs/"
"refs/heads/main/images/docs/"
)

@once
def _integrations_logos_url() -> str:
"""Symlink the integrations_docs logos into assets/external and return the public URL.

def _integrations_images_url(subdir: str) -> str:
"""Symlink integrations_docs/images/<subdir> into assets/external and return its public URL.

Args:
subdir: The image subdirectory to expose (e.g. ``"logos"`` or ``"docs"``).

Returns:
The public frontend URL prefix for the integrations_docs logos directory.
The public frontend URL prefix for the integrations_docs images subdirectory.

Raises:
RuntimeError: If the integrations_docs logos directory cannot be found.
RuntimeError: If the integrations_docs images directory cannot be found.
"""
src = Path(integrations_docs.__file__).parent / "images" / "logos"
src = Path(integrations_docs.__file__).parent / "images" / subdir
if not src.is_dir():
msg = f"integrations_docs logos directory not found at {src}"
msg = f"integrations_docs images directory not found at {src}"
raise RuntimeError(msg)
relative_path = f"/{constants.Dirs.EXTERNAL_APP_ASSETS}/integrations_docs/logos/"
relative_path = f"/{constants.Dirs.EXTERNAL_APP_ASSETS}/integrations_docs/{subdir}/"
if not EnvironmentVariables.REFLEX_BACKEND_ONLY.get():
dst = (
Path.cwd()
/ constants.Dirs.APP_ASSETS
/ constants.Dirs.EXTERNAL_APP_ASSETS
/ "integrations_docs"
/ "logos"
/ subdir
)
dst.parent.mkdir(parents=True, exist_ok=True)
if not dst.is_symlink() or dst.resolve() != src.resolve():
Expand All @@ -41,6 +48,26 @@ def _integrations_logos_url() -> str:
return get_config().prepend_frontend_path(relative_path)


@once
def _integrations_logos_url() -> str:
"""Return the public URL prefix for the integrations_docs logos directory.

Returns:
The public frontend URL prefix for the integrations_docs logos directory.
"""
return _integrations_images_url("logos")


@once
def _integrations_doc_images_url() -> str:
"""Return the public URL prefix for the integrations_docs screenshots directory.

Returns:
The public frontend URL prefix for the integrations_docs docs images directory.
"""
return _integrations_images_url("docs")


def format_integration_name(integration_name: str) -> str:
"""Normalize an integration name into the slug used by its logo filename.

Expand All @@ -66,3 +93,19 @@ def get_integration_logo_url(
The public URL for the SVG logo.
"""
return f"{_integrations_logos_url()}{theme}/{format_integration_name(integration_name)}.svg"


def rewrite_integration_doc_images_in_source(source: str) -> str:
"""Rewrite raw GitHub integrations-docs screenshot URLs in a markdown source to local URLs.

Operates on the raw markdown text before parsing.

Args:
source: The markdown document source.

Returns:
The source with every raw GitHub doc screenshot URL replaced by its local asset URL.
"""
if RAW_DOC_IMAGES_PREFIX not in source:
return source
return source.replace(RAW_DOC_IMAGES_PREFIX, _integrations_doc_images_url())
Loading