diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml new file mode 100644 index 000000000..ea1ec5051 --- /dev/null +++ b/.github/workflows/docs-ci.yml @@ -0,0 +1,46 @@ +name: Docs CI + +on: + pull_request: + branches: + - main + push: + branches: + - main + +concurrency: + group: docs-ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + checks: + name: Docs checks + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.12" + - name: Install OpenAPI validator + run: pip install --quiet pyyaml openapi-spec-validator + # continue-on-error is deliberately absent: each check is a hard gate. + # Violations that already existed on main are listed in + # scripts/docs-ci-baseline.json and don't fail; only new ones do. + # Steps run in order and the job reports the first failure. + - name: Check frontmatter + run: python scripts/check_frontmatter.py + - name: Check code samples + if: '!cancelled()' + run: python scripts/check_code_samples.py + - name: Check internal links + if: '!cancelled()' + run: python scripts/check_links.py + - name: Check redirects + if: '!cancelled()' + run: python scripts/check_redirects.py + - name: Check OpenAPI specs + if: '!cancelled()' + run: python scripts/check_openapi.py diff --git a/.gitignore b/.gitignore index 94f1119ef..68a281d60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store .vscode +__pycache__/ diff --git a/docs/access-security/audit-log-streaming.mdx b/docs/access-security/audit-log-streaming.mdx index 32033b9de..6a842d83e 100644 --- a/docs/access-security/audit-log-streaming.mdx +++ b/docs/access-security/audit-log-streaming.mdx @@ -59,7 +59,7 @@ Each line contains one complete audit log entry. See the [Audit Log Reference](/ Files use this path: -``` +```text [/]YYYY/MM/DD/HH/MM/.ndjson.gz ``` @@ -73,7 +73,7 @@ Mixpanel delivers each batch of audit log entries as a gzipped NDJSON object int The external ID is unique to your Mixpanel organization and has this format: -``` +```text mixpanel-audit-log-streaming- ``` @@ -177,7 +177,7 @@ Mixpanel delivers each batch of audit log entries as a gzipped NDJSON object int Grant this service account: -``` +```text audit-log-streaming@mixpanel-prod-1.iam.gserviceaccount.com ``` diff --git a/docs/agent-intelligence.mdx b/docs/agent-intelligence.mdx index d60272898..7e6d26ab3 100644 --- a/docs/agent-intelligence.mdx +++ b/docs/agent-intelligence.mdx @@ -1,5 +1,6 @@ --- title: "Agent Intelligence" +description: "Send your AI agent traces to Mixpanel over OpenTelemetry and analyze usage, cost, errors, and conversations alongside your product data" --- Agent Intelligence brings your AI agent's traces into Mixpanel, next to the product data you already track. diff --git a/docs/cohort-sync/integrations/wingify.mdx b/docs/cohort-sync/integrations/wingify.mdx index 43c1e860e..0caba6e5f 100644 --- a/docs/cohort-sync/integrations/wingify.mdx +++ b/docs/cohort-sync/integrations/wingify.mdx @@ -1,5 +1,6 @@ --- title: Wingify +description: "Export Mixpanel cohorts to Wingify (formerly VWO) as custom segments for your experiments" --- ## Overview diff --git a/docs/tracking-methods/first-party-domains.mdx b/docs/tracking-methods/first-party-domains.mdx index 5241c839b..31a726d96 100644 --- a/docs/tracking-methods/first-party-domains.mdx +++ b/docs/tracking-methods/first-party-domains.mdx @@ -40,7 +40,7 @@ Each organization can have up to **3** first-party domains. ## How it works -``` +```text Your app ──► track.yourcompany.com ──► Mixpanel ingestion API (CNAME to Mixpanel) ``` diff --git a/openapi/gdpr.openapi.yaml b/openapi/gdpr.openapi.yaml index 87bb8a949..1ebbd24d4 100644 --- a/openapi/gdpr.openapi.yaml +++ b/openapi/gdpr.openapi.yaml @@ -400,7 +400,7 @@ components: status: SUCCESS requesting_user: user@mail.com compliance_type: GDPR - project_id: your project ID + project_id: 12345 date_requested: YYYY-MM-DDTHH:MM:SS distinct_ids: - distinct_id_1 diff --git a/scripts/check_code_samples.py b/scripts/check_code_samples.py new file mode 100644 index 000000000..2c948b6fb --- /dev/null +++ b/scripts/check_code_samples.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +CI gate: every fenced code block in MDX files must declare a language. + +A fenced block opening looks like: + ```python + ```javascript + ```bash + +A block with no language identifier: + ``` + +will cause this check to fail. + +Excluded directories (same as other checks): + - snippets/ + - openapi/ +""" + +import sys +import glob +import os +import re + +EXCLUDED_DIRS = {"snippets", "openapi"} + +def check_file(path: str, display: str) -> list[str]: + errors = [] + with open(path, encoding="utf-8") as fh: + content = fh.read() + + fence_len = 0 # 0 = outside a block; otherwise the opening fence's length + for lineno, line in enumerate(content.splitlines(), 1): + stripped = line.strip() + if not stripped.startswith("```"): + continue + ticks = len(stripped) - len(stripped.lstrip("`")) + rest = stripped[ticks:].strip() + if fence_len: + # Only a bare fence at least as long as the opener closes the block, + # so a ```python block nested inside ````mdx does not end it early. + if ticks >= fence_len and not rest: + fence_len = 0 + continue + if not rest: + errors.append( + f"{display}:{lineno}: code block is missing a language identifier" + ) + fence_len = ticks + + return errors + + +def is_excluded(path: str) -> bool: + parts = path.replace(os.sep, "/").split("/") + return any(part in EXCLUDED_DIRS for part in parts) + + +def main() -> int: + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + mdx_files = glob.glob(os.path.join(root, "**", "*.mdx"), recursive=True) + + checked = 0 + all_errors: list[str] = [] + for path in sorted(mdx_files): + rel = os.path.relpath(path, root) + if is_excluded(rel): + continue + all_errors.extend(check_file(path, rel)) + checked += 1 + + if all_errors: + print("Code-sample check FAILED:") + for err in all_errors: + print(f" {err}") + return 1 + + print(f"Code-sample check PASSED ({checked} files checked).") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/check_frontmatter.py b/scripts/check_frontmatter.py new file mode 100644 index 000000000..f272ef583 --- /dev/null +++ b/scripts/check_frontmatter.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +""" +CI gate: every MDX page needs a non-empty title and description in its YAML +front-matter, and no two pages may share a title. + +Known violations on main are listed in docs-ci-baseline.json (see +ci_baseline.py); only new violations fail the check. + +Directories that are intentionally excluded from the check: + - snippets/ (reusable MDX components, not standalone pages) + - links/ (external-link stubs that use 'url' instead of a body) + - openapi/ (OpenAPI spec files, not MDX pages) +""" + +import re +import sys +import glob +import os + +from ci_baseline import report + +EXCLUDED_DIRS = {"snippets", "links", "openapi"} + +FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---", re.DOTALL) + + +def check_file(path: str, display: str) -> tuple[list[str], str | None]: + """Return (errors, title). Title is None when absent or empty.""" + errors = [] + with open(path, encoding="utf-8") as fh: + content = fh.read() + + m = FRONTMATTER_RE.match(content) + if not m: + errors.append(f"{display}: missing front-matter block") + return errors, None + + fm = m.group(1) + title = None + title_match = re.search(r"^\s*title\s*:\s*(.*)$", fm, re.MULTILINE) + if not title_match: + errors.append(f"{display}: front-matter is missing required 'title' field") + else: + title = title_match.group(1).strip().strip("\"'") + if not title: + errors.append(f"{display}: front-matter 'title' is empty") + title = None + + # A description is what search results and llms.txt entries render, so a + # page without one is invisible to both. + desc_match = re.search(r"^\s*description\s*:\s*(.*)$", fm, re.MULTILINE) + if not desc_match: + errors.append(f"{display}: front-matter is missing required 'description' field") + elif not desc_match.group(1).strip().strip("\"'"): + errors.append(f"{display}: front-matter 'description' is empty") + + return errors, title + + +def is_excluded(path: str) -> bool: + parts = path.replace(os.sep, "/").split("/") + return any(part in EXCLUDED_DIRS for part in parts) + + +def main() -> int: + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + mdx_files = glob.glob(os.path.join(root, "**", "*.mdx"), recursive=True) + + checked = 0 + all_errors: list[str] = [] + titles: dict[str, list[str]] = {} + for path in sorted(mdx_files): + rel = os.path.relpath(path, root) + if is_excluded(rel): + continue + errors, title = check_file(path, rel) + all_errors.extend(errors) + if title: + titles.setdefault(title, []).append(rel) + checked += 1 + + # Two pages sharing a rendered title are indistinguishable in search + # results and to answer engines. One entry per page (not per group) keeps + # baseline entries stable when a group shrinks during cleanup. + for title, pages in sorted(titles.items()): + if len(pages) > 1: + for page in sorted(pages): + all_errors.append(f'{page}: title "{title}" is also used by another page') + + return report("frontmatter", "Frontmatter check", all_errors, f"{checked} files checked") + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/check_links.py b/scripts/check_links.py new file mode 100644 index 000000000..1f21305a1 --- /dev/null +++ b/scripts/check_links.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +""" +CI gate: internal links in MDX files must resolve to an existing page. + +Rules: + - Only links whose path starts with '/' are checked (relative links are + ignored because they are rare and context-dependent). + - External URLs (http/https), anchor-only links (#…), and mailto links + are skipped. + - Query-string parameters and fragment anchors are stripped before + looking up the target file. + - A link target is considered valid when one of the following is true: + 1. /.mdx exists + 2. //index.mdx exists (index pages) + 3. is the source of a redirect declared in docs.json + (redirect sources are valid inbound paths even without a backing file) + - A trailing '.md' is dropped before lookup: Mintlify serves every page as + Markdown at .md, so '/docs/quickstart.md' is valid whenever + '/docs/quickstart' is. + +Source locations checked: + - href="…" attributes (JSX / HTML in MDX) + - [text](…) Markdown links + +Excluded directories: + - snippets/ + - openapi/ +""" + +import json +import os +import re +import sys +import glob +from urllib.parse import urlparse, unquote + +EXCLUDED_DIRS = {"snippets", "openapi"} + +# Path prefixes that point to static assets, not pages — skip these. +NON_PAGE_PREFIXES = ("/images/", "/icons/", "/logo/", "/favicon") + +# href="..." or href='...' +HREF_RE = re.compile(r"""href=["']([^"']+)["']""") +# [label](url) – skip image links starting with ! +MD_LINK_RE = re.compile(r"(? str: + """Blank out fenced code blocks and inline code spans so that example + links inside them are not treated as real links. Line count is preserved + so reported line numbers stay accurate.""" + out: list[str] = [] + fence_len = 0 + for line in content.splitlines(): + stripped = line.strip() + if stripped.startswith("```"): + ticks = len(stripped) - len(stripped.lstrip("`")) + if fence_len: + if ticks >= fence_len and not stripped[ticks:].strip(): + fence_len = 0 + else: + fence_len = ticks + out.append("") + continue + out.append("" if fence_len else INLINE_CODE_RE.sub("", line)) + return "\n".join(out) + + +def extract_links(content: str) -> list[str]: + links: list[str] = [] + for m in HREF_RE.finditer(content): + links.append(m.group(1)) + for m in MD_LINK_RE.finditer(content): + links.append(m.group(1)) + return links + + +def is_internal(link: str) -> bool: + if link.startswith(("http://", "https://", "mailto:", "#")): + return False + if not link.startswith("/"): + return False + if link.startswith(NON_PAGE_PREFIXES): + return False + return True + + +def normalise(link: str) -> str: + """Strip fragment, query-string and a trailing '.md', then decode percent-encoding.""" + parsed = urlparse(link) + path = unquote(parsed.path).rstrip("/") + return path[:-3] if path.endswith(".md") else path + + +def build_valid_paths(root: str) -> tuple[set[str], list[str]]: + """Return (exact_paths, wildcard_prefixes) for valid root-relative paths. + + exact_paths – full paths that must match exactly (O(1) lookup via set). + wildcard_prefixes – path prefixes derived from wildcard redirect sources + (e.g. '/changelogs' from '/changelogs/*'). A link + target is valid if it starts with one of these prefixes + followed by '/'. + """ + exact: set[str] = set() + prefixes: list[str] = [] + + # Every .mdx file contributes its path (without extension) and with extension + for mdx in glob.glob(os.path.join(root, "**", "*.mdx"), recursive=True): + rel = os.path.relpath(mdx, root).replace(os.sep, "/") + # e.g. "docs/what-is-mixpanel.mdx" + exact.add("/" + rel) # with .mdx + exact.add("/" + rel[:-4]) # without .mdx + # index pages: "docs/foo/index.mdx" → "/docs/foo" + if rel.endswith("/index.mdx"): + exact.add("/" + rel[: -len("/index.mdx")]) + + # Redirect sources are also valid inbound paths + docs_json = os.path.join(root, "docs.json") + if os.path.exists(docs_json): + with open(docs_json, encoding="utf-8") as fh: + data = json.load(fh) + for redir in data.get("redirects", []): + src = redir.get("source", "") + if src.endswith("/*"): + # Wildcard source: store the prefix for prefix matching + prefix = src[:-2] # e.g. "/changelogs" + exact.add(prefix) + prefixes.append(prefix) + else: + exact.add(src) + + return exact, prefixes + + +def is_excluded(path: str) -> bool: + parts = path.replace(os.sep, "/").split("/") + return any(part in EXCLUDED_DIRS for part in parts) + + +def main() -> int: + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + valid_paths, wildcard_prefixes = build_valid_paths(root) + + mdx_files = glob.glob(os.path.join(root, "**", "*.mdx"), recursive=True) + + checked = 0 + all_errors: list[str] = [] + + for path in sorted(mdx_files): + rel = os.path.relpath(path, root) + if is_excluded(rel): + continue + checked += 1 + + with open(path, encoding="utf-8") as fh: + content = strip_code(fh.read()) + + for raw_link in extract_links(content): + if not is_internal(raw_link): + continue + target = normalise(raw_link) + if not target: + continue + + # Direct match (O(1)) + if target in valid_paths: + continue + + # Wildcard-redirect prefix match: check only the small list of + # known wildcard prefixes rather than iterating all valid_paths. + if any(target.startswith(p + "/") for p in wildcard_prefixes): + continue + + all_errors.append( + f"{rel}: broken internal link '{raw_link}' → '{target}'" + ) + + if all_errors: + print("Links check FAILED:") + for err in all_errors: + print(f" {err}") + return 1 + + print(f"Links check PASSED ({checked} files, {len(valid_paths)} valid paths indexed).") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/check_openapi.py b/scripts/check_openapi.py new file mode 100644 index 000000000..7ea799903 --- /dev/null +++ b/scripts/check_openapi.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 +""" +CI gate: validate every OpenAPI specification under openapi/. + +Checks performed: + 1. The file parses as YAML or JSON. + 2. Required top-level structure is present (openapi/swagger version, info + with title and version, and paths). + 3. Every local $ref ("#/...") resolves to a node that exists in the document. + 4. If openapi-spec-validator is installed, the full spec is validated against + the OpenAPI schema, and every example is validated against the schema it + illustrates (media-type and parameter `example`/`examples`, plus + schema-level `example`). Without it, checks 1-3 still run. +""" + +import glob +import json +import os +import sys + +import yaml + + +def load(path: str): + with open(path, encoding="utf-8") as fh: + text = fh.read() + if path.endswith(".json"): + return json.loads(text) + return yaml.safe_load(text) + + +def iter_refs(node, trail="#"): + """Yield every ($ref value, location) pair in the document.""" + if isinstance(node, dict): + for key, value in node.items(): + if key == "$ref" and isinstance(value, str): + yield value, trail + else: + yield from iter_refs(value, f"{trail}/{key}") + elif isinstance(node, list): + for index, value in enumerate(node): + yield from iter_refs(value, f"{trail}/{index}") + + +def resolves(doc, ref: str) -> bool: + """Walk a local JSON pointer ("#/components/schemas/Foo") through the doc.""" + node = doc + for part in ref.lstrip("#/").split("/"): + # JSON pointer escapes, per RFC 6901. + part = part.replace("~1", "/").replace("~0", "~") + if isinstance(node, list): + if not part.isdigit() or int(part) >= len(node): + return False + node = node[int(part)] + elif isinstance(node, dict): + if part not in node: + return False + node = node[part] + else: + return False + return True + +# Keywords that mark a mapping as a Schema Object, so a schema-level `example` +# can be told apart from an arbitrary mapping that happens to have that key. +SCHEMA_KEYWORDS = {"type", "properties", "items", "allOf", "oneOf", "anyOf", "enum", "format", "$ref"} + + +def escape_pointer(key) -> str: + return str(key).replace("~", "~0").replace("/", "~1") + + +def deref(doc, node): + """Follow local $refs (e.g. to components/examples) to the target node.""" + for _ in range(20): + if not (isinstance(node, dict) and str(node.get("$ref", "")).startswith("#")): + return node + if not resolves(doc, node["$ref"]): + return None + target = doc + for part in node["$ref"][2:].split("/"): + part = part.replace("~1", "/").replace("~0", "~") + target = target[int(part)] if isinstance(target, list) else target[part] + node = target + return node + + +def iter_examples(doc, node, pointer=""): + """Yield (schema pointer, example location, example value) triples. + + Example Objects from an `examples` map are unwrapped to their `value` + (following $refs); ones using externalValue are skipped. + """ + if isinstance(node, list): + for index, value in enumerate(node): + yield from iter_examples(doc, value, f"{pointer}/{index}") + return + if not isinstance(node, dict): + return + + if isinstance(node.get("schema"), dict): + # Media Type or Parameter Object: examples illustrate node["schema"]. + if "example" in node: + yield f"{pointer}/schema", f"{pointer}/example", node["example"] + for name, example in (node.get("examples") or {}).items(): + example = deref(doc, example) + if isinstance(example, dict) and "value" in example: + yield f"{pointer}/schema", f"{pointer}/examples/{escape_pointer(name)}", example["value"] + elif "example" in node and SCHEMA_KEYWORDS & node.keys(): + yield pointer, f"{pointer}/example", node["example"] + + for key, value in node.items(): + # Example payloads are data, not schema; don't mistake their keys for + # keywords. + if key in ("example", "examples"): + continue + child = f"{pointer}/{escape_pointer(key)}" + if key == "properties" and isinstance(value, dict): + # Keys here are property names, so a property literally called + # "example" or "type" must not make the map look like a schema. + for name, prop in value.items(): + yield from iter_examples(doc, prop, f"{child}/{escape_pointer(name)}") + continue + yield from iter_examples(doc, value, child) + + +def check_examples(doc, display: str) -> list[str]: + """Validate every example against its schema, resolving $refs in the doc.""" + from openapi_schema_validator import OAS30Validator, OAS31Validator + from referencing import Registry, Resource + from referencing.jsonschema import DRAFT4, DRAFT202012 + + is_31 = str(doc.get("openapi", "")).startswith("3.1") + validator_cls = OAS31Validator if is_31 else OAS30Validator + resource = Resource.from_contents(doc, default_specification=DRAFT202012 if is_31 else DRAFT4) + registry = Registry().with_resource("urn:spec", resource) + + errors = [] + for schema_pointer, where, example in iter_examples(doc, doc): + validator = validator_cls({"$ref": f"urn:spec#{schema_pointer}"}, registry=registry) + first = next(iter(validator.iter_errors(example)), None) + if first is not None: + errors.append(f"{display}: example at {where} does not match its schema: {first.message}") + return errors + + +def check_file(path: str, display: str) -> list[str]: + errors = [] + try: + doc = load(path) + except Exception as exc: # noqa: BLE001 - report any parse failure verbatim + return [f"{display}: does not parse ({type(exc).__name__}: {exc})"] + + if not isinstance(doc, dict): + return [f"{display}: top level is not a mapping"] + + if not (doc.get("openapi") or doc.get("swagger")): + errors.append(f"{display}: missing 'openapi' (or 'swagger') version field") + + info = doc.get("info") + if not isinstance(info, dict): + errors.append(f"{display}: missing 'info' object") + else: + for field in ("title", "version"): + if not info.get(field): + errors.append(f"{display}: 'info.{field}' is missing or empty") + + if "paths" not in doc and "webhooks" not in doc: + errors.append(f"{display}: missing 'paths'") + + for ref, where in iter_refs(doc): + if ref.startswith("#"): + if not resolves(doc, ref): + errors.append(f"{display}: unresolved local $ref '{ref}' at {where}") + elif not ref.startswith(("http://", "https://")): + target = os.path.join(os.path.dirname(path), ref.split("#")[0]) + if not os.path.exists(target): + errors.append(f"{display}: $ref points at a missing file '{ref}'") + + return errors + + +def main() -> int: + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + spec_dir = os.path.join(root, "openapi") + if not os.path.isdir(spec_dir): + print("OpenAPI check SKIPPED (no openapi/ directory).") + return 0 + + specs = sorted( + glob.glob(os.path.join(spec_dir, "**", "*.yaml"), recursive=True) + + glob.glob(os.path.join(spec_dir, "**", "*.yml"), recursive=True) + + glob.glob(os.path.join(spec_dir, "**", "*.json"), recursive=True) + ) + + try: + from openapi_spec_validator import validate as spec_validate + + deep = True + except ImportError: + spec_validate = None + deep = False + + all_errors: list[str] = [] + for path in specs: + rel = os.path.relpath(path, root) + errors = check_file(path, rel) + if not errors and spec_validate is not None: + try: + spec_validate(load(path)) + except Exception as exc: # noqa: BLE001 - surface the validator's message + first = str(exc).split("\n")[0] + errors.append(f"{rel}: failed OpenAPI schema validation: {first}") + else: + errors.extend(check_examples(load(path), rel)) + all_errors.extend(errors) + + if all_errors: + print("OpenAPI check FAILED:") + for err in all_errors: + print(f" {err}") + return 1 + + depth = "structure + schema + examples" if deep else "structure only (validator not installed)" + print(f"OpenAPI check PASSED ({len(specs)} specs validated, {depth}).") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/check_redirects.py b/scripts/check_redirects.py new file mode 100644 index 000000000..78d45334d --- /dev/null +++ b/scripts/check_redirects.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +""" +CI gate: validate the 'redirects' section of docs.json. + +Checks performed: + 1. No duplicate redirect source paths. + 2. No redirect loops (a self-redirect or a cycle). + 3. No redirect chains: a destination must not itself be the source of + another redirect (exact or wildcard). Each hop costs crawlers and answer + engines a round trip, and chains decay into loops. Point the redirect at + the final page instead. + 4. Every destination resolves to an existing page. A wildcard destination + (e.g. '/guides/mcp/*') can't be checked child by child, since the + incoming URLs are unknown, so it must at least match one existing page: + removing or renaming the whole target section fails the check. + +Known violations on main are listed in docs-ci-baseline.json (see +ci_baseline.py); only new violations fail the check. +""" + +import fnmatch +import json +import os +import sys +import glob + +from ci_baseline import report + + +def build_file_paths(root: str) -> set[str]: + """Return all root-relative page paths derived from .mdx files.""" + paths: set[str] = set() + for mdx in glob.glob(os.path.join(root, "**", "*.mdx"), recursive=True): + rel = os.path.relpath(mdx, root).replace(os.sep, "/") + paths.add("/" + rel) # with extension + paths.add("/" + rel[:-4]) # without extension + if rel.endswith("/index.mdx"): + paths.add("/" + rel[: -len("/index.mdx")]) + return paths + + +def normalise(path: str) -> str: + """Strip trailing slash and query string.""" + return path.split("?")[0].rstrip("/") or "/" + + +def main() -> int: + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + docs_json_path = os.path.join(root, "docs.json") + + if not os.path.exists(docs_json_path): + print("Redirects check SKIPPED (docs.json not found).") + return 0 + + with open(docs_json_path, encoding="utf-8") as fh: + data = json.load(fh) + + redirects = data.get("redirects", []) + all_errors: list[str] = [] + + # ── 1. Duplicate sources ──────────────────────────────────────────────── + sources: list[str] = [r.get("source", "") for r in redirects] + seen: set[str] = set() + duplicates: set[str] = set() + for src in sources: + if src in seen: + duplicates.add(src) + seen.add(src) + + for dup in sorted(duplicates): + all_errors.append(f"docs.json: duplicate redirect source '{dup}'") + + # ── 2. Redirect loops (self-redirects and cycles) ─────────────────────── + dest_by_source = {r.get("source", ""): r.get("destination", "") for r in redirects} + reported_cycles: set[frozenset[str]] = set() + reported_loop_sources: set[str] = set() + for start in dest_by_source: + path = [start] + node = dest_by_source[start] + while node in dest_by_source: + if node in path: + reported_loop_sources.add(start) + cycle = path[path.index(node):] + if frozenset(cycle) not in reported_cycles: + reported_cycles.add(frozenset(cycle)) + # Rotate to the smallest member so the message is stable. + pivot = cycle.index(min(cycle)) + ordered = cycle[pivot:] + cycle[:pivot] + hops = " -> ".join(ordered + [ordered[0]]) + all_errors.append(f"docs.json: redirect loop {hops}") + break + path.append(node) + node = dest_by_source[node] + + # ── 3 + 4. Destinations are final pages, not further redirects ───────── + file_paths = build_file_paths(root) + exact_sources = {s for s in sources if "*" not in s} + wildcard_sources = [s for s in sources if "*" in s] + + def is_redirected(path: str) -> bool: + """True when the CDN would redirect `path` again (a chain).""" + if path in exact_sources: + return True + return any(fnmatch.fnmatch(path, pat) for pat in wildcard_sources) + + for redir in redirects: + src = redir.get("source", "") + dest = redir.get("destination", "") + if not dest: + all_errors.append(f"docs.json: redirect from '{src}' has an empty destination") + continue + + if dest.startswith("http"): + continue + + # Strip anchors and query strings from destination + dest_path = normalise(dest.split("#")[0]) + + # Loops were reported above; don't double-report them as chains. + if src in reported_loop_sources: + continue + + if "*" in dest: + # A wildcard destination chains only if it is itself a source. + if dest_path in sources: + all_errors.append(f"docs.json: redirect chain '{src}' -> '{dest}' (destination is redirected again)") + elif not any(fnmatch.fnmatch(page, dest_path) for page in file_paths): + all_errors.append( + f"docs.json: wildcard redirect destination '{dest}' matches no existing page " + f"(source: '{src}')" + ) + continue + + if is_redirected(dest_path): + all_errors.append(f"docs.json: redirect chain '{src}' -> '{dest}' (destination is redirected again)") + elif dest_path not in file_paths: + all_errors.append( + f"docs.json: redirect destination '{dest}' does not resolve " + f"to a known page (source: '{src}')" + ) + + return report( + "redirects", + "Redirects check", + all_errors, + f"{len(redirects)} redirects validated, {len(file_paths)} pages indexed", + ) + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/ci_baseline.py b/scripts/ci_baseline.py new file mode 100644 index 000000000..b318102b1 --- /dev/null +++ b/scripts/ci_baseline.py @@ -0,0 +1,74 @@ +""" +Shared baseline handling for the docs CI gates. + +A new gate should not block every unrelated PR on day one because of +violations that already exist on main. Each check can therefore list its +known, pre-existing violations in scripts/docs-ci-baseline.json. The check +fails only on violations that are NOT in the baseline, so new content is held +to the full standard while old content is cleaned up separately. + +Baseline entries that no longer occur are reported but never fail the build, +so a cleanup PR (for example TOF-439 descriptions or TOF-441 redirect chains) +can land without touching the baseline. Prune them with --update-baseline. + +To regenerate one check's entries from the current tree: + python scripts/check_frontmatter.py --update-baseline +""" + +import json +import os +import sys + +BASELINE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "docs-ci-baseline.json") + + +def _load() -> dict[str, list[str]]: + if not os.path.exists(BASELINE_PATH): + return {} + with open(BASELINE_PATH, encoding="utf-8") as fh: + return json.load(fh) + + +def _save(baseline: dict[str, list[str]]) -> None: + with open(BASELINE_PATH, "w", encoding="utf-8") as fh: + json.dump(baseline, fh, indent=2, sort_keys=True) + fh.write("\n") + + +def report(check: str, label: str, errors: list[str], passed_summary: str) -> int: + """Print results for one check and return its exit code. + + `errors` must be stable strings (no line numbers) so they can be matched + against the baseline across unrelated edits. + """ + if "--update-baseline" in sys.argv: + baseline = _load() + if errors: + baseline[check] = sorted(set(errors)) + else: + baseline.pop(check, None) + _save(baseline) + print(f"{label} baseline updated: {len(set(errors))} known violation(s) recorded.") + return 0 + + known = set(_load().get(check, [])) + new_errors = [err for err in errors if err not in known] + fixed = sorted(known - set(errors)) + baselined = len(errors) - len(new_errors) + + if fixed: + script = os.path.relpath(os.path.abspath(sys.argv[0]), os.path.dirname(os.path.dirname(BASELINE_PATH))) + print( + f"{label}: {len(fixed)} baseline entry(ies) no longer occur (fixed). " + f"Run `python {script} --update-baseline` to prune them." + ) + + if new_errors: + print(f"{label} FAILED ({len(new_errors)} new violation(s); {baselined} known and baselined):") + for err in new_errors: + print(f" {err}") + return 1 + + suffix = f"; {baselined} known violation(s) baselined" if baselined else "" + print(f"{label} PASSED ({passed_summary}{suffix}).") + return 0 diff --git a/scripts/docs-ci-baseline.json b/scripts/docs-ci-baseline.json new file mode 100644 index 000000000..e2b14623d --- /dev/null +++ b/scripts/docs-ci-baseline.json @@ -0,0 +1,642 @@ +{ + "frontmatter": [ + "changelogs.mdx: front-matter is missing required 'description' field", + "docs/access-security.mdx: front-matter is missing required 'description' field", + "docs/access-security/audit-log-reference.mdx: front-matter is missing required 'description' field", + "docs/access-security/audit-log.mdx: front-matter is missing required 'description' field", + "docs/access-security/login-methods.mdx: front-matter is missing required 'description' field", + "docs/access-security/single-sign-on/azure.mdx: front-matter is missing required 'description' field", + "docs/access-security/single-sign-on/google.mdx: front-matter is missing required 'description' field", + "docs/access-security/single-sign-on/jumpcloud.mdx: front-matter is missing required 'description' field", + "docs/access-security/single-sign-on/okta.mdx: front-matter is missing required 'description' field", + "docs/access-security/two-factor-authentication.mdx: front-matter is missing required 'description' field", + "docs/agentic-automations.mdx: front-matter is missing required 'description' field", + "docs/boards/sharing-and-permission.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/build-an-integration.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/abtasty.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/airship.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/appcues.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/apptimize.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/braze.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/chameleon.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/clevertap.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/facebook-ads.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/google-ads.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/insider.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/iterable.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/kameleoon.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/leanplum.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/mailchimp.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/marketo.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/moengage.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/mparticle.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/mparticle.mdx: title \"mParticle\" is also used by another page", + "docs/cohort-sync/integrations/onesignal.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/salesforce-marketing-cloud.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/segment.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/segment.mdx: title \"Segment\" is also used by another page", + "docs/cohort-sync/integrations/taplytics.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/vwo.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/webengage.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/integrations/xtremepush.mdx: front-matter is missing required 'description' field", + "docs/cohort-sync/webhooks.mdx: front-matter is missing required 'description' field", + "docs/community.mdx: front-matter is missing required 'description' field", + "docs/community/guidelines.mdx: front-matter is missing required 'description' field", + "docs/data-governance.mdx: front-matter is missing required 'description' field", + "docs/data-governance/ai-powered-data-governance.mdx: front-matter is missing required 'description' field", + "docs/data-governance/data-clean-up.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/common-sql-queries.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/integrations.mdx: title \"Data Pipeline Integrations\" is also used by another page", + "docs/data-pipelines/integrations/aws-s3.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/integrations/azure-blob-storage.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/integrations/bigquery.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/integrations/databricks.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/integrations/gcp-gcs.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/integrations/gcp-gcs.mdx: title \"Google Cloud Storage\" is also used by another page", + "docs/data-pipelines/integrations/redshift-spectrum.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/integrations/snowflake.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/json-pipelines.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/old-pipelines.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/old-pipelines/integrations.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/old-pipelines/integrations.mdx: title \"Data Pipeline Integrations\" is also used by another page", + "docs/data-pipelines/old-pipelines/integrations/raw-aws-pipeline.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/old-pipelines/integrations/raw-azure-pipeline.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/old-pipelines/integrations/raw-gcs-pipeline.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/old-pipelines/integrations/schematized-aws-pipeline.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/old-pipelines/integrations/schematized-azure-pipeline.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/old-pipelines/integrations/schematized-bigquery-pipeline.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/old-pipelines/integrations/schematized-gcs-pipeline.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/old-pipelines/integrations/schematized-snowflake-pipeline.mdx: front-matter is missing required 'description' field", + "docs/data-pipelines/old-pipelines/schematized-export-pipeline.mdx: front-matter is missing required 'description' field", + "docs/data-structure/lookup-tables.mdx: title \"Lookup Tables\" is also used by another page", + "docs/features.mdx: front-matter is missing required 'description' field", + "docs/features.mdx: title \"Features\" is also used by another page", + "docs/features/annotations.mdx: front-matter is missing required 'description' field", + "docs/features/comments.mdx: front-matter is missing required 'description' field", + "docs/features/computed-properties.mdx: front-matter is missing required 'description' field", + "docs/features/custom-buckets.mdx: front-matter is missing required 'description' field", + "docs/features/revenue-analytics.mdx: front-matter is missing required 'description' field", + "docs/features/saved-metrics-and-behaviors.mdx: front-matter is missing required 'description' field", + "docs/features/slack-integration.mdx: front-matter is missing required 'description' field", + "docs/mcp.mdx: front-matter is missing required 'description' field", + "docs/metric_tree.mdx: front-matter is missing required 'description' field", + "docs/migration.mdx: front-matter is missing required 'description' field", + "docs/migration/adobe-analytics.mdx: front-matter is missing required 'description' field", + "docs/migration/amplitude.mdx: front-matter is missing required 'description' field", + "docs/migration/google-analytics.mdx: front-matter is missing required 'description' field", + "docs/mixpanel-agent.mdx: front-matter is missing required 'description' field", + "docs/mixpanel-headless.mdx: front-matter is missing required 'description' field", + "docs/mixpanel-headless.mdx: title \"Mixpanel Headless\" is also used by another page", + "docs/orgs-and-projects.mdx: front-matter is missing required 'description' field", + "docs/orgs-and-projects/managing-projects.mdx: front-matter is missing required 'description' field", + "docs/orgs-and-projects/organizations.mdx: front-matter is missing required 'description' field", + "docs/orgs-and-projects/roles-and-permissions.mdx: front-matter is missing required 'description' field", + "docs/pricing/legacy-mtu-billing.mdx: front-matter is missing required 'description' field", + "docs/privacy.mdx: front-matter is missing required 'description' field", + "docs/privacy/eu-residency.mdx: front-matter is missing required 'description' field", + "docs/privacy/gdpr-compliance.mdx: front-matter is missing required 'description' field", + "docs/privacy/in-residency.mdx: front-matter is missing required 'description' field", + "docs/quickstart.mdx: front-matter is missing required 'description' field", + "docs/quickstart/capture-events.mdx: front-matter is missing required 'description' field", + "docs/quickstart/capture-events/autocapture.mdx: front-matter is missing required 'description' field", + "docs/quickstart/capture-events/autocapture.mdx: title \"Autocapture\" is also used by another page", + "docs/quickstart/capture-events/track-events.mdx: front-matter is missing required 'description' field", + "docs/quickstart/capture-events/track-events.mdx: title \"Track Events\" is also used by another page", + "docs/quickstart/company-analytics.mdx: front-matter is missing required 'description' field", + "docs/quickstart/connect-your-data.mdx: front-matter is missing required 'description' field", + "docs/quickstart/identify-users.mdx: front-matter is missing required 'description' field", + "docs/quickstart/install-mixpanel.mdx: front-matter is missing required 'description' field", + "docs/quickstart/install-with-ai.mdx: front-matter is missing required 'description' field", + "docs/quickstart/tips-and-tricks.mdx: front-matter is missing required 'description' field", + "docs/reports.mdx: front-matter is missing required 'description' field", + "docs/reports/apps.mdx: front-matter is missing required 'description' field", + "docs/reports/funnels.mdx: title \"Funnels\" is also used by another page", + "docs/reports/funnels/funnels-advanced.mdx: front-matter is missing required 'description' field", + "docs/reports/funnels/funnels-faq.mdx: front-matter is missing required 'description' field", + "docs/reports/funnels/funnels-overview.mdx: title \"Funnels\" is also used by another page", + "docs/reports/funnels/funnels-quickstart.mdx: front-matter is missing required 'description' field", + "docs/response-times.mdx: front-matter is missing required 'description' field", + "docs/session-replay/heatmaps.mdx: front-matter is missing required 'description' field", + "docs/session-replay/session-replay-privacy-controls.mdx: front-matter is missing required 'description' field", + "docs/tracking-best-practices.mdx: front-matter is missing required 'description' field", + "docs/tracking-best-practices/bot-traffic.mdx: front-matter is missing required 'description' field", + "docs/tracking-best-practices/developer-environments.mdx: front-matter is missing required 'description' field", + "docs/tracking-best-practices/hot-shard-limits.mdx: front-matter is missing required 'description' field", + "docs/tracking-best-practices/server-side-best-practices.mdx: front-matter is missing required 'description' field", + "docs/tracking-best-practices/tracking-plan.mdx: front-matter is missing required 'description' field", + "docs/tracking-best-practices/warehouse-best-practices.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/autocapture.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/autocapture.mdx: title \"Autocapture\" is also used by another page", + "docs/tracking-methods/choosing-the-right-method.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/data-inspector.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/id-management.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/id-management/identifying-users-original.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/id-management/identifying-users-simplified.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/id-management/migrating-to-simplified-id-merge-system.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/ad-spend.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/amazon-s3.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/aws-kafka.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/cms-ecommerce.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/freshpaint.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/google-cloud-storage.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/google-cloud-storage.mdx: title \"Google Cloud Storage\" is also used by another page", + "docs/tracking-methods/integrations/google-pubsub.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/google-sheets.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/google-tag-manager.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/langfuse.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/launchdarkly.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/mobile-attribution-tracking.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/mparticle.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/mparticle.mdx: title \"mParticle\" is also used by another page", + "docs/tracking-methods/integrations/nextjs.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/rudderstack.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/segment.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/segment.mdx: title \"Segment\" is also used by another page", + "docs/tracking-methods/integrations/shopify.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/snowplow.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/stripe.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/tealium.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/integrations/vendo.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/android.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/android/android-flags.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/android/android-openfeature.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/android/android-replay.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/flutter.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/flutter/flutter-flags.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/flutter/flutter-replay.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/go.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/go/go-flags.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/go/go-openfeature.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/ios.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/java/index.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/java/java-flags.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/java/java-openfeature.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/javascript.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/javascript/javascript-flags.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/javascript/javascript-openfeature.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/javascript/javascript-replay.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/nodejs.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/nodejs/nodejs-flags.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/nodejs/nodejs-openfeature.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/php.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/python.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/python/python-flags.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/python/python-openfeature.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/react-native.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/react-native/react-native-flags.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/react-native/react-native-replay.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/ruby.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/ruby/ruby-flags.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/ruby/ruby-openfeature.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/swift.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/swift/swift-flags.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/swift/swift-openfeature.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/swift/swift-replay.mdx: front-matter is missing required 'description' field", + "docs/tracking-methods/sdks/unity.mdx: front-matter is missing required 'description' field", + "guides/benchmarks.mdx: front-matter is missing required 'description' field", + "guides/board-templates.mdx: front-matter is missing required 'description' field", + "guides/glossary.mdx: front-matter is missing required 'description' field", + "guides/guides-by-topic/continuous-innovation.mdx: front-matter is missing required 'description' field", + "guides/guides-by-topic/core-reports.mdx: front-matter is missing required 'description' field", + "guides/guides-by-topic/features.mdx: front-matter is missing required 'description' field", + "guides/guides-by-topic/features.mdx: title \"Features\" is also used by another page", + "guides/guides-by-topic/govern-data.mdx: front-matter is missing required 'description' field", + "guides/guides-by-use-case.mdx: front-matter is missing required 'description' field", + "guides/guides-by-use-case/empower-your-team/close-strategy-execution-gap.mdx: front-matter is missing required 'description' field", + "guides/guides-by-use-case/empower-your-team/see-replays.mdx: front-matter is missing required 'description' field", + "guides/guides-by-use-case/engage-your-users/drive-product-innovation.mdx: front-matter is missing required 'description' field", + "guides/guides-by-use-case/engage-your-users/ship-features.mdx: front-matter is missing required 'description' field", + "guides/guides-by-use-case/grow-your-usership/grow-revenue.mdx: front-matter is missing required 'description' field", + "guides/guides-by-workflow/build-tracking-strategy.mdx: front-matter is missing required 'description' field", + "guides/guides-by-workflow/data-privacy.mdx: front-matter is missing required 'description' field", + "guides/guides-by-workflow/embed-data.mdx: front-matter is missing required 'description' field", + "guides/guides-by-workflow/ensure-data-quality.mdx: front-matter is missing required 'description' field", + "guides/guides-by-workflow/sync-with-mirror.mdx: front-matter is missing required 'description' field", + "guides/headless.mdx: title \"Mixpanel Headless\" is also used by another page", + "guides/mcp.mdx: front-matter is missing required 'description' field", + "guides/mcp/integrations.mdx: front-matter is missing required 'description' field", + "guides/mcp/mcp-by-industry.mdx: front-matter is missing required 'description' field", + "guides/mixpanel-agent.mdx: front-matter is missing required 'description' field", + "guides/mixpanel-agent/use-cases.mdx: front-matter is missing required 'description' field", + "guides/mixpanel-introduction.mdx: front-matter is missing required 'description' field", + "guides/self-guided-tours.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/feature-flag-migration-playbook.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/guide-to-product-analytics/conclusion.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/guide-to-product-analytics/get-to-know-your-users.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/guide-to-product-analytics/grow-and-scale.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/guide-to-product-analytics/intro.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/guide-to-product-analytics/retain-your-users.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/onboarding-playbook.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/product-analytics-strategy/advanced.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/product-analytics-strategy/expert.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/product-analytics-strategy/finale.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/product-analytics-strategy/get-started.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/product-analytics-strategy/intermediate.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/product-analytics-strategy/non-existent.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/product-analytics-strategy/novice.mdx: front-matter is missing required 'description' field", + "guides/strategic-playbooks/project-migration.mdx: front-matter is missing required 'description' field", + "guides/use-mixpanel-ai/trust-ai-answers.mdx: front-matter is missing required 'description' field", + "guides/use-mixpanel-ai/write-business-context.mdx: front-matter is missing required 'description' field", + "reference/activity-stream-query.mdx: front-matter is missing required 'description' field", + "reference/add-service-accounts-to-projects.mdx: front-matter is missing required 'description' field", + "reference/archive-experiment.mdx: front-matter is missing required 'description' field", + "reference/authentication-2.mdx: front-matter is missing required 'description' field", + "reference/authentication-2.mdx: title \"Authentication\" is also used by another page", + "reference/authentication.mdx: front-matter is missing required 'description' field", + "reference/cancel-warehouse-pipeline.mdx: front-matter is missing required 'description' field", + "reference/cohorts-list.mdx: front-matter is missing required 'description' field", + "reference/create-annotation-tag-1.mdx: front-matter is missing required 'description' field", + "reference/create-annotation.mdx: front-matter is missing required 'description' field", + "reference/create-deletion.mdx: front-matter is missing required 'description' field", + "reference/create-event-stream-import.mdx: front-matter is missing required 'description' field", + "reference/create-experiment.mdx: front-matter is missing required 'description' field", + "reference/create-feature-flag-1.mdx: front-matter is missing required 'description' field", + "reference/create-groups-import.mdx: front-matter is missing required 'description' field", + "reference/create-identity.mdx: front-matter is missing required 'description' field", + "reference/create-lookup-table-import.mdx: front-matter is missing required 'description' field", + "reference/create-people-import.mdx: front-matter is missing required 'description' field", + "reference/create-retrieval-1.mdx: front-matter is missing required 'description' field", + "reference/create-service-account.mdx: front-matter is missing required 'description' field", + "reference/create-warehouse-pipeline.mdx: front-matter is missing required 'description' field", + "reference/decide-experiment.mdx: front-matter is missing required 'description' field", + "reference/delete-all-schemas-in-project.mdx: front-matter is missing required 'description' field", + "reference/delete-annotation-1.mdx: front-matter is missing required 'description' field", + "reference/delete-deletion.mdx: front-matter is missing required 'description' field", + "reference/delete-experiment.mdx: front-matter is missing required 'description' field", + "reference/delete-feature-flag-1.mdx: front-matter is missing required 'description' field", + "reference/delete-group.mdx: front-matter is missing required 'description' field", + "reference/delete-profile.mdx: front-matter is missing required 'description' field", + "reference/delete-schema-by-entity-and-name.mdx: front-matter is missing required 'description' field", + "reference/delete-schemas-for-entity.mdx: front-matter is missing required 'description' field", + "reference/delete-service-account.mdx: front-matter is missing required 'description' field", + "reference/delete-warehouse-import.mdx: front-matter is missing required 'description' field", + "reference/edit-warehouse-pipeline.mdx: front-matter is missing required 'description' field", + "reference/engage-query.mdx: front-matter is missing required 'description' field", + "reference/event-deduplication.mdx: front-matter is missing required 'description' field", + "reference/feature-flags-api.mdx: front-matter is missing required 'description' field", + "reference/feature-flags-api.mdx: title \"Overview\" is also used by another page", + "reference/feature-flags-management-api.mdx: front-matter is missing required 'description' field", + "reference/feature-flags-management-api.mdx: title \"Overview\" is also used by another page", + "reference/force-conclude-experiment.mdx: front-matter is missing required 'description' field", + "reference/funnels-list-saved.mdx: front-matter is missing required 'description' field", + "reference/funnels-query.mdx: front-matter is missing required 'description' field", + "reference/funnels-query.mdx: title \"Query Saved Report\" is also used by another page", + "reference/gdpr-api.mdx: front-matter is missing required 'description' field", + "reference/gdpr-api.mdx: title \"Overview\" is also used by another page", + "reference/get-annotation-1.mdx: front-matter is missing required 'description' field", + "reference/get-annotation-tags-1.mdx: front-matter is missing required 'description' field", + "reference/get-deletion.mdx: front-matter is missing required 'description' field", + "reference/get-experiment.mdx: front-matter is missing required 'description' field", + "reference/get-feature-flag-1.mdx: front-matter is missing required 'description' field", + "reference/get-flag-definitions.mdx: front-matter is missing required 'description' field", + "reference/get-import-history.mdx: front-matter is missing required 'description' field", + "reference/get-retrieval.mdx: front-matter is missing required 'description' field", + "reference/get-service-account.mdx: front-matter is missing required 'description' field", + "reference/get-variant-assignments.mdx: front-matter is missing required 'description' field", + "reference/get-warehouse-import.mdx: front-matter is missing required 'description' field", + "reference/get-warehouse-pipeline-status.mdx: front-matter is missing required 'description' field", + "reference/group-batch-update.mdx: front-matter is missing required 'description' field", + "reference/group-delete-property.mdx: front-matter is missing required 'description' field", + "reference/group-remove-from-list-property.mdx: front-matter is missing required 'description' field", + "reference/group-set-property-once.mdx: front-matter is missing required 'description' field", + "reference/group-set-property.mdx: front-matter is missing required 'description' field", + "reference/group-union.mdx: front-matter is missing required 'description' field", + "reference/identity-create-alias.mdx: front-matter is missing required 'description' field", + "reference/identity-merge.mdx: front-matter is missing required 'description' field", + "reference/import-events.mdx: front-matter is missing required 'description' field", + "reference/ingestion-api-authentication.mdx: front-matter is missing required 'description' field", + "reference/ingestion-api-authentication.mdx: title \"Authentication\" is also used by another page", + "reference/ingestion-api.mdx: front-matter is missing required 'description' field", + "reference/ingestion-api.mdx: title \"Overview\" is also used by another page", + "reference/insights-query.mdx: front-matter is missing required 'description' field", + "reference/insights-query.mdx: title \"Query Saved Report\" is also used by another page", + "reference/launch-experiment.mdx: front-matter is missing required 'description' field", + "reference/lexicon-schemas-api-authentication.mdx: front-matter is missing required 'description' field", + "reference/lexicon-schemas-api-authentication.mdx: title \"Authentication\" is also used by another page", + "reference/lexicon-schemas-api.mdx: front-matter is missing required 'description' field", + "reference/lexicon-schemas-api.mdx: title \"Overview\" is also used by another page", + "reference/limits-1.mdx: front-matter is missing required 'description' field", + "reference/limits-1.mdx: title \"Limits\" is also used by another page", + "reference/limits.mdx: front-matter is missing required 'description' field", + "reference/limits.mdx: title \"Limits\" is also used by another page", + "reference/list-all-annotations-for-project.mdx: front-matter is missing required 'description' field", + "reference/list-all-schemas-for-project.mdx: front-matter is missing required 'description' field", + "reference/list-experiments.mdx: front-matter is missing required 'description' field", + "reference/list-feature-flags-1.mdx: front-matter is missing required 'description' field", + "reference/list-lookup-tables.mdx: front-matter is missing required 'description' field", + "reference/list-project-service-accounts.mdx: front-matter is missing required 'description' field", + "reference/list-recent-events.mdx: front-matter is missing required 'description' field", + "reference/list-schemas-by-entity-and-name.mdx: front-matter is missing required 'description' field", + "reference/list-schemas-for-entity.mdx: front-matter is missing required 'description' field", + "reference/list-service-accounts.mdx: front-matter is missing required 'description' field", + "reference/list-warehouse-imports.mdx: front-matter is missing required 'description' field", + "reference/list-warehouse-pipeline-jobs.mdx: front-matter is missing required 'description' field", + "reference/list-warehouse-pipeline-sync-dates.mdx: front-matter is missing required 'description' field", + "reference/lookup-tables.mdx: front-matter is missing required 'description' field", + "reference/lookup-tables.mdx: title \"Lookup Tables\" is also used by another page", + "reference/overview-1.mdx: front-matter is missing required 'description' field", + "reference/overview-1.mdx: title \"Overview\" is also used by another page", + "reference/overview-2.mdx: front-matter is missing required 'description' field", + "reference/overview-2.mdx: title \"Overview\" is also used by another page", + "reference/overview.mdx: front-matter is missing required 'description' field", + "reference/overview.mdx: title \"Overview\" is also used by another page", + "reference/patch-annotation-1.mdx: front-matter is missing required 'description' field", + "reference/pause-warehouse-pipeline.mdx: front-matter is missing required 'description' field", + "reference/permissions.mdx: front-matter is missing required 'description' field", + "reference/platform-api.mdx: front-matter is missing required 'description' field", + "reference/platform-api.mdx: title \"Overview\" is also used by another page", + "reference/profile-append-to-list-property.mdx: front-matter is missing required 'description' field", + "reference/profile-batch-update.mdx: front-matter is missing required 'description' field", + "reference/profile-delete-property.mdx: front-matter is missing required 'description' field", + "reference/profile-numerical-add.mdx: front-matter is missing required 'description' field", + "reference/profile-remove-from-list-property.mdx: front-matter is missing required 'description' field", + "reference/profile-set-property-once.mdx: front-matter is missing required 'description' field", + "reference/profile-set.mdx: front-matter is missing required 'description' field", + "reference/project-secret.mdx: front-matter is missing required 'description' field", + "reference/project-token.mdx: front-matter is missing required 'description' field", + "reference/query-api-authentication.mdx: front-matter is missing required 'description' field", + "reference/query-api-authentication.mdx: title \"Authentication\" is also used by another page", + "reference/query-api.mdx: front-matter is missing required 'description' field", + "reference/query-api.mdx: title \"Overview\" is also used by another page", + "reference/query-event-properties.mdx: front-matter is missing required 'description' field", + "reference/query-events-top-properties.mdx: front-matter is missing required 'description' field", + "reference/query-events-top-property-values.mdx: front-matter is missing required 'description' field", + "reference/query-jql.mdx: front-matter is missing required 'description' field", + "reference/query-months-top-event-names.mdx: front-matter is missing required 'description' field", + "reference/query-top-events.mdx: front-matter is missing required 'description' field", + "reference/rate-limits.mdx: front-matter is missing required 'description' field", + "reference/raw-data-export-api-authentication.mdx: front-matter is missing required 'description' field", + "reference/raw-data-export-api-authentication.mdx: title \"Authentication\" is also used by another page", + "reference/raw-data-export-api.mdx: front-matter is missing required 'description' field", + "reference/raw-data-export-api.mdx: title \"Overview\" is also used by another page", + "reference/raw-event-export.mdx: front-matter is missing required 'description' field", + "reference/remove-service-accounts-from-projects.mdx: front-matter is missing required 'description' field", + "reference/replace-lookup-table.mdx: front-matter is missing required 'description' field", + "reference/request-signature.mdx: front-matter is missing required 'description' field", + "reference/resume-warehouse-pipeline.mdx: front-matter is missing required 'description' field", + "reference/retention-frequency-query.mdx: front-matter is missing required 'description' field", + "reference/retention-query.mdx: front-matter is missing required 'description' field", + "reference/run-an-import-1.mdx: front-matter is missing required 'description' field", + "reference/run-an-import.mdx: front-matter is missing required 'description' field", + "reference/segmentation-expressions.mdx: front-matter is missing required 'description' field", + "reference/segmentation-numeric-query.mdx: front-matter is missing required 'description' field", + "reference/segmentation-query-average.mdx: front-matter is missing required 'description' field", + "reference/segmentation-query.mdx: front-matter is missing required 'description' field", + "reference/segmentation-sum-query.mdx: front-matter is missing required 'description' field", + "reference/service-accounts-api-authentication.mdx: front-matter is missing required 'description' field", + "reference/service-accounts-api-authentication.mdx: title \"Authentication\" is also used by another page", + "reference/service-accounts-api.mdx: front-matter is missing required 'description' field", + "reference/service-accounts-api.mdx: title \"Overview\" is also used by another page", + "reference/service-accounts.mdx: front-matter is missing required 'description' field", + "reference/track-event.mdx: front-matter is missing required 'description' field", + "reference/track-event.mdx: title \"Track Events\" is also used by another page", + "reference/unarchive-experiment.mdx: front-matter is missing required 'description' field", + "reference/update-experiment.mdx: front-matter is missing required 'description' field", + "reference/update-feature-flag-1.mdx: front-matter is missing required 'description' field", + "reference/update-warehouse-import.mdx: front-matter is missing required 'description' field", + "reference/upload-schema-by-entity-and-name.mdx: front-matter is missing required 'description' field", + "reference/upload-schemas-for-project.mdx: front-matter is missing required 'description' field", + "reference/user-profile-limits.mdx: front-matter is missing required 'description' field", + "reference/user-profile-limits.mdx: title \"Limits\" is also used by another page", + "reference/user-profile-union.mdx: front-matter is missing required 'description' field", + "reference/warehouse-connectors-api-authentication.mdx: front-matter is missing required 'description' field", + "reference/warehouse-connectors-api-authentication.mdx: title \"Authentication\" is also used by another page", + "reference/warehouse-connectors-api.mdx: front-matter is missing required 'description' field", + "reference/warehouse-connectors-api.mdx: title \"Overview\" is also used by another page", + "troubleshooting/faqs.mdx: front-matter is missing required 'description' field" + ], + "redirects": [ + "docs.json: redirect chain '/developer-docs-redirect/android-push-notifications' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/developer-docs-redirect/aws-raw-pipeline' -> '/docs/data-pipelines/integrations/aws-raw-pipeline' (destination is redirected again)", + "docs.json: redirect chain '/developer-docs-redirect/azure-raw-pipeline' -> '/docs/data-pipelines/integrations/azure-raw-pipeline' (destination is redirected again)", + "docs.json: redirect chain '/developer-docs-redirect/client-side-vs-server-side-tracking' -> '/docs/getting-started/plan-your-implementation#need-to-start-tracking-product-data' (destination is redirected again)", + "docs.json: redirect chain '/developer-docs-redirect/cloud-ingestion' -> '/docs/quickstart/track-events?sdk=httpapi' (destination is redirected again)", + "docs.json: redirect chain '/developer-docs-redirect/data-structure-deep-dive' -> '/docs/how-it-works/concepts' (destination is redirected again)", + "docs.json: redirect chain '/developer-docs-redirect/gcs' -> '/docs/data-pipelines/integrations/google-cloud-storage' (destination is redirected again)", + "docs.json: redirect chain '/developer-docs-redirect/gcs-raw-pipeline' -> '/docs/data-pipelines/integrations/google-cloud-storage-raw-pipeline' (destination is redirected again)", + "docs.json: redirect chain '/developer-docs-redirect/plan-your-implementation' -> '/docs/getting-started/plan-your-implementation' (destination is redirected again)", + "docs.json: redirect chain '/developer-docs-redirect/sending-events' -> '/docs/quickstart/track-events' (destination is redirected again)", + "docs.json: redirect chain '/developer-docs-redirect/under-the-hood' -> '/docs/how-it-works/infrastructure' (destination is redirected again)", + "docs.json: redirect chain '/docs/analysis/advanced/group-analytics' -> '/docs/data-structure/advanced/group-analytics' (destination is redirected again)", + "docs.json: redirect chain '/docs/analysis/advanced/other-advanced-features' -> '/docs/features/advanced' (destination is redirected again)", + "docs.json: redirect chain '/docs/analysis/advanced/other-advanced-features.md' -> '/docs/features/advanced' (destination is redirected again)", + "docs.json: redirect chain '/docs/analysis/boards/advanced' -> '/docs/boards/advanced' (destination is redirected again)", + "docs.json: redirect chain '/docs/android-push-notifications' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/docs/best-practices/analytics-strategy' -> '/guides/plan/framework' (destination is redirected again)", + "docs.json: redirect chain '/docs/best-practices/analytics-strategy/overview' -> '/guides/plan/framework' (destination is redirected again)", + "docs.json: redirect chain '/docs/best-practices/analytics-strategy/retention' -> '/guides/launch/track-user-retention' (destination is redirected again)", + "docs.json: redirect chain '/docs/data-pipelines/integrations/aws-raw-pipeline' -> '/docs/data-pipelines/integrations/raw-aws-pipeline' (destination is redirected again)", + "docs.json: redirect chain '/docs/features/revenue_analytics' -> '/guides/launch/revenue-analytics' (destination is redirected again)", + "docs.json: redirect chain '/docs/ios-push-notifications' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/data-pipelines/aws-raw-pipeline' -> '/docs/data-pipelines/integrations/aws-raw-pipeline' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/data-pipelines/azure-raw-pipeline' -> '/docs/data-pipelines/integrations/azure-raw-pipeline' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/data-pipelines/gcs' -> '/docs/data-pipelines/integrations/google-cloud-storage' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/data-pipelines/gcs-raw-pipeline' -> '/docs/data-pipelines/integrations/google-cloud-storage-raw-pipeline' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/tutorials/adoption-reference-guide' -> '/docs/best-practices/adoption' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/tutorials/analytics-strategy' -> '/docs/best-practices/analytics-strategy/overview' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/tutorials/creating-a-tracking-plan' -> '/docs/best-practices/create-a-tracking-plan' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/tutorials/developers/mixpanel-for-developers-commonly-asked-questions' -> '/docs/debugging/overview' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/tutorials/developers/mixpanel-for-developers-fundamentals' -> '/docs/how-it-works/concepts' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/tutorials/developers/mixpanel-for-developers-id-management' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/tutorials/mixpanel-analysis' -> '/docs/how-it-works/concepts#overview' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/tutorials/mixpanel-analysis/data-model' -> '/docs/how-it-works/concepts' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/tutorials/mixpanel-analysis/overview' -> '/docs/how-it-works/concepts' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/tutorials/setting-up-mixpanel' -> '/docs/best-practices/project-setup' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/under-the-hoo' -> '/docs/how-it-works/infrastructure' (destination is redirected again)", + "docs.json: redirect chain '/docs/other-bits/under-the-hood' -> '/docs/how-it-works/infrastructure' (destination is redirected again)", + "docs.json: redirect chain '/docs/session-replay/session-replay-android' -> '/docs/session-replay/implement-session-replay/session-replay-android' (destination is redirected again)", + "docs.json: redirect chain '/docs/session-replay/session-replay-ios' -> '/docs/session-replay/implement-session-replay/session-replay-ios' (destination is redirected again)", + "docs.json: redirect chain '/docs/session-replay/session-replay-web' -> '/docs/session-replay/implement-session-replay/session-replay-web' (destination is redirected again)", + "docs.json: redirect chain '/docs/sms-notifications' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking-methods/identifying-users' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/bigquery' -> '/docs/tracking-methods/data-warehouse/bigquery' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/data-warehouse' -> '/docs/tracking-methods/data-warehouse/overview' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/data-warehouse/bigquery' -> '/docs/tracking-methods/data-warehouse/bigquery' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/data-warehouse/databricks' -> '/docs/tracking-methods/data-warehouse/databricks' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/data-warehouse/events' -> '/docs/tracking-methods/data-warehouse/sending-events' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/data-warehouse/redshift' -> '/docs/tracking-methods/data-warehouse/redshift' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/data-warehouse/snowflake' -> '/docs/tracking-methods/data-warehouse/snowflake' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/data-warehouse/users' -> '/docs/tracking-methods/data-warehouse/sending-user-profiles' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/how-tos/ad-spend' -> '/docs/data-structure/advanced/ad-spend' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/how-tos/debugging' -> '/docs/debugging/overview' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/how-tos/effective-server' -> '/docs/best-practices/server-side-best-practices' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/how-tos/effective-server-side-tracking' -> '/docs/best-practices/server-side-best-practices' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/how-tos/identifying-users' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/how-tos/set-up-projects' -> '/docs/best-practices/developer-environments' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/how-tos/tracking-utm-tags' -> '/docs/data-structure/advanced/utm-tags' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/how-tos/utm' -> '/docs/data-structure/advanced/utm-tags' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/http-api' -> '/docs/quickstart/track-events?sdk=httpapi' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/integrations/bigquery' -> '/docs/tracking-methods/data-warehouse/bigquery' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/integrations/snowflake' -> '/docs/tracking-methods/data-warehouse/snowflake' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/mobile' -> '/docs/tracking/reference/mobile-sdk' (destination is redirected again)", + "docs.json: redirect chain '/docs/tracking/reference/data-model' -> '/docs/how-it-works/concepts' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/analytics-strategy' -> '/guides/analytics-strategy' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/beyond-onboarding' -> '/guides/beyond-onboarding' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/developers' -> '/docs/how-it-works/concepts' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/implement/establish-governance' -> '/guides/implement/establish-governance' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/implement/qa-data-audit' -> '/guides/implement/qa-data-audit' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/implement/send-your-data' -> '/guides/implement/send-your-data' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/launch/analyze-conversions' -> '/guides/launch/analyze-conversions' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/launch/build-user-flows' -> '/guides/launch/build-user-flows' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/launch/create-boards' -> '/guides/launch/create-boards' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/launch/define-cohorts' -> '/guides/launch/define-cohorts' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/launch/discover-insights' -> '/guides/launch/discover-insights' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/launch/track-user-retention' -> '/guides/launch/track-user-retention' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/mixpanel-analysis' -> '/docs/how-it-works/concepts#overview' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/onboarding-overview' -> '/guides/onboarding-overview' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/plan/framework' -> '/guides/plan/framework' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/plan/setup' -> '/guides/plan/setup' (destination is redirected again)", + "docs.json: redirect chain '/docs/tutorials/plan/tracking-strategy' -> '/guides/plan/tracking-strategy' (destination is redirected again)", + "docs.json: redirect chain '/guides/analytics-strategy' -> '/guides/plan/framework' (destination is redirected again)", + "docs.json: redirect chain '/guides/beyond-onboarding' -> '/guides/strategic-playbooks/onboarding-playbook/beyond-onboarding' (destination is redirected again)", + "docs.json: redirect chain '/guides/launch/analyze-conversions' -> '/guides/strategic-playbooks/onboarding-playbook/launch/analyze-conversions' (destination is redirected again)", + "docs.json: redirect chain '/guides/launch/build-user-flows' -> '/guides/strategic-playbooks/onboarding-playbook/launch/build-user-flows' (destination is redirected again)", + "docs.json: redirect chain '/guides/launch/create-boards' -> '/guides/strategic-playbooks/onboarding-playbook/launch/create-boards' (destination is redirected again)", + "docs.json: redirect chain '/guides/launch/define-cohorts' -> '/guides/strategic-playbooks/onboarding-playbook/launch/define-cohorts' (destination is redirected again)", + "docs.json: redirect chain '/guides/launch/discover-insights' -> '/guides/strategic-playbooks/onboarding-playbook/launch/discover-insights' (destination is redirected again)", + "docs.json: redirect chain '/guides/launch/revenue-analytics' -> '/guides/strategic-playbooks/onboarding-playbook/launch/revenue-analytics' (destination is redirected again)", + "docs.json: redirect chain '/guides/launch/track-user-retention' -> '/guides/strategic-playbooks/onboarding-playbook/launch/track-user-retention' (destination is redirected again)", + "docs.json: redirect chain '/guides/strategic-playbooks/onboarding-playbook/launch/analyze-conversions' -> '/guides/guides-by-topic/core-reports/analyze-conversions' (destination is redirected again)", + "docs.json: redirect chain '/guides/strategic-playbooks/onboarding-playbook/launch/build-user-flows' -> '/guides/guides-by-topic/core-reports/build-user-flows' (destination is redirected again)", + "docs.json: redirect chain '/guides/strategic-playbooks/onboarding-playbook/launch/create-boards' -> '/guides/guides-by-topic/core-reports/create-boards' (destination is redirected again)", + "docs.json: redirect chain '/guides/strategic-playbooks/onboarding-playbook/launch/define-cohorts' -> '/guides/guides-by-topic/core-reports/define-cohorts' (destination is redirected again)", + "docs.json: redirect chain '/guides/strategic-playbooks/onboarding-playbook/launch/discover-insights' -> '/guides/guides-by-topic/core-reports/discover-insights' (destination is redirected again)", + "docs.json: redirect chain '/guides/strategic-playbooks/onboarding-playbook/launch/track-user-retention' -> '/guides/guides-by-topic/core-reports/track-user-retention' (destination is redirected again)", + "docs.json: redirect chain '/hc/admin/general_settings' -> '/docs/admin/organizations-projects' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us' -> '/' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/10147279357076*' -> '/docs/debugging/overview#data-discrepancies' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004491523*' -> '/docs/best-practices/developer-environments#send-data-to-multiple-projects' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004491683*' -> '/docs/best-practices/developer-environments#when-to-use-multiple-production-projects' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004495783*' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004497803*' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004499323*' -> '/docs/' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004499343*' -> '/docs/best-practices/server-side-best-practices#tracking-geolocation' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004499403*' -> '/docs/features/advanced#undefined-and-null-properties' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004509406*' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004509426*' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004510946*' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004511086*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004511126*' -> '/docs/' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004511246*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004519886*' -> '/docs/best-practices/create-a-tracking-plan' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004545603*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004547063*' -> '/docs/how-it-works/concepts#supported-data-types' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004551583*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004561766*' -> '/docs/tracking/how-tos/utm' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004561786*' -> '/docs/tracking/how-tos/utm' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004562563*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004563123*' -> '/docs/' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004565766*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004567026*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004580746*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004581743*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004581763*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004582646*' -> '/docs/' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004582706*' -> '/docs/' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004582746*' -> '/docs/' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004600343*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004600723*' -> '/docs/data-structure/advanced/utm-tags#mobile-attribution' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004602143*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004602503*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004602603*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004602683*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004615466*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004615486*' -> '/docs/' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004615526*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004615566*' -> '/docs/' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004616406*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004616486*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004617246*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004617306*' -> '/docs/' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004617366*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004617386*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004617426*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004617486*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004617526*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004617566*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004617706*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004670543*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004670563*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004670603*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004670703*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004670743*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004670763*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004686363*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004686443*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004686503*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004686543*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004687563*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004687583*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004687743*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004688423*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004688623*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004690006*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004690046*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004690066*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004690086*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004690106*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004690126*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004690166*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004695303*' -> '/docs/' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004702823*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004706666*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004706906*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004706966*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004707726*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004707786*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004707946*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004734463*' -> '/docs/quickstart/track-events#http-api' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115004958823*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115005593506*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115005641266*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115005641306*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115005664806*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115005665706*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/115005717546*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/14377628688788*' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/14383975110292*' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/15235786397716*' -> '/docs/data-structure/advanced/ad-spend#gathering-data-from-ad-networks' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/15271983688212*' -> '/docs/features/attribution#overview' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360000857366*' -> '/docs/how-it-works/concepts' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360000865566*' -> '/docs/how-it-works/concepts' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360000894623*' -> '/docs/how-it-works/concepts' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360000953003*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360001131323*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360001321243*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360001337043*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360001337103*' -> '/docs/tracking/how-tos/utm' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360001354886*' -> '/docs/best-practices/developer-environments#separate-development-data' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360001355146*' -> '/docs/best-practices/server-side-best-practices#tracking-browser-device-and-os' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360001355206*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360001355466*' -> '/docs/best-practices/server-side-best-practices#identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360001360643*' -> '/docs/features/advanced#top-segments-logic' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360001361023*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360020461952*' -> '/docs/admin/organizations-projects' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360021085271*' -> '/docs/admin/organizations-projects' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360021916032*' -> '/docs/how-it-works/concepts' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360024910951*' -> '/docs/admin/organizations-projects' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360025333632*' -> '/docs/data-structure/advanced/group-analytics' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360027754631*' -> '/docs/features/advanced#query-result-caching' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360028380611*' -> '/docs/how-it-works/concepts' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360035109991*' -> '/docs/best-practices/create-a-tracking-plan' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360039133851*' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360041039771*' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360047094171*' -> '/docs/features/advanced#list-property-support' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360048486671*' -> '/docs/debugging/overview#inactive-events-and-properties' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/360061218812*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/4402828400020*' -> '/docs/features/advanced#limits-and-ordering' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/4402837164948*' -> '/docs/debugging/overview' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/4409841288724*' -> '/docs/boards/advanced#faq' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/4409850288276*' -> '/docs/boards/advanced' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/7715039510548*' -> '/docs/features/advanced#query-builder-features' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/articles/9648680824852*' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/categories/115000963103*' -> '/docs/admin/organizations-projects' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/categories/115001031023*' -> '/docs/how-it-works/concepts#overview' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/categories/115001197226*' -> '/docs' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/sections/115001299203*' -> '/docs/tracking-methods/id-management/identifying-users' (destination is redirected again)", + "docs.json: redirect chain '/hc/en-us/sections/115001308546*' -> '/docs/quickstart/track-events?sdk=httpapi' (destination is redirected again)" + ] +}