diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index f7c171f4..5d8d2a48 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,4 +1,4 @@ -name: Deploy Docs +name: "Docs: Build & Upload to R2" on: push: @@ -115,3 +115,19 @@ jobs: accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: deploy workingDirectory: worker + + - name: Notify Slack on failure + if: failure() + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_ALERTS_WEBHOOK_URL }} + REPO: ${{ github.repository }} + WORKFLOW: ${{ github.workflow }} + BRANCH: ${{ github.ref_name }} + SHA: ${{ github.sha }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + SHORT_SHA="${SHA:0:7}" + printf '🔴 WORKFLOW FAILED — %s\nRepo: %s\nBranch: %s @ %s\n%s' \ + "$WORKFLOW" "$REPO" "$BRANCH" "$SHORT_SHA" "$RUN_URL" \ + | jq -Rs '{text: .}' \ + | curl -s -X POST "$SLACK_WEBHOOK" -H 'Content-Type: application/json' -d @- diff --git a/.github/workflows/post-deploy-tests.yml b/.github/workflows/post-deploy-tests.yml index f657621e..74176921 100644 --- a/.github/workflows/post-deploy-tests.yml +++ b/.github/workflows/post-deploy-tests.yml @@ -1,4 +1,5 @@ -name: Post-Deploy Tests +name: "Docs: Post-Deploy Integration & E2E Tests" +run-name: "Docs: Post-Deploy Tests (${{ github.event.client_payload.environment || 'unknown' }})" on: repository_dispatch: @@ -35,3 +36,19 @@ jobs: env: TEST_ENV: ${{ github.event.client_payload.environment }} run: yarn test:e2e + + - name: Notify Slack on failure + if: failure() + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_ALERTS_WEBHOOK_URL }} + REPO: ${{ github.repository }} + WORKFLOW: ${{ github.workflow }} + BRANCH: ${{ github.ref_name }} + SHA: ${{ github.sha }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + SHORT_SHA="${SHA:0:7}" + printf '🔴 WORKFLOW FAILED — %s\nRepo: %s\nBranch: %s @ %s\n%s' \ + "$WORKFLOW" "$REPO" "$BRANCH" "$SHORT_SHA" "$RUN_URL" \ + | jq -Rs '{text: .}' \ + | curl -s -X POST "$SLACK_WEBHOOK" -H 'Content-Type: application/json' -d @- diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index dda1380b..7f856d4d 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -1,4 +1,4 @@ -name: PR Checks (Staging Integration Tests) +name: "Docs: PR Checks" on: pull_request: @@ -54,6 +54,22 @@ jobs: TEST_ENV: staging run: yarn test:integration + - name: Notify Slack on failure + if: failure() + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_ALERTS_WEBHOOK_URL }} + REPO: ${{ github.repository }} + WORKFLOW: ${{ github.workflow }} + BRANCH: ${{ github.ref_name }} + SHA: ${{ github.sha }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + SHORT_SHA="${SHA:0:7}" + printf '🔴 WORKFLOW FAILED — %s\nRepo: %s\nBranch: %s @ %s\n%s' \ + "$WORKFLOW" "$REPO" "$BRANCH" "$SHORT_SHA" "$RUN_URL" \ + | jq -Rs '{text: .}' \ + | curl -s -X POST "$SLACK_WEBHOOK" -H 'Content-Type: application/json' -d @- + staging-e2e-tests: runs-on: ubuntu-latest steps: @@ -72,3 +88,19 @@ jobs: env: TEST_ENV: staging run: yarn test:e2e + + - name: Notify Slack on failure + if: failure() + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_ALERTS_WEBHOOK_URL }} + REPO: ${{ github.repository }} + WORKFLOW: ${{ github.workflow }} + BRANCH: ${{ github.ref_name }} + SHA: ${{ github.sha }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + SHORT_SHA="${SHA:0:7}" + printf '🔴 WORKFLOW FAILED — %s\nRepo: %s\nBranch: %s @ %s\n%s' \ + "$WORKFLOW" "$REPO" "$BRANCH" "$SHORT_SHA" "$RUN_URL" \ + | jq -Rs '{text: .}' \ + | curl -s -X POST "$SLACK_WEBHOOK" -H 'Content-Type: application/json' -d @- diff --git a/.gitignore b/.gitignore index 0512b4f1..7a6f4a60 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ llm-docs/ *.draft .cursorignore .playwright-mcp/ +e2e/.auth/ test-results/ npm-debug.log* @@ -27,5 +28,6 @@ yarn-error.log* .env static/robots.txt +static/css/ worker/node_modules diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index cdec526b..00000000 --- a/docs/index.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Introduction -sidebar_position: 1 -noindex: true ---- diff --git a/docusaurus.config.js b/docusaurus.config.js index 481ae109..a7380963 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -9,7 +9,7 @@ require("dotenv").config(); /** @type {import('@docusaurus/types').Config} */ const config = { title: - process.env.PROD == "true" ? "Market Data" : "Market Data Docs (dev)", + process.env.PROD == "true" ? "Market Data" : "Market Data Docs (staging)", tagline: "Complete Documentation For All Market Data Products & Services", url: @@ -35,6 +35,13 @@ const config = { content: "BAA3BC0EFD344D0C", }, }, + { + tagName: "link", + attributes: { + rel: "stylesheet", + href: "/docs/css/components.no-reset.css", + }, + }, ], i18n: { @@ -47,9 +54,10 @@ const config = { "classic", /** @type {import('@docusaurus/preset-classic').Options} */ ({ + docs: false, blog: false, theme: { - customCss: require.resolve("./src/css/custom.css"), + customCss: [require.resolve("./src/css/custom.css")], }, sitemap: process.env.PROD == "true" @@ -64,7 +72,10 @@ const config = { ], ], - clientModules: ['./src/clientModules/themeCookieSync.js'], + clientModules: [ + './src/clientModules/themeCookieSync.js', + './src/clientModules/navbarOverflow.js', + ], plugins: [ './plugins/theme-cookie-sync', @@ -222,8 +233,7 @@ const config = { position: "right", }, { - href: "https://github.com/MarketDataApp/documentation", - label: "GitHub", + type: "custom-UserProfile", position: "right", }, ], diff --git a/package.json b/package.json index a0d91e3f..9fa1ead7 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,9 @@ "private": true, "scripts": { "docusaurus": "docusaurus", - "start": "docusaurus start", - "build": "docusaurus build", + "copy:ui-css": "mkdir -p static/css && cp node_modules/@marketdataapp/ui/dist/css/components.no-reset.css static/css/", + "start": "yarn copy:ui-css && docusaurus start", + "build": "yarn copy:ui-css && docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", @@ -23,7 +24,7 @@ "@docusaurus/plugin-client-redirects": "3.0.1", "@docusaurus/plugin-content-docs": "3.0.1", "@docusaurus/preset-classic": "3.0.1", - "@marketdataapp/ui": "github:MarketDataApp/ui", + "@marketdataapp/ui": "github:MarketDataApp/ui#main", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "dotenv": "^16.0.2", diff --git a/playwright.config.js b/playwright.config.js index b8b784db..2f46ca3f 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -1,13 +1,23 @@ import { defineConfig } from '@playwright/test'; +try { + process.loadEnvFile(); +} catch {} + export default defineConfig({ testDir: './e2e', timeout: 30_000, retries: 1, use: { headless: true, + screenshot: 'only-on-failure', + trace: 'on-first-retry', }, projects: [ - { name: 'chromium', use: { browserName: 'chromium' } }, + { + name: 'smoke', + testMatch: ['context7-widget.spec.js'], + use: { browserName: 'chromium' }, + }, ], }); diff --git a/src/clientModules/navbarOverflow.js b/src/clientModules/navbarOverflow.js new file mode 100644 index 00000000..6c3c2b77 --- /dev/null +++ b/src/clientModules/navbarOverflow.js @@ -0,0 +1,17 @@ +import { initNavbarOverflow } from '@marketdataapp/ui/navbar-overflow'; + +export function onRouteDidUpdate() { + const container = document.querySelector('.navbar__inner'); + if (!container || container._overflowInit) return; + + container._overflowInit = true; + initNavbarOverflow({ + container, + items: [ + // Priority order: lowest number = hidden first + { selector: '.navbar__items--right [class*="colorModeToggle"]', priority: 1 }, + { selector: '.user-profile-wrapper', priority: 2 }, + { selector: '.navbar__items--right .DocSearch-Button', priority: 3 }, + ], + }); +} diff --git a/src/css/custom.css b/src/css/custom.css index c7da7a30..d2554bb7 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -202,6 +202,57 @@ background-color: #E65100; /* Beta: Orange */ } +/* ========================================================================== + Navbar overflow prevention + At desktop widths (≥997px) the right-side items (search, avatar, username, + logout button, dark-mode toggle) can overflow the navbar when a user is + logged in. These rules keep everything on one line: + - flex-wrap: nowrap on .navbar__inner stops the two halves from stacking. + - white-space: nowrap on nav links stops multi-word labels ("Sheets Add-On", + "Accounts & Billing") from wrapping inside their flex item at ~1024px. + - The JS utility (navbarOverflow.js) then hides lower-priority right-side + items one at a time until nothing is compressed. + ========================================================================== */ +@media (min-width: 997px) { + .navbar__inner { + flex-wrap: nowrap; + } + .navbar__items .navbar__link { + white-space: nowrap; + } +} + +/* Navbar user-profile positioning + Desktop (≥997px): 12px gap before the dark-mode toggle. + Below desktop (<997px): Docusaurus absolutely-positions the search bar to + the right, which puts the profile to its left. Override search back to + normal flow so both items participate in flex layout and the profile + (later in DOM order) stays on the far right. */ +.navbar__items--right .user-profile-wrapper { + align-self: center; + line-height: 0; +} +@media (min-width: 997px) { + .navbar__items--right .user-profile-wrapper { + margin-right: 12px; + } +} +@media (max-width: 996px) { + .navbar__items--right [class*="navbarSearchContainer"] { + position: relative; + right: auto; + } + .navbar__items--right .user-profile-wrapper { + margin-left: 12px; + } +} + +/* Generic hidden utility — required by the UI library's dropdown toggle + (classList.add('hidden') / classList.remove('hidden')). */ +.hidden { + display: none; +} + /* Clear floats so following content starts below (e.g. after float: right images) */ .clear-float { clear: both; diff --git a/src/theme/NavbarItem/ComponentTypes.js b/src/theme/NavbarItem/ComponentTypes.js new file mode 100644 index 00000000..30ef4de2 --- /dev/null +++ b/src/theme/NavbarItem/ComponentTypes.js @@ -0,0 +1,7 @@ +import ComponentTypes from '@theme-original/NavbarItem/ComponentTypes'; +import UserProfile from '@site/src/theme/NavbarItem/UserProfile'; + +export default { + ...ComponentTypes, + 'custom-UserProfile': UserProfile, +}; diff --git a/src/theme/NavbarItem/UserProfile.js b/src/theme/NavbarItem/UserProfile.js new file mode 100644 index 00000000..2f003c27 --- /dev/null +++ b/src/theme/NavbarItem/UserProfile.js @@ -0,0 +1,33 @@ +import React, { useRef, useEffect } from 'react'; +import { initUserProfile } from '@marketdataapp/ui/user-profile'; + +export default function UserProfile({ mobile }) { + const ref = useRef(null); + + useEffect(() => { + if (mobile) return; + let cancelled = false; + let cleanup; + initUserProfile({ + container: ref.current, + dropdown: true, + loginUrl: 'https://www.marketdata.app/dashboard/', + logoutUrl: 'https://dashboard.marketdata.app/marketdata/logout', + dashboardUrl: 'https://www.marketdata.app/dashboard/', + loginText: 'Log in', + }).then((fn) => { + if (cancelled) { + fn(); + return; + } + cleanup = fn; + }); + return () => { + cancelled = true; + if (cleanup) cleanup(); + }; + }, [mobile]); + + if (mobile) return null; + return
; +} diff --git a/src/theme/Root.js b/src/theme/Root.js index e23fdc5e..a71b0a6c 100644 --- a/src/theme/Root.js +++ b/src/theme/Root.js @@ -2,12 +2,16 @@ import React from 'react'; import Head from '@docusaurus/Head'; import Context7Widget from '@site/src/components/Context7Widget'; +const isProd = process.env.NODE_ENV === 'production'; + export default function Root({children}) { return ( <> - -