@amplitude/wizard 1.0.0-beta.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -74
- package/dist/bin.js +338 -222
- package/dist/src/lib/agent-interface.js +64 -9
- package/dist/src/lib/agent-runner.js +1 -10
- package/dist/src/lib/api.d.ts +22 -4
- package/dist/src/lib/api.js +114 -12
- package/dist/src/lib/commandments.js +14 -1
- package/dist/src/lib/constants.d.ts +6 -5
- package/dist/src/lib/constants.js +13 -13
- package/dist/src/lib/credential-resolution.d.ts +45 -0
- package/dist/src/lib/credential-resolution.js +311 -0
- package/dist/src/lib/exit-codes.d.ts +10 -0
- package/dist/src/lib/exit-codes.js +12 -0
- package/dist/src/lib/health-checks/statuspage.d.ts +1 -0
- package/dist/src/lib/health-checks/statuspage.js +5 -1
- package/dist/src/lib/mode-config.d.ts +14 -0
- package/dist/src/lib/mode-config.js +14 -0
- package/dist/src/lib/session-checkpoint.d.ts +27 -0
- package/dist/src/lib/session-checkpoint.js +134 -0
- package/dist/src/lib/wizard-session.d.ts +44 -1
- package/dist/src/lib/wizard-session.js +70 -14
- package/dist/src/lib/wizard-tools.js +19 -4
- package/dist/src/steps/add-mcp-server-to-clients/clients/claude.d.ts +3 -0
- package/dist/src/steps/add-mcp-server-to-clients/clients/claude.js +6 -0
- package/dist/src/steps/add-mcp-server-to-clients/clients/cursor.js +3 -1
- package/dist/src/ui/agent-ui.d.ts +91 -0
- package/dist/src/ui/agent-ui.js +277 -0
- package/dist/src/ui/logging-ui.js +1 -1
- package/dist/src/ui/tui/App.d.ts +12 -0
- package/dist/src/ui/tui/App.js +29 -18
- package/dist/src/ui/tui/components/AmplitudeLogo.js +16 -17
- package/dist/src/ui/tui/components/AmplitudeTextLogo.d.ts +0 -2
- package/dist/src/ui/tui/components/AmplitudeTextLogo.js +53 -18
- package/dist/src/ui/tui/components/BrailleSpinner.d.ts +8 -0
- package/dist/src/ui/tui/components/BrailleSpinner.js +15 -0
- package/dist/src/ui/tui/components/ConsoleView.d.ts +8 -11
- package/dist/src/ui/tui/components/ConsoleView.js +51 -34
- package/dist/src/ui/tui/components/HeaderBar.d.ts +12 -0
- package/dist/src/ui/tui/components/HeaderBar.js +17 -0
- package/dist/src/ui/tui/components/JourneyStepper.d.ts +16 -0
- package/dist/src/ui/tui/components/JourneyStepper.js +83 -0
- package/dist/src/ui/tui/components/KeyHintBar.d.ts +19 -0
- package/dist/src/ui/tui/components/KeyHintBar.js +20 -0
- package/dist/src/ui/tui/console-commands.d.ts +1 -2
- package/dist/src/ui/tui/console-commands.js +48 -7
- package/dist/src/ui/tui/flows.d.ts +1 -1
- package/dist/src/ui/tui/flows.js +1 -1
- package/dist/src/ui/tui/hooks/useAsyncEffect.d.ts +15 -0
- package/dist/src/ui/tui/hooks/useAsyncEffect.js +35 -0
- package/dist/src/ui/tui/hooks/useWizardStore.d.ts +9 -0
- package/dist/src/ui/tui/hooks/useWizardStore.js +11 -0
- package/dist/src/ui/tui/ink-ui.js +1 -1
- package/dist/src/ui/tui/primitives/DissolveTransition.js +4 -5
- package/dist/src/ui/tui/primitives/EventPlanViewer.d.ts +3 -1
- package/dist/src/ui/tui/primitives/EventPlanViewer.js +8 -3
- package/dist/src/ui/tui/primitives/ProgressList.js +1 -1
- package/dist/src/ui/tui/primitives/SlashCommandInput.js +19 -4
- package/dist/src/ui/tui/primitives/SplitView.d.ts +2 -1
- package/dist/src/ui/tui/primitives/SplitView.js +10 -2
- package/dist/src/ui/tui/primitives/TabContainer.js +10 -2
- package/dist/src/ui/tui/primitives/index.d.ts +0 -1
- package/dist/src/ui/tui/primitives/index.js +0 -1
- package/dist/src/ui/tui/router.js +1 -1
- package/dist/src/ui/tui/screen-registry.d.ts +0 -7
- package/dist/src/ui/tui/screen-registry.js +13 -4
- package/dist/src/ui/tui/screens/ActivationOptionsScreen.d.ts +2 -2
- package/dist/src/ui/tui/screens/ActivationOptionsScreen.js +8 -8
- package/dist/src/ui/tui/screens/AuthScreen.js +57 -27
- package/dist/src/ui/tui/screens/ChecklistScreen.d.ts +2 -12
- package/dist/src/ui/tui/screens/ChecklistScreen.js +22 -33
- package/dist/src/ui/tui/screens/DataIngestionCheckScreen.d.ts +3 -12
- package/dist/src/ui/tui/screens/DataIngestionCheckScreen.js +109 -39
- package/dist/src/ui/tui/screens/DataSetupScreen.d.ts +3 -3
- package/dist/src/ui/tui/screens/DataSetupScreen.js +17 -10
- package/dist/src/ui/tui/screens/IntroScreen.d.ts +5 -3
- package/dist/src/ui/tui/screens/IntroScreen.js +132 -41
- package/dist/src/ui/tui/screens/LoginScreen.d.ts +1 -1
- package/dist/src/ui/tui/screens/LoginScreen.js +4 -4
- package/dist/src/ui/tui/screens/LogoutScreen.d.ts +4 -2
- package/dist/src/ui/tui/screens/LogoutScreen.js +17 -5
- package/dist/src/ui/tui/screens/McpScreen.d.ts +4 -4
- package/dist/src/ui/tui/screens/McpScreen.js +25 -17
- package/dist/src/ui/tui/screens/OutageScreen.d.ts +1 -1
- package/dist/src/ui/tui/screens/OutageScreen.js +5 -5
- package/dist/src/ui/tui/screens/OutroScreen.d.ts +5 -0
- package/dist/src/ui/tui/screens/OutroScreen.js +21 -14
- package/dist/src/ui/tui/screens/RegionSelectScreen.js +15 -13
- package/dist/src/ui/tui/screens/RunScreen.d.ts +7 -5
- package/dist/src/ui/tui/screens/RunScreen.js +102 -157
- package/dist/src/ui/tui/screens/SettingsOverrideScreen.d.ts +1 -1
- package/dist/src/ui/tui/screens/SettingsOverrideScreen.js +6 -5
- package/dist/src/ui/tui/screens/SetupScreen.d.ts +1 -1
- package/dist/src/ui/tui/screens/SetupScreen.js +7 -7
- package/dist/src/ui/tui/screens/SlackScreen.d.ts +2 -2
- package/dist/src/ui/tui/screens/SlackScreen.js +60 -35
- package/dist/src/ui/tui/session-constants.d.ts +41 -0
- package/dist/src/ui/tui/session-constants.js +38 -0
- package/dist/src/ui/tui/start-tui.d.ts +3 -1
- package/dist/src/ui/tui/start-tui.js +14 -10
- package/dist/src/ui/tui/store.d.ts +2 -1
- package/dist/src/ui/tui/store.js +33 -7
- package/dist/src/ui/tui/styles.d.ts +75 -19
- package/dist/src/ui/tui/styles.js +101 -19
- package/dist/src/ui/tui/utils/classify-error.d.ts +14 -0
- package/dist/src/ui/tui/utils/classify-error.js +90 -0
- package/dist/src/ui/tui/utils/diagnostics.d.ts +21 -0
- package/dist/src/ui/tui/utils/diagnostics.js +72 -0
- package/dist/src/ui/tui/utils/with-retry.d.ts +12 -0
- package/dist/src/ui/tui/utils/with-retry.js +32 -0
- package/dist/src/ui/tui/utils/with-timeout.d.ts +10 -0
- package/dist/src/ui/tui/utils/with-timeout.js +24 -0
- package/dist/src/utils/ampli-settings.d.ts +1 -1
- package/dist/src/utils/ampli-settings.js +15 -5
- package/dist/src/utils/api-key-store.js +5 -5
- package/dist/src/utils/atomic-write.d.ts +15 -0
- package/dist/src/utils/atomic-write.js +34 -0
- package/dist/src/utils/setup-utils.js +2 -2
- package/dist/src/utils/token-refresh.d.ts +22 -0
- package/dist/src/utils/token-refresh.js +79 -0
- package/dist/src/utils/wizard-abort.js +6 -1
- package/package.json +6 -6
- package/skills/instrumentation/add-analytics-instrumentation/SKILL.md +142 -0
- package/skills/instrumentation/diff-intake/SKILL.md +128 -0
- package/skills/instrumentation/discover-analytics-patterns/SKILL.md +185 -0
- package/skills/instrumentation/discover-event-surfaces/SKILL.md +322 -0
- package/skills/instrumentation/discover-event-surfaces/references/best-practices.md +563 -0
- package/skills/instrumentation/instrument-events/SKILL.md +169 -0
- package/skills/instrumentation/instrument-events/references/best-practices.md +563 -0
- package/skills/integration/integration-android/SKILL.md +49 -0
- package/skills/integration/integration-android/references/EXAMPLE.md +1977 -0
- package/skills/integration/integration-android/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-android/references/analytics.md +1778 -0
- package/skills/integration/integration-android/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-android/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-android/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-android/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-angular/SKILL.md +49 -0
- package/skills/integration/integration-angular/references/EXAMPLE.md +899 -0
- package/skills/integration/integration-angular/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-angular/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-angular/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-angular/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-angular/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-angular/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-astro-hybrid/SKILL.md +56 -0
- package/skills/integration/integration-astro-hybrid/references/EXAMPLE.md +1095 -0
- package/skills/integration/integration-astro-hybrid/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-astro-hybrid/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-astro-hybrid/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-astro-hybrid/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-astro-hybrid/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-astro-hybrid/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-astro-ssr/SKILL.md +52 -0
- package/skills/integration/integration-astro-ssr/references/EXAMPLE.md +1106 -0
- package/skills/integration/integration-astro-ssr/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-astro-ssr/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-astro-ssr/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-astro-ssr/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-astro-ssr/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-astro-ssr/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-astro-static/SKILL.md +49 -0
- package/skills/integration/integration-astro-static/references/EXAMPLE.md +910 -0
- package/skills/integration/integration-astro-static/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-astro-static/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-astro-static/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-astro-static/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-astro-static/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-astro-static/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-astro-view-transitions/SKILL.md +51 -0
- package/skills/integration/integration-astro-view-transitions/references/EXAMPLE.md +979 -0
- package/skills/integration/integration-astro-view-transitions/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-astro-view-transitions/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-astro-view-transitions/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-astro-view-transitions/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-astro-view-transitions/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-astro-view-transitions/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-django/SKILL.md +57 -0
- package/skills/integration/integration-django/references/EXAMPLE.md +1005 -0
- package/skills/integration/integration-django/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-django/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-django/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-django/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-django/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-django/references/python.md +1424 -0
- package/skills/integration/integration-expo/SKILL.md +53 -0
- package/skills/integration/integration-expo/references/EXAMPLE.md +1291 -0
- package/skills/integration/integration-expo/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-expo/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-expo/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-expo/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-expo/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-expo/references/react-native-sdk.md +2819 -0
- package/skills/integration/integration-fastapi/SKILL.md +57 -0
- package/skills/integration/integration-fastapi/references/EXAMPLE.md +1389 -0
- package/skills/integration/integration-fastapi/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-fastapi/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-fastapi/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-fastapi/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-fastapi/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-fastapi/references/python.md +1424 -0
- package/skills/integration/integration-flask/SKILL.md +56 -0
- package/skills/integration/integration-flask/references/EXAMPLE.md +1130 -0
- package/skills/integration/integration-flask/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-flask/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-flask/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-flask/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-flask/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-flask/references/python.md +1424 -0
- package/skills/integration/integration-javascript_node/SKILL.md +54 -0
- package/skills/integration/integration-javascript_node/references/EXAMPLE.md +365 -0
- package/skills/integration/integration-javascript_node/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-javascript_node/references/analytics.md +1778 -0
- package/skills/integration/integration-javascript_node/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-javascript_node/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-javascript_node/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-javascript_node/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-javascript_web/SKILL.md +58 -0
- package/skills/integration/integration-javascript_web/references/EXAMPLE.md +451 -0
- package/skills/integration/integration-javascript_web/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-javascript_web/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-javascript_web/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-javascript_web/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-javascript_web/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-javascript_web/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-laravel/SKILL.md +52 -0
- package/skills/integration/integration-laravel/references/EXAMPLE.md +2039 -0
- package/skills/integration/integration-laravel/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-laravel/references/analytics.md +1778 -0
- package/skills/integration/integration-laravel/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-laravel/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-laravel/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-laravel/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-nextjs-app-router/SKILL.md +54 -0
- package/skills/integration/integration-nextjs-app-router/references/EXAMPLE.md +673 -0
- package/skills/integration/integration-nextjs-app-router/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-nextjs-app-router/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-nextjs-app-router/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-nextjs-app-router/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-nextjs-app-router/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-nextjs-app-router/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-nextjs-pages-router/SKILL.md +54 -0
- package/skills/integration/integration-nextjs-pages-router/references/EXAMPLE.md +735 -0
- package/skills/integration/integration-nextjs-pages-router/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-nextjs-pages-router/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-nextjs-pages-router/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-nextjs-pages-router/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-nextjs-pages-router/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-nextjs-pages-router/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-nuxt-3.6/SKILL.md +46 -0
- package/skills/integration/integration-nuxt-3.6/references/EXAMPLE.md +8422 -0
- package/skills/integration/integration-nuxt-3.6/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-nuxt-3.6/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-nuxt-3.6/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-nuxt-3.6/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-nuxt-3.6/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-nuxt-3.6/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-nuxt-4/SKILL.md +46 -0
- package/skills/integration/integration-nuxt-4/references/EXAMPLE.md +8670 -0
- package/skills/integration/integration-nuxt-4/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-nuxt-4/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-nuxt-4/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-nuxt-4/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-nuxt-4/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-nuxt-4/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-python/SKILL.md +53 -0
- package/skills/integration/integration-python/references/EXAMPLE.md +445 -0
- package/skills/integration/integration-python/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-python/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-python/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-python/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-python/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-python/references/python.md +1424 -0
- package/skills/integration/integration-react-native/SKILL.md +49 -0
- package/skills/integration/integration-react-native/references/EXAMPLE.md +2253 -0
- package/skills/integration/integration-react-native/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-native/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-native/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-native/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-native/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-native/references/react-native-sdk.md +2819 -0
- package/skills/integration/integration-react-react-router-6/SKILL.md +53 -0
- package/skills/integration/integration-react-react-router-6/references/EXAMPLE.md +570 -0
- package/skills/integration/integration-react-react-router-6/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-react-router-6/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-react-router-6/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-react-router-6/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-react-router-6/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-react-router-6/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-react-react-router-7-data/SKILL.md +53 -0
- package/skills/integration/integration-react-react-router-7-data/references/EXAMPLE.md +830 -0
- package/skills/integration/integration-react-react-router-7-data/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-react-router-7-data/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-react-router-7-data/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-react-router-7-data/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-react-router-7-data/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-react-router-7-data/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-react-react-router-7-declarative/SKILL.md +53 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/EXAMPLE.md +609 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-react-react-router-7-framework/SKILL.md +53 -0
- package/skills/integration/integration-react-react-router-7-framework/references/EXAMPLE.md +1081 -0
- package/skills/integration/integration-react-react-router-7-framework/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-react-router-7-framework/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-react-router-7-framework/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-react-router-7-framework/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-react-router-7-framework/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-react-router-7-framework/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-react-tanstack-router-code-based/SKILL.md +57 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/EXAMPLE.md +659 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-react-tanstack-router-file-based/SKILL.md +57 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/EXAMPLE.md +777 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-react-vite/SKILL.md +53 -0
- package/skills/integration/integration-react-vite/references/EXAMPLE.md +542 -0
- package/skills/integration/integration-react-vite/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-vite/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-vite/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-vite/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-vite/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-vite/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-ruby/SKILL.md +50 -0
- package/skills/integration/integration-ruby/references/EXAMPLE.md +420 -0
- package/skills/integration/integration-ruby/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-ruby/references/analytics.md +1778 -0
- package/skills/integration/integration-ruby/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-ruby/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-ruby/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-ruby/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-ruby-on-rails/SKILL.md +55 -0
- package/skills/integration/integration-ruby-on-rails/references/EXAMPLE.md +1013 -0
- package/skills/integration/integration-ruby-on-rails/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-ruby-on-rails/references/analytics.md +1778 -0
- package/skills/integration/integration-ruby-on-rails/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-ruby-on-rails/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-ruby-on-rails/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-ruby-on-rails/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-sveltekit/SKILL.md +47 -0
- package/skills/integration/integration-sveltekit/references/EXAMPLE.md +14121 -0
- package/skills/integration/integration-sveltekit/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-sveltekit/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-sveltekit/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-sveltekit/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-sveltekit/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-sveltekit/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-swift/SKILL.md +49 -0
- package/skills/integration/integration-swift/references/EXAMPLE.md +660 -0
- package/skills/integration/integration-swift/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-swift/references/analytics.md +1778 -0
- package/skills/integration/integration-swift/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-swift/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-swift/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-swift/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-tanstack-start/SKILL.md +58 -0
- package/skills/integration/integration-tanstack-start/references/EXAMPLE.md +998 -0
- package/skills/integration/integration-tanstack-start/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-tanstack-start/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-tanstack-start/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-tanstack-start/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-tanstack-start/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-tanstack-start/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-vue-3/SKILL.md +46 -0
- package/skills/integration/integration-vue-3/references/EXAMPLE.md +846 -0
- package/skills/integration/integration-vue-3/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-vue-3/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-vue-3/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-vue-3/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-vue-3/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-vue-3/references/browser-sdk-2.md +4680 -0
- package/skills/taxonomy/amplitude-quickstart-taxonomy-agent/SKILL.md +228 -0
- package/dist/src/ui/tui/components/TitleBar.d.ts +0 -8
- package/dist/src/ui/tui/components/TitleBar.js +0 -27
- package/dist/src/ui/tui/primitives/KagiSmallWebViewer.d.ts +0 -7
- package/dist/src/ui/tui/primitives/KagiSmallWebViewer.js +0 -101
- package/dist/src/utils/anthropic-status.d.ts +0 -17
- package/dist/src/utils/anthropic-status.js +0 -51
|
@@ -0,0 +1,1130 @@
|
|
|
1
|
+
# Amplitude Flask Example Project
|
|
2
|
+
|
|
3
|
+
Repository: https://github.com/amplitude/context-hub
|
|
4
|
+
Path: basics/flask
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## README.md
|
|
9
|
+
|
|
10
|
+
# Amplitude Flask Example
|
|
11
|
+
|
|
12
|
+
A Flask application demonstrating Amplitude integration for analytics and event tracking.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- User registration and authentication with Flask-Login
|
|
17
|
+
- SQLite database persistence with Flask-SQLAlchemy
|
|
18
|
+
- User identification and property tracking
|
|
19
|
+
- Custom event tracking
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
1. Create and activate a virtual environment:
|
|
24
|
+
```bash
|
|
25
|
+
python -m venv venv
|
|
26
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
2. Install dependencies:
|
|
30
|
+
```bash
|
|
31
|
+
pip install -r requirements.txt
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
3. Copy the environment file and configure:
|
|
35
|
+
```bash
|
|
36
|
+
cp .env.example .env
|
|
37
|
+
# Edit .env with your Amplitude API key
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
4. Run the application:
|
|
41
|
+
```bash
|
|
42
|
+
python run.py
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
5. Open http://localhost:5001 and either:
|
|
46
|
+
- Login with default credentials: `admin@example.com` / `admin`
|
|
47
|
+
- Or click "Sign up here" to create a new account
|
|
48
|
+
|
|
49
|
+
## Amplitude Integration Points
|
|
50
|
+
|
|
51
|
+
### User Registration
|
|
52
|
+
New users are identified and tracked on signup:
|
|
53
|
+
```python
|
|
54
|
+
client = get_amplitude_client()
|
|
55
|
+
if client:
|
|
56
|
+
identify_obj = Identify()
|
|
57
|
+
identify_obj.set('email', user.email)
|
|
58
|
+
identify_obj.set('is_staff', user.is_staff)
|
|
59
|
+
identify_obj.set('date_joined', user.date_joined.isoformat())
|
|
60
|
+
client.identify(identify_obj, {'user_id': user.email})
|
|
61
|
+
|
|
62
|
+
client.track(BaseEvent(
|
|
63
|
+
event_type='User Signed Up',
|
|
64
|
+
user_id=user.email,
|
|
65
|
+
event_properties={'signup_method': 'form'},
|
|
66
|
+
))
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### User Identification
|
|
70
|
+
Users are identified on login with their properties:
|
|
71
|
+
```python
|
|
72
|
+
client = get_amplitude_client()
|
|
73
|
+
if client:
|
|
74
|
+
identify_obj = Identify()
|
|
75
|
+
identify_obj.set('email', user.email)
|
|
76
|
+
identify_obj.set('is_staff', user.is_staff)
|
|
77
|
+
client.identify(identify_obj, {'user_id': user.email})
|
|
78
|
+
|
|
79
|
+
client.track(BaseEvent(
|
|
80
|
+
event_type='User Logged In',
|
|
81
|
+
user_id=user.email,
|
|
82
|
+
event_properties={'login_method': 'password'},
|
|
83
|
+
))
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Event Tracking
|
|
87
|
+
Custom events are tracked throughout the app:
|
|
88
|
+
```python
|
|
89
|
+
client = get_amplitude_client()
|
|
90
|
+
if client:
|
|
91
|
+
client.track(BaseEvent(
|
|
92
|
+
event_type='Burrito Considered',
|
|
93
|
+
user_id=current_user.email,
|
|
94
|
+
event_properties={'total_considerations': count},
|
|
95
|
+
))
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Project Structure
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
basics/flask/
|
|
102
|
+
├── app/
|
|
103
|
+
│ ├── __init__.py # Application factory
|
|
104
|
+
│ ├── config.py # Configuration classes
|
|
105
|
+
│ ├── extensions.py # Extension instances
|
|
106
|
+
│ ├── models.py # User model (SQLAlchemy)
|
|
107
|
+
│ ├── main/
|
|
108
|
+
│ │ ├── __init__.py # Main blueprint
|
|
109
|
+
│ │ └── routes.py # View functions
|
|
110
|
+
│ ├── templates/ # HTML templates
|
|
111
|
+
│ └── api/
|
|
112
|
+
│ ├── __init__.py # API blueprint
|
|
113
|
+
│ └── routes.py # API endpoints
|
|
114
|
+
├── .env.example
|
|
115
|
+
├── .gitignore
|
|
116
|
+
├── requirements.txt
|
|
117
|
+
├── README.md
|
|
118
|
+
└── run.py # Entry point
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## .env.example
|
|
124
|
+
|
|
125
|
+
```example
|
|
126
|
+
AMPLITUDE_API_KEY=your_amplitude_api_key_here
|
|
127
|
+
FLASK_SECRET_KEY=your-secret-key-here
|
|
128
|
+
FLASK_DEBUG=True
|
|
129
|
+
AMPLITUDE_DISABLED=False
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## app/__init__.py
|
|
136
|
+
|
|
137
|
+
```py
|
|
138
|
+
"""Flask application factory."""
|
|
139
|
+
|
|
140
|
+
from flask import Flask, jsonify, render_template, request
|
|
141
|
+
|
|
142
|
+
from app.config import config
|
|
143
|
+
from app.extensions import db, login_manager
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def create_app(config_name="default"):
|
|
147
|
+
"""Application factory."""
|
|
148
|
+
app = Flask(__name__)
|
|
149
|
+
app.config.from_object(config[config_name])
|
|
150
|
+
|
|
151
|
+
# Initialize extensions
|
|
152
|
+
db.init_app(app)
|
|
153
|
+
login_manager.init_app(app)
|
|
154
|
+
|
|
155
|
+
# Import models after db is initialized
|
|
156
|
+
from app.models import User
|
|
157
|
+
|
|
158
|
+
# User loader for Flask-Login
|
|
159
|
+
@login_manager.user_loader
|
|
160
|
+
def load_user(user_id):
|
|
161
|
+
return User.get_by_id(user_id)
|
|
162
|
+
|
|
163
|
+
# Simple error handlers
|
|
164
|
+
@app.errorhandler(404)
|
|
165
|
+
def page_not_found(e):
|
|
166
|
+
if request.path.startswith('/api/'):
|
|
167
|
+
return jsonify({"error": "Not found"}), 404
|
|
168
|
+
return render_template('errors/404.html'), 404
|
|
169
|
+
|
|
170
|
+
@app.errorhandler(500)
|
|
171
|
+
def internal_server_error(e):
|
|
172
|
+
if request.path.startswith('/api/'):
|
|
173
|
+
return jsonify({"error": "Internal server error"}), 500
|
|
174
|
+
return render_template('errors/500.html'), 500
|
|
175
|
+
|
|
176
|
+
# Register blueprints
|
|
177
|
+
from app.api import api_bp
|
|
178
|
+
from app.main import main_bp
|
|
179
|
+
|
|
180
|
+
app.register_blueprint(main_bp)
|
|
181
|
+
app.register_blueprint(api_bp, url_prefix="/api")
|
|
182
|
+
|
|
183
|
+
# Create database tables and seed default admin user
|
|
184
|
+
with app.app_context():
|
|
185
|
+
db.create_all()
|
|
186
|
+
if not User.get_by_email("admin@example.com"):
|
|
187
|
+
User.create_user(
|
|
188
|
+
email="admin@example.com",
|
|
189
|
+
password="admin",
|
|
190
|
+
is_staff=True,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return app
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## app/api/__init__.py
|
|
200
|
+
|
|
201
|
+
```py
|
|
202
|
+
"""API blueprint registration."""
|
|
203
|
+
|
|
204
|
+
from flask import Blueprint
|
|
205
|
+
|
|
206
|
+
api_bp = Blueprint("api", __name__)
|
|
207
|
+
|
|
208
|
+
from app.api import routes # noqa: E402, F401
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## app/api/routes.py
|
|
215
|
+
|
|
216
|
+
```py
|
|
217
|
+
"""API endpoints demonstrating Amplitude integration patterns."""
|
|
218
|
+
|
|
219
|
+
from amplitude import BaseEvent
|
|
220
|
+
from flask import jsonify, request, session
|
|
221
|
+
from flask_login import current_user, login_required
|
|
222
|
+
|
|
223
|
+
from app.api import api_bp
|
|
224
|
+
from app.main.routes import get_amplitude_client
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@api_bp.route("/burrito/consider", methods=["POST"])
|
|
228
|
+
@login_required
|
|
229
|
+
def consider_burrito():
|
|
230
|
+
"""Track burrito consideration event."""
|
|
231
|
+
# Increment session counter
|
|
232
|
+
burrito_count = session.get("burrito_count", 0) + 1
|
|
233
|
+
session["burrito_count"] = burrito_count
|
|
234
|
+
|
|
235
|
+
# Amplitude: Capture custom event
|
|
236
|
+
client = get_amplitude_client()
|
|
237
|
+
if client:
|
|
238
|
+
client.track(BaseEvent(
|
|
239
|
+
event_type="Burrito Considered",
|
|
240
|
+
user_id=current_user.email,
|
|
241
|
+
event_properties={"total_considerations": burrito_count},
|
|
242
|
+
))
|
|
243
|
+
|
|
244
|
+
return jsonify({"success": True, "count": burrito_count})
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@api_bp.route("/test-error", methods=["POST"])
|
|
248
|
+
@login_required
|
|
249
|
+
def test_error():
|
|
250
|
+
"""Test endpoint demonstrating manual event capture in Amplitude.
|
|
251
|
+
|
|
252
|
+
Shows how to track error events in Amplitude.
|
|
253
|
+
Use this pattern for critical operations where you want error tracking.
|
|
254
|
+
|
|
255
|
+
Query params:
|
|
256
|
+
- capture: "true" to capture the error event in Amplitude, "false" to just raise it
|
|
257
|
+
"""
|
|
258
|
+
should_capture = request.args.get("capture", "true").lower() == "true"
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
# Simulate a critical operation failure
|
|
262
|
+
raise Exception("Test exception from critical operation")
|
|
263
|
+
except Exception as e:
|
|
264
|
+
if should_capture:
|
|
265
|
+
# Manually capture this specific error event in Amplitude
|
|
266
|
+
client = get_amplitude_client()
|
|
267
|
+
if client:
|
|
268
|
+
client.track(BaseEvent(
|
|
269
|
+
event_type="Error Occurred",
|
|
270
|
+
user_id=current_user.email,
|
|
271
|
+
event_properties={
|
|
272
|
+
"error_message": str(e),
|
|
273
|
+
"error_type": type(e).__name__,
|
|
274
|
+
},
|
|
275
|
+
))
|
|
276
|
+
|
|
277
|
+
return jsonify({
|
|
278
|
+
"error": "Operation failed",
|
|
279
|
+
"message": f"Error captured in Amplitude: {str(e)}"
|
|
280
|
+
}), 500
|
|
281
|
+
else:
|
|
282
|
+
# Just return error without Amplitude capture
|
|
283
|
+
return jsonify({"error": str(e)}), 500
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## app/config.py
|
|
290
|
+
|
|
291
|
+
```py
|
|
292
|
+
"""Flask application configuration."""
|
|
293
|
+
|
|
294
|
+
import os
|
|
295
|
+
from dotenv import load_dotenv
|
|
296
|
+
|
|
297
|
+
load_dotenv()
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class Config:
|
|
301
|
+
"""Base configuration."""
|
|
302
|
+
|
|
303
|
+
SECRET_KEY = os.environ.get("FLASK_SECRET_KEY", "dev-secret-key-change-in-production")
|
|
304
|
+
|
|
305
|
+
# Database configuration (SQLite like Django example)
|
|
306
|
+
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", "sqlite:///db.sqlite3")
|
|
307
|
+
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
308
|
+
|
|
309
|
+
# Amplitude configuration
|
|
310
|
+
AMPLITUDE_API_KEY = os.environ.get("AMPLITUDE_API_KEY", "")
|
|
311
|
+
AMPLITUDE_DISABLED = os.environ.get("AMPLITUDE_DISABLED", "False").lower() == "true"
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class DevelopmentConfig(Config):
|
|
315
|
+
"""Development configuration."""
|
|
316
|
+
|
|
317
|
+
DEBUG = True
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class ProductionConfig(Config):
|
|
321
|
+
"""Production configuration."""
|
|
322
|
+
|
|
323
|
+
DEBUG = False
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
config = {
|
|
327
|
+
"development": DevelopmentConfig,
|
|
328
|
+
"production": ProductionConfig,
|
|
329
|
+
"default": DevelopmentConfig,
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## app/extensions.py
|
|
337
|
+
|
|
338
|
+
```py
|
|
339
|
+
"""Flask extensions initialized without binding to app."""
|
|
340
|
+
|
|
341
|
+
from flask_login import LoginManager
|
|
342
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
343
|
+
|
|
344
|
+
db = SQLAlchemy()
|
|
345
|
+
|
|
346
|
+
login_manager = LoginManager()
|
|
347
|
+
login_manager.login_view = "main.home"
|
|
348
|
+
login_manager.login_message = "Please log in to access this page."
|
|
349
|
+
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## app/main/__init__.py
|
|
355
|
+
|
|
356
|
+
```py
|
|
357
|
+
"""Main blueprint registration."""
|
|
358
|
+
|
|
359
|
+
from flask import Blueprint
|
|
360
|
+
|
|
361
|
+
main_bp = Blueprint("main", __name__, template_folder="../templates")
|
|
362
|
+
|
|
363
|
+
from app.main import routes # noqa: E402, F401
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## app/main/routes.py
|
|
370
|
+
|
|
371
|
+
```py
|
|
372
|
+
"""Core view functions demonstrating Amplitude integration patterns."""
|
|
373
|
+
|
|
374
|
+
from amplitude import Amplitude, BaseEvent, Identify
|
|
375
|
+
from flask import current_app, flash, redirect, render_template, request, session, url_for
|
|
376
|
+
from flask_login import current_user, login_required, login_user, logout_user
|
|
377
|
+
|
|
378
|
+
from app.main import main_bp
|
|
379
|
+
from app.models import User
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def get_amplitude_client():
|
|
383
|
+
"""Get the Amplitude client instance."""
|
|
384
|
+
api_key = current_app.config.get('AMPLITUDE_API_KEY', '')
|
|
385
|
+
if not api_key or current_app.config.get('AMPLITUDE_DISABLED', False):
|
|
386
|
+
return None
|
|
387
|
+
return Amplitude(api_key)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@main_bp.route("/", methods=["GET", "POST"])
|
|
391
|
+
def home():
|
|
392
|
+
"""Home/login page."""
|
|
393
|
+
if current_user.is_authenticated:
|
|
394
|
+
return redirect(url_for("main.dashboard"))
|
|
395
|
+
|
|
396
|
+
if request.method == "POST":
|
|
397
|
+
email = request.form.get("email")
|
|
398
|
+
password = request.form.get("password")
|
|
399
|
+
|
|
400
|
+
user = User.authenticate(email, password)
|
|
401
|
+
if user:
|
|
402
|
+
login_user(user)
|
|
403
|
+
|
|
404
|
+
# Amplitude: Identify user and capture login event
|
|
405
|
+
client = get_amplitude_client()
|
|
406
|
+
if client:
|
|
407
|
+
identify_obj = Identify()
|
|
408
|
+
identify_obj.set("email", user.email)
|
|
409
|
+
identify_obj.set("is_staff", user.is_staff)
|
|
410
|
+
identify_obj.set("date_joined", user.date_joined.isoformat())
|
|
411
|
+
client.identify(identify_obj, {"user_id": user.email})
|
|
412
|
+
|
|
413
|
+
client.track(BaseEvent(
|
|
414
|
+
event_type="User Logged In",
|
|
415
|
+
user_id=user.email,
|
|
416
|
+
event_properties={"login_method": "password"},
|
|
417
|
+
))
|
|
418
|
+
|
|
419
|
+
return redirect(url_for("main.dashboard"))
|
|
420
|
+
else:
|
|
421
|
+
flash("Invalid email or password", "error")
|
|
422
|
+
|
|
423
|
+
return render_template("home.html")
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
@main_bp.route("/signup", methods=["GET", "POST"])
|
|
427
|
+
def signup():
|
|
428
|
+
"""User registration page."""
|
|
429
|
+
if current_user.is_authenticated:
|
|
430
|
+
return redirect(url_for("main.dashboard"))
|
|
431
|
+
|
|
432
|
+
if request.method == "POST":
|
|
433
|
+
email = request.form.get("email")
|
|
434
|
+
password = request.form.get("password")
|
|
435
|
+
password_confirm = request.form.get("password_confirm")
|
|
436
|
+
|
|
437
|
+
# Validation
|
|
438
|
+
if not email or not password:
|
|
439
|
+
flash("Email and password are required", "error")
|
|
440
|
+
elif password != password_confirm:
|
|
441
|
+
flash("Passwords do not match", "error")
|
|
442
|
+
elif User.get_by_email(email):
|
|
443
|
+
flash("Email already registered", "error")
|
|
444
|
+
else:
|
|
445
|
+
# Create new user
|
|
446
|
+
user = User.create_user(
|
|
447
|
+
email=email,
|
|
448
|
+
password=password,
|
|
449
|
+
is_staff=False,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# Amplitude: Identify new user and capture signup event
|
|
453
|
+
client = get_amplitude_client()
|
|
454
|
+
if client:
|
|
455
|
+
identify_obj = Identify()
|
|
456
|
+
identify_obj.set("email", user.email)
|
|
457
|
+
identify_obj.set("is_staff", user.is_staff)
|
|
458
|
+
identify_obj.set("date_joined", user.date_joined.isoformat())
|
|
459
|
+
client.identify(identify_obj, {"user_id": user.email})
|
|
460
|
+
|
|
461
|
+
client.track(BaseEvent(
|
|
462
|
+
event_type="User Signed Up",
|
|
463
|
+
user_id=user.email,
|
|
464
|
+
event_properties={"signup_method": "form"},
|
|
465
|
+
))
|
|
466
|
+
|
|
467
|
+
# Log the user in
|
|
468
|
+
login_user(user)
|
|
469
|
+
flash("Account created successfully!", "success")
|
|
470
|
+
return redirect(url_for("main.dashboard"))
|
|
471
|
+
|
|
472
|
+
return render_template("signup.html")
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
@main_bp.route("/logout")
|
|
476
|
+
@login_required
|
|
477
|
+
def logout():
|
|
478
|
+
"""Logout and capture event."""
|
|
479
|
+
# Amplitude: Capture logout event before session ends
|
|
480
|
+
client = get_amplitude_client()
|
|
481
|
+
if client:
|
|
482
|
+
client.track(BaseEvent(
|
|
483
|
+
event_type="User Logged Out",
|
|
484
|
+
user_id=current_user.email,
|
|
485
|
+
))
|
|
486
|
+
|
|
487
|
+
logout_user()
|
|
488
|
+
return redirect(url_for("main.home"))
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
@main_bp.route("/dashboard")
|
|
492
|
+
@login_required
|
|
493
|
+
def dashboard():
|
|
494
|
+
"""Dashboard page."""
|
|
495
|
+
# Amplitude: Capture dashboard view
|
|
496
|
+
client = get_amplitude_client()
|
|
497
|
+
if client:
|
|
498
|
+
client.track(BaseEvent(
|
|
499
|
+
event_type="Dashboard Viewed",
|
|
500
|
+
user_id=current_user.email,
|
|
501
|
+
event_properties={"is_staff": current_user.is_staff},
|
|
502
|
+
))
|
|
503
|
+
|
|
504
|
+
# TODO: Use Amplitude Experiment for feature flags
|
|
505
|
+
|
|
506
|
+
return render_template("dashboard.html")
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
@main_bp.route("/burrito")
|
|
510
|
+
@login_required
|
|
511
|
+
def burrito():
|
|
512
|
+
"""Burrito consideration tracker page."""
|
|
513
|
+
burrito_count = session.get("burrito_count", 0)
|
|
514
|
+
return render_template("burrito.html", burrito_count=burrito_count)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
@main_bp.route("/profile")
|
|
518
|
+
@login_required
|
|
519
|
+
def profile():
|
|
520
|
+
"""User profile page."""
|
|
521
|
+
# Amplitude: Capture profile view
|
|
522
|
+
client = get_amplitude_client()
|
|
523
|
+
if client:
|
|
524
|
+
client.track(BaseEvent(
|
|
525
|
+
event_type="Profile Viewed",
|
|
526
|
+
user_id=current_user.email,
|
|
527
|
+
))
|
|
528
|
+
|
|
529
|
+
return render_template("profile.html")
|
|
530
|
+
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## app/models.py
|
|
536
|
+
|
|
537
|
+
```py
|
|
538
|
+
"""User model with SQLite persistence (similar to Django's auth.User)."""
|
|
539
|
+
|
|
540
|
+
from datetime import datetime, timezone
|
|
541
|
+
|
|
542
|
+
from flask_login import UserMixin
|
|
543
|
+
from werkzeug.security import check_password_hash, generate_password_hash
|
|
544
|
+
|
|
545
|
+
from app.extensions import db
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
class User(UserMixin, db.Model):
|
|
549
|
+
"""User model with SQLite persistence."""
|
|
550
|
+
|
|
551
|
+
__tablename__ = "users"
|
|
552
|
+
|
|
553
|
+
id = db.Column(db.Integer, primary_key=True)
|
|
554
|
+
email = db.Column(db.String(254), unique=True, nullable=False)
|
|
555
|
+
password_hash = db.Column(db.String(256), nullable=False)
|
|
556
|
+
is_staff = db.Column(db.Boolean, default=False)
|
|
557
|
+
is_active = db.Column(db.Boolean, default=True)
|
|
558
|
+
date_joined = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
|
559
|
+
|
|
560
|
+
def set_password(self, password):
|
|
561
|
+
"""Hash and set the user's password."""
|
|
562
|
+
self.password_hash = generate_password_hash(password)
|
|
563
|
+
|
|
564
|
+
def check_password(self, password):
|
|
565
|
+
"""Verify the password against the hash."""
|
|
566
|
+
return check_password_hash(self.password_hash, password)
|
|
567
|
+
|
|
568
|
+
@classmethod
|
|
569
|
+
def create_user(cls, email, password, is_staff=False):
|
|
570
|
+
"""Create and save a new user."""
|
|
571
|
+
user = cls(email=email, is_staff=is_staff)
|
|
572
|
+
# nosemgrep: python.django.security.audit.unvalidated-password.unvalidated-password
|
|
573
|
+
user.set_password(password)
|
|
574
|
+
db.session.add(user)
|
|
575
|
+
db.session.commit()
|
|
576
|
+
return user
|
|
577
|
+
|
|
578
|
+
@classmethod
|
|
579
|
+
def get_by_id(cls, user_id):
|
|
580
|
+
"""Get user by ID."""
|
|
581
|
+
return cls.query.get(int(user_id))
|
|
582
|
+
|
|
583
|
+
@classmethod
|
|
584
|
+
def get_by_email(cls, email):
|
|
585
|
+
"""Get user by email."""
|
|
586
|
+
return cls.query.filter_by(email=email).first()
|
|
587
|
+
|
|
588
|
+
@classmethod
|
|
589
|
+
def authenticate(cls, email, password):
|
|
590
|
+
"""Authenticate user with email and password."""
|
|
591
|
+
user = cls.get_by_email(email)
|
|
592
|
+
if user and user.check_password(password):
|
|
593
|
+
return user
|
|
594
|
+
return None
|
|
595
|
+
|
|
596
|
+
def __repr__(self):
|
|
597
|
+
return f"<User {self.email}>"
|
|
598
|
+
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
---
|
|
602
|
+
|
|
603
|
+
## app/templates/base.html
|
|
604
|
+
|
|
605
|
+
```html
|
|
606
|
+
<!DOCTYPE html>
|
|
607
|
+
<html lang="en">
|
|
608
|
+
<head>
|
|
609
|
+
<meta charset="UTF-8">
|
|
610
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
611
|
+
<title>{% block title %}Amplitude Flask Example{% endblock %}</title>
|
|
612
|
+
<style>
|
|
613
|
+
* {
|
|
614
|
+
box-sizing: border-box;
|
|
615
|
+
margin: 0;
|
|
616
|
+
padding: 0;
|
|
617
|
+
}
|
|
618
|
+
body {
|
|
619
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
620
|
+
line-height: 1.6;
|
|
621
|
+
background-color: #f5f5f5;
|
|
622
|
+
color: #333;
|
|
623
|
+
}
|
|
624
|
+
.container {
|
|
625
|
+
max-width: 800px;
|
|
626
|
+
margin: 0 auto;
|
|
627
|
+
padding: 20px;
|
|
628
|
+
}
|
|
629
|
+
nav {
|
|
630
|
+
background: #1d4ed8;
|
|
631
|
+
padding: 15px 20px;
|
|
632
|
+
margin-bottom: 30px;
|
|
633
|
+
}
|
|
634
|
+
nav a {
|
|
635
|
+
color: white;
|
|
636
|
+
text-decoration: none;
|
|
637
|
+
margin-right: 20px;
|
|
638
|
+
}
|
|
639
|
+
nav a:hover {
|
|
640
|
+
text-decoration: underline;
|
|
641
|
+
}
|
|
642
|
+
.card {
|
|
643
|
+
background: white;
|
|
644
|
+
border-radius: 8px;
|
|
645
|
+
padding: 20px;
|
|
646
|
+
margin-bottom: 20px;
|
|
647
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
648
|
+
}
|
|
649
|
+
h1, h2, h3 {
|
|
650
|
+
margin-bottom: 15px;
|
|
651
|
+
color: #1d4ed8;
|
|
652
|
+
}
|
|
653
|
+
button, .btn {
|
|
654
|
+
background: #1d4ed8;
|
|
655
|
+
color: white;
|
|
656
|
+
border: none;
|
|
657
|
+
padding: 10px 20px;
|
|
658
|
+
border-radius: 5px;
|
|
659
|
+
cursor: pointer;
|
|
660
|
+
font-size: 14px;
|
|
661
|
+
display: inline-block;
|
|
662
|
+
text-decoration: none;
|
|
663
|
+
}
|
|
664
|
+
button:hover, .btn:hover {
|
|
665
|
+
background: #1e40af;
|
|
666
|
+
}
|
|
667
|
+
button.danger {
|
|
668
|
+
background: #dc2626;
|
|
669
|
+
}
|
|
670
|
+
button.danger:hover {
|
|
671
|
+
background: #b91c1c;
|
|
672
|
+
}
|
|
673
|
+
input {
|
|
674
|
+
width: 100%;
|
|
675
|
+
padding: 10px;
|
|
676
|
+
margin-bottom: 15px;
|
|
677
|
+
border: 1px solid #ddd;
|
|
678
|
+
border-radius: 5px;
|
|
679
|
+
font-size: 14px;
|
|
680
|
+
}
|
|
681
|
+
.messages {
|
|
682
|
+
margin-bottom: 20px;
|
|
683
|
+
}
|
|
684
|
+
.message {
|
|
685
|
+
padding: 10px 15px;
|
|
686
|
+
border-radius: 5px;
|
|
687
|
+
margin-bottom: 10px;
|
|
688
|
+
}
|
|
689
|
+
.message.error {
|
|
690
|
+
background: #fee2e2;
|
|
691
|
+
color: #dc2626;
|
|
692
|
+
}
|
|
693
|
+
.message.success {
|
|
694
|
+
background: #d1fae5;
|
|
695
|
+
color: #059669;
|
|
696
|
+
}
|
|
697
|
+
.feature-flag {
|
|
698
|
+
background: #fef3c7;
|
|
699
|
+
border: 2px dashed #f59e0b;
|
|
700
|
+
padding: 15px;
|
|
701
|
+
border-radius: 8px;
|
|
702
|
+
margin: 20px 0;
|
|
703
|
+
}
|
|
704
|
+
code {
|
|
705
|
+
background: #f3f4f6;
|
|
706
|
+
padding: 2px 6px;
|
|
707
|
+
border-radius: 3px;
|
|
708
|
+
font-family: monospace;
|
|
709
|
+
}
|
|
710
|
+
pre {
|
|
711
|
+
background: #1e293b;
|
|
712
|
+
color: #e2e8f0;
|
|
713
|
+
padding: 16px;
|
|
714
|
+
border-radius: 8px;
|
|
715
|
+
overflow-x: auto;
|
|
716
|
+
font-size: 13px;
|
|
717
|
+
}
|
|
718
|
+
.count {
|
|
719
|
+
font-size: 48px;
|
|
720
|
+
font-weight: bold;
|
|
721
|
+
color: #1d4ed8;
|
|
722
|
+
text-align: center;
|
|
723
|
+
padding: 20px;
|
|
724
|
+
}
|
|
725
|
+
table {
|
|
726
|
+
width: 100%;
|
|
727
|
+
border-collapse: collapse;
|
|
728
|
+
margin: 16px 0;
|
|
729
|
+
}
|
|
730
|
+
th, td {
|
|
731
|
+
padding: 12px;
|
|
732
|
+
text-align: left;
|
|
733
|
+
border-bottom: 1px solid #eee;
|
|
734
|
+
}
|
|
735
|
+
th {
|
|
736
|
+
background: #f8fafc;
|
|
737
|
+
font-weight: 600;
|
|
738
|
+
}
|
|
739
|
+
</style>
|
|
740
|
+
</head>
|
|
741
|
+
<body>
|
|
742
|
+
{% if current_user.is_authenticated %}
|
|
743
|
+
<nav>
|
|
744
|
+
<a href="{{ url_for('main.dashboard') }}">Dashboard</a>
|
|
745
|
+
<a href="{{ url_for('main.burrito') }}">Burrito</a>
|
|
746
|
+
<a href="{{ url_for('main.profile') }}">Profile</a>
|
|
747
|
+
<a href="{{ url_for('main.logout') }}" style="float: right;">Logout ({{ current_user.email }})</a>
|
|
748
|
+
</nav>
|
|
749
|
+
{% endif %}
|
|
750
|
+
|
|
751
|
+
<div class="container">
|
|
752
|
+
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
753
|
+
{% if messages %}
|
|
754
|
+
<div class="messages">
|
|
755
|
+
{% for category, message in messages %}
|
|
756
|
+
<div class="message {{ category }}">{{ message }}</div>
|
|
757
|
+
{% endfor %}
|
|
758
|
+
</div>
|
|
759
|
+
{% endif %}
|
|
760
|
+
{% endwith %}
|
|
761
|
+
|
|
762
|
+
{% block content %}{% endblock %}
|
|
763
|
+
</div>
|
|
764
|
+
|
|
765
|
+
{% block scripts %}{% endblock %}
|
|
766
|
+
</body>
|
|
767
|
+
</html>
|
|
768
|
+
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
---
|
|
772
|
+
|
|
773
|
+
## app/templates/burrito.html
|
|
774
|
+
|
|
775
|
+
```html
|
|
776
|
+
{% extends "base.html" %}
|
|
777
|
+
|
|
778
|
+
{% block title %}Burrito - Amplitude Flask Example{% endblock %}
|
|
779
|
+
|
|
780
|
+
{% block content %}
|
|
781
|
+
<div class="card">
|
|
782
|
+
<h1>Burrito Consideration Tracker</h1>
|
|
783
|
+
<p>This page demonstrates custom event tracking with Amplitude.</p>
|
|
784
|
+
|
|
785
|
+
<div class="count" id="burrito-count">{{ burrito_count }}</div>
|
|
786
|
+
<p style="text-align: center; color: #666;">Times you've considered a burrito</p>
|
|
787
|
+
|
|
788
|
+
<div style="text-align: center; margin-top: 20px;">
|
|
789
|
+
<button onclick="considerBurrito()">Consider a Burrito</button>
|
|
790
|
+
</div>
|
|
791
|
+
</div>
|
|
792
|
+
|
|
793
|
+
<div class="card">
|
|
794
|
+
<h3>Code Example</h3>
|
|
795
|
+
<pre>
|
|
796
|
+
# API endpoint captures the event
|
|
797
|
+
client = get_amplitude_client()
|
|
798
|
+
if client:
|
|
799
|
+
client.track(BaseEvent(
|
|
800
|
+
event_type='Burrito Considered',
|
|
801
|
+
user_id=current_user.email,
|
|
802
|
+
event_properties={'total_considerations': burrito_count},
|
|
803
|
+
))</pre>
|
|
804
|
+
</div>
|
|
805
|
+
{% endblock %}
|
|
806
|
+
|
|
807
|
+
{% block scripts %}
|
|
808
|
+
<script>
|
|
809
|
+
async function considerBurrito() {
|
|
810
|
+
try {
|
|
811
|
+
const response = await fetch('/api/burrito/consider', {
|
|
812
|
+
method: 'POST',
|
|
813
|
+
headers: {
|
|
814
|
+
'Content-Type': 'application/json'
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
const data = await response.json();
|
|
818
|
+
if (data.success) {
|
|
819
|
+
document.getElementById('burrito-count').textContent = data.count;
|
|
820
|
+
}
|
|
821
|
+
} catch (error) {
|
|
822
|
+
console.error('Error:', error);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
</script>
|
|
826
|
+
{% endblock %}
|
|
827
|
+
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
---
|
|
831
|
+
|
|
832
|
+
## app/templates/dashboard.html
|
|
833
|
+
|
|
834
|
+
```html
|
|
835
|
+
{% extends "base.html" %}
|
|
836
|
+
|
|
837
|
+
{% block title %}Dashboard - Amplitude Flask Example{% endblock %}
|
|
838
|
+
|
|
839
|
+
{% block content %}
|
|
840
|
+
<div class="card">
|
|
841
|
+
<h1>Dashboard</h1>
|
|
842
|
+
<p>Welcome back, {{ current_user.email }}!</p>
|
|
843
|
+
</div>
|
|
844
|
+
|
|
845
|
+
<div class="card">
|
|
846
|
+
<h2>Amplitude Event Tracking</h2>
|
|
847
|
+
<p>This page is tracked with Amplitude on every visit.</p>
|
|
848
|
+
|
|
849
|
+
<h3 style="margin-top: 20px;">Code Example</h3>
|
|
850
|
+
<pre>
|
|
851
|
+
# Track dashboard view
|
|
852
|
+
client = get_amplitude_client()
|
|
853
|
+
if client:
|
|
854
|
+
client.track(BaseEvent(
|
|
855
|
+
event_type='Dashboard Viewed',
|
|
856
|
+
user_id=current_user.email,
|
|
857
|
+
event_properties={'is_staff': current_user.is_staff},
|
|
858
|
+
))
|
|
859
|
+
|
|
860
|
+
# TODO: Use Amplitude Experiment for feature flags</pre>
|
|
861
|
+
</div>
|
|
862
|
+
{% endblock %}
|
|
863
|
+
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
---
|
|
867
|
+
|
|
868
|
+
## app/templates/errors/404.html
|
|
869
|
+
|
|
870
|
+
```html
|
|
871
|
+
{% extends "base.html" %}
|
|
872
|
+
|
|
873
|
+
{% block title %}404 - Page Not Found{% endblock %}
|
|
874
|
+
|
|
875
|
+
{% block content %}
|
|
876
|
+
<div class="card" style="text-align: center; padding: 60px 20px;">
|
|
877
|
+
<h1 style="font-size: 72px; color: #dc2626; margin-bottom: 10px;">404</h1>
|
|
878
|
+
<h2 style="color: #333; margin-bottom: 20px;">Page Not Found</h2>
|
|
879
|
+
<p style="font-size: 18px; color: #666; margin-bottom: 30px;">
|
|
880
|
+
The page you're looking for doesn't exist or has been moved.
|
|
881
|
+
</p>
|
|
882
|
+
|
|
883
|
+
{% if error_id %}
|
|
884
|
+
<div style="background: #fef3c7; border: 1px solid #fbbf24; border-radius: 8px; padding: 15px; margin: 30px 0;">
|
|
885
|
+
<p style="color: #92400e; margin-bottom: 5px; font-weight: 600;">Error Reference ID:</p>
|
|
886
|
+
<code style="background: #fff; padding: 5px 10px; border-radius: 4px; font-family: monospace; color: #1e40af;">{{ error_id }}</code>
|
|
887
|
+
<p style="color: #92400e; margin-top: 10px; font-size: 14px;">
|
|
888
|
+
Share this ID with support if you need assistance.
|
|
889
|
+
</p>
|
|
890
|
+
</div>
|
|
891
|
+
{% endif %}
|
|
892
|
+
|
|
893
|
+
<div style="margin-top: 40px;">
|
|
894
|
+
<a href="{{ url_for('main.home') }}" class="btn" style="margin-right: 10px;">Go to Home</a>
|
|
895
|
+
{% if current_user.is_authenticated %}
|
|
896
|
+
<a href="{{ url_for('main.dashboard') }}" class="btn">Go to Dashboard</a>
|
|
897
|
+
{% endif %}
|
|
898
|
+
</div>
|
|
899
|
+
</div>
|
|
900
|
+
{% endblock %}
|
|
901
|
+
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
---
|
|
905
|
+
|
|
906
|
+
## app/templates/errors/500.html
|
|
907
|
+
|
|
908
|
+
```html
|
|
909
|
+
{% extends "base.html" %}
|
|
910
|
+
|
|
911
|
+
{% block title %}500 - Internal Server Error{% endblock %}
|
|
912
|
+
|
|
913
|
+
{% block content %}
|
|
914
|
+
<div class="card" style="text-align: center; padding: 60px 20px;">
|
|
915
|
+
<h1 style="font-size: 72px; color: #dc2626; margin-bottom: 10px;">500</h1>
|
|
916
|
+
<h2 style="color: #333; margin-bottom: 20px;">Internal Server Error</h2>
|
|
917
|
+
<p style="font-size: 18px; color: #666; margin-bottom: 30px;">
|
|
918
|
+
Something went wrong on our end. We've been notified and are looking into it.
|
|
919
|
+
</p>
|
|
920
|
+
|
|
921
|
+
{% if error_id %}
|
|
922
|
+
<div style="background: #fef3c7; border: 1px solid #fbbf24; border-radius: 8px; padding: 15px; margin: 30px 0;">
|
|
923
|
+
<p style="color: #92400e; margin-bottom: 5px; font-weight: 600;">Error Reference ID:</p>
|
|
924
|
+
<code style="background: #fff; padding: 5px 10px; border-radius: 4px; font-family: monospace; color: #1e40af;">{{ error_id }}</code>
|
|
925
|
+
<p style="color: #92400e; margin-top: 10px; font-size: 14px;">
|
|
926
|
+
Share this ID with support if you need assistance.
|
|
927
|
+
</p>
|
|
928
|
+
</div>
|
|
929
|
+
{% endif %}
|
|
930
|
+
|
|
931
|
+
{% if error and config.DEBUG %}
|
|
932
|
+
<div style="background: #fee2e2; border: 1px solid #dc2626; border-radius: 8px; padding: 15px; margin: 30px 0; text-align: left;">
|
|
933
|
+
<p style="color: #7f1d1d; margin-bottom: 5px; font-weight: 600;">Debug Information:</p>
|
|
934
|
+
<code style="background: #fff; padding: 10px; border-radius: 4px; font-family: monospace; color: #dc2626; display: block; overflow-x: auto;">{{ error }}</code>
|
|
935
|
+
</div>
|
|
936
|
+
{% endif %}
|
|
937
|
+
|
|
938
|
+
<div style="margin-top: 40px;">
|
|
939
|
+
<a href="{{ url_for('main.home') }}" class="btn" style="margin-right: 10px;">Go to Home</a>
|
|
940
|
+
{% if current_user.is_authenticated %}
|
|
941
|
+
<a href="{{ url_for('main.dashboard') }}" class="btn">Go to Dashboard</a>
|
|
942
|
+
{% endif %}
|
|
943
|
+
</div>
|
|
944
|
+
</div>
|
|
945
|
+
{% endblock %}
|
|
946
|
+
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
---
|
|
950
|
+
|
|
951
|
+
## app/templates/home.html
|
|
952
|
+
|
|
953
|
+
```html
|
|
954
|
+
{% extends "base.html" %}
|
|
955
|
+
|
|
956
|
+
{% block title %}Login - Amplitude Flask Example{% endblock %}
|
|
957
|
+
|
|
958
|
+
{% block content %}
|
|
959
|
+
<div class="card">
|
|
960
|
+
<h1>Welcome to Amplitude Flask Example</h1>
|
|
961
|
+
<p>This example demonstrates how to integrate Amplitude with a Flask application.</p>
|
|
962
|
+
|
|
963
|
+
<form method="POST">
|
|
964
|
+
<label for="email">Email</label>
|
|
965
|
+
<input type="email" id="email" name="email" required>
|
|
966
|
+
|
|
967
|
+
<label for="password">Password</label>
|
|
968
|
+
<input type="password" id="password" name="password" required>
|
|
969
|
+
|
|
970
|
+
<button type="submit">Login</button>
|
|
971
|
+
</form>
|
|
972
|
+
|
|
973
|
+
<p style="margin-top: 16px; font-size: 14px; color: #666;">
|
|
974
|
+
Don't have an account? <a href="{{ url_for('main.signup') }}">Sign up here</a>
|
|
975
|
+
</p>
|
|
976
|
+
<p style="font-size: 14px; color: #666;">
|
|
977
|
+
<strong>Tip:</strong> Default credentials are admin@example.com/admin
|
|
978
|
+
</p>
|
|
979
|
+
</div>
|
|
980
|
+
|
|
981
|
+
<div class="card">
|
|
982
|
+
<h2>Features Demonstrated</h2>
|
|
983
|
+
<ul style="margin-left: 20px; color: #666;">
|
|
984
|
+
<li>User registration and identification</li>
|
|
985
|
+
<li>Event tracking</li>
|
|
986
|
+
<li>User properties</li>
|
|
987
|
+
</ul>
|
|
988
|
+
</div>
|
|
989
|
+
{% endblock %}
|
|
990
|
+
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
---
|
|
994
|
+
|
|
995
|
+
## app/templates/profile.html
|
|
996
|
+
|
|
997
|
+
```html
|
|
998
|
+
{% extends "base.html" %}
|
|
999
|
+
|
|
1000
|
+
{% block title %}Profile - Amplitude Flask Example{% endblock %}
|
|
1001
|
+
|
|
1002
|
+
{% block content %}
|
|
1003
|
+
<div class="card">
|
|
1004
|
+
<h1>Your Profile</h1>
|
|
1005
|
+
<p>This page demonstrates event tracking with Amplitude.</p>
|
|
1006
|
+
|
|
1007
|
+
<table>
|
|
1008
|
+
<tr>
|
|
1009
|
+
<th>Email</th>
|
|
1010
|
+
<td>{{ current_user.email }}</td>
|
|
1011
|
+
</tr>
|
|
1012
|
+
<tr>
|
|
1013
|
+
<th>Date Joined</th>
|
|
1014
|
+
<td>{{ current_user.date_joined.strftime('%Y-%m-%d %H:%M') }}</td>
|
|
1015
|
+
</tr>
|
|
1016
|
+
<tr>
|
|
1017
|
+
<th>Staff Status</th>
|
|
1018
|
+
<td>{{ 'Yes' if current_user.is_staff else 'No' }}</td>
|
|
1019
|
+
</tr>
|
|
1020
|
+
</table>
|
|
1021
|
+
</div>
|
|
1022
|
+
|
|
1023
|
+
<div class="card">
|
|
1024
|
+
<h3>Code Example</h3>
|
|
1025
|
+
<pre>
|
|
1026
|
+
# Track profile view
|
|
1027
|
+
client = get_amplitude_client()
|
|
1028
|
+
if client:
|
|
1029
|
+
client.track(BaseEvent(
|
|
1030
|
+
event_type='Profile Viewed',
|
|
1031
|
+
user_id=current_user.email,
|
|
1032
|
+
))</pre>
|
|
1033
|
+
</div>
|
|
1034
|
+
{% endblock %}
|
|
1035
|
+
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
---
|
|
1039
|
+
|
|
1040
|
+
## app/templates/signup.html
|
|
1041
|
+
|
|
1042
|
+
```html
|
|
1043
|
+
{% extends "base.html" %}
|
|
1044
|
+
|
|
1045
|
+
{% block title %}Sign Up - Amplitude Flask Example{% endblock %}
|
|
1046
|
+
|
|
1047
|
+
{% block content %}
|
|
1048
|
+
<div class="card">
|
|
1049
|
+
<h1>Create an Account</h1>
|
|
1050
|
+
<p>Sign up to explore the Amplitude Flask integration example.</p>
|
|
1051
|
+
|
|
1052
|
+
<form method="POST">
|
|
1053
|
+
<label for="email">Email *</label>
|
|
1054
|
+
<input type="email" id="email" name="email" required>
|
|
1055
|
+
|
|
1056
|
+
<label for="password">Password *</label>
|
|
1057
|
+
<input type="password" id="password" name="password" required>
|
|
1058
|
+
|
|
1059
|
+
<label for="password_confirm">Confirm Password *</label>
|
|
1060
|
+
<input type="password" id="password_confirm" name="password_confirm" required>
|
|
1061
|
+
|
|
1062
|
+
<button type="submit">Sign Up</button>
|
|
1063
|
+
</form>
|
|
1064
|
+
|
|
1065
|
+
<p style="margin-top: 16px; font-size: 14px; color: #666;">
|
|
1066
|
+
Already have an account? <a href="{{ url_for('main.home') }}">Login here</a>
|
|
1067
|
+
</p>
|
|
1068
|
+
</div>
|
|
1069
|
+
|
|
1070
|
+
<div class="card">
|
|
1071
|
+
<h2>Amplitude Integration</h2>
|
|
1072
|
+
<p>When you sign up, the following Amplitude events are captured:</p>
|
|
1073
|
+
<ul style="margin-left: 20px; color: #666;">
|
|
1074
|
+
<li><code>client.identify()</code> - Sets user properties (email, is_staff, date_joined)</li>
|
|
1075
|
+
<li><code>User Signed Up</code> event - Tracks the signup action</li>
|
|
1076
|
+
</ul>
|
|
1077
|
+
|
|
1078
|
+
<h3 style="margin-top: 20px;">Code Example</h3>
|
|
1079
|
+
<pre>
|
|
1080
|
+
# After creating the user
|
|
1081
|
+
client = get_amplitude_client()
|
|
1082
|
+
if client:
|
|
1083
|
+
identify_obj = Identify()
|
|
1084
|
+
identify_obj.set('email', user.email)
|
|
1085
|
+
identify_obj.set('is_staff', user.is_staff)
|
|
1086
|
+
identify_obj.set('date_joined', user.date_joined.isoformat())
|
|
1087
|
+
client.identify(identify_obj, {'user_id': user.email})
|
|
1088
|
+
|
|
1089
|
+
client.track(BaseEvent(
|
|
1090
|
+
event_type='User Signed Up',
|
|
1091
|
+
user_id=user.email,
|
|
1092
|
+
event_properties={'signup_method': 'form'},
|
|
1093
|
+
))</pre>
|
|
1094
|
+
</div>
|
|
1095
|
+
{% endblock %}
|
|
1096
|
+
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
---
|
|
1100
|
+
|
|
1101
|
+
## requirements.txt
|
|
1102
|
+
|
|
1103
|
+
```txt
|
|
1104
|
+
Flask>=3.1.0
|
|
1105
|
+
Flask-Login>=0.6.3
|
|
1106
|
+
Flask-SQLAlchemy>=3.1.0
|
|
1107
|
+
python-dotenv>=1.0.0
|
|
1108
|
+
amplitude-analytics>=1.0.0
|
|
1109
|
+
Werkzeug>=3.0.0
|
|
1110
|
+
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
---
|
|
1114
|
+
|
|
1115
|
+
## run.py
|
|
1116
|
+
|
|
1117
|
+
```py
|
|
1118
|
+
"""Development server entry point."""
|
|
1119
|
+
|
|
1120
|
+
from app import create_app
|
|
1121
|
+
|
|
1122
|
+
app = create_app()
|
|
1123
|
+
|
|
1124
|
+
if __name__ == "__main__":
|
|
1125
|
+
app.run(port=5001)
|
|
1126
|
+
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
---
|
|
1130
|
+
|