From c055dc1535725f3e794ea46971454699000f4a4f Mon Sep 17 00:00:00 2001 From: Weves Date: Sat, 13 Apr 2024 10:35:19 -0700 Subject: [PATCH] Add custom analytics script --- .../danswer/server/enterprise_settings/api.py | 18 +++ .../server/enterprise_settings/models.py | 5 + .../server/enterprise_settings/store.py | 26 ++++ web/next.config.js | 9 +- web/src/app/admin/settings/SettingsForm.tsx | 2 +- web/src/app/chat/ChatBanner.tsx | 8 +- web/src/app/chat/ChatPopup.tsx | 2 +- .../CustomAnalyticsUpdateForm.tsx | 111 ++++++++++++++++++ .../performance/custom-analytics/page.tsx | 46 ++++++++ web/src/app/ee/admin/performance/lib.ts | 2 +- .../query-history/QueryHistoryTable.tsx | 2 +- .../performance/query-history/[id]/page.tsx | 2 +- .../{analytics => usage}/FeedbackChart.tsx | 0 .../QueryPerformanceChart.tsx | 0 .../performance/{analytics => usage}/page.tsx | 6 +- .../performance/{analytics => usage}/types.ts | 0 .../admin/whitelabeling/WhitelabelingForm.tsx | 2 +- web/src/app/layout.tsx | 13 +- web/src/components/Logo.tsx | 2 +- web/src/components/admin/Layout.tsx | 26 +++- .../SettingsProviderClientSideHelper.tsx | 20 ---- 21 files changed, 264 insertions(+), 38 deletions(-) create mode 100644 web/src/app/ee/admin/performance/custom-analytics/CustomAnalyticsUpdateForm.tsx create mode 100644 web/src/app/ee/admin/performance/custom-analytics/page.tsx rename web/src/app/ee/admin/performance/{analytics => usage}/FeedbackChart.tsx (100%) rename web/src/app/ee/admin/performance/{analytics => usage}/QueryPerformanceChart.tsx (100%) rename web/src/app/ee/admin/performance/{analytics => usage}/page.tsx (83%) rename web/src/app/ee/admin/performance/{analytics => usage}/types.ts (100%) delete mode 100644 web/src/components/settings/SettingsProviderClientSideHelper.tsx diff --git a/backend/ee/danswer/server/enterprise_settings/api.py b/backend/ee/danswer/server/enterprise_settings/api.py index ec26ff1e1cf9..d30a5ce7a36c 100644 --- a/backend/ee/danswer/server/enterprise_settings/api.py +++ b/backend/ee/danswer/server/enterprise_settings/api.py @@ -9,8 +9,11 @@ from danswer.auth.users import current_admin_user from danswer.db.engine import get_session from danswer.db.file_store import get_default_file_store from danswer.db.models import User +from ee.danswer.server.enterprise_settings.models import AnalyticsScriptUpload from ee.danswer.server.enterprise_settings.models import EnterpriseSettings +from ee.danswer.server.enterprise_settings.store import load_analytics_script from ee.danswer.server.enterprise_settings.store import load_settings +from ee.danswer.server.enterprise_settings.store import store_analytics_script from ee.danswer.server.enterprise_settings.store import store_settings @@ -65,3 +68,18 @@ def fetch_logo(db_session: Session = Depends(get_session)) -> Response: # NOTE: specifying "image/jpeg" here, but it still works for pngs # TODO: do this properly return Response(content=file_io.read(), media_type="image/jpeg") + + +@admin_router.put("/custom-analytics-script") +def upload_custom_analytics_script( + script_upload: AnalyticsScriptUpload, _: User | None = Depends(current_admin_user) +) -> None: + try: + store_analytics_script(script_upload) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@basic_router.get("/custom-analytics-script") +def fetch_custom_analytics_script() -> str | None: + return load_analytics_script() diff --git a/backend/ee/danswer/server/enterprise_settings/models.py b/backend/ee/danswer/server/enterprise_settings/models.py index 00b2c995c6a4..c2142c64162f 100644 --- a/backend/ee/danswer/server/enterprise_settings/models.py +++ b/backend/ee/danswer/server/enterprise_settings/models.py @@ -16,3 +16,8 @@ class EnterpriseSettings(BaseModel): def check_validity(self) -> None: return + + +class AnalyticsScriptUpload(BaseModel): + script: str + secret_key: str diff --git a/backend/ee/danswer/server/enterprise_settings/store.py b/backend/ee/danswer/server/enterprise_settings/store.py index fc9774b2c1cc..023d36ef0366 100644 --- a/backend/ee/danswer/server/enterprise_settings/store.py +++ b/backend/ee/danswer/server/enterprise_settings/store.py @@ -1,7 +1,9 @@ +import os from typing import cast from danswer.dynamic_configs.factory import get_dynamic_config_store from danswer.dynamic_configs.interface import ConfigNotFoundError +from ee.danswer.server.enterprise_settings.models import AnalyticsScriptUpload from ee.danswer.server.enterprise_settings.models import EnterpriseSettings @@ -23,3 +25,27 @@ def load_settings() -> EnterpriseSettings: def store_settings(settings: EnterpriseSettings) -> None: get_dynamic_config_store().store(_ENTERPRISE_SETTINGS_KEY, settings.dict()) + + +_CUSTOM_ANALYTICS_SCRIPT_KEY = "__custom_analytics_script__" +_CUSTOM_ANALYTICS_SECRET_KEY = os.environ.get("CUSTOM_ANALYTICS_SECRET_KEY") + + +def load_analytics_script() -> str | None: + dynamic_config_store = get_dynamic_config_store() + try: + return cast(str, dynamic_config_store.load(_CUSTOM_ANALYTICS_SCRIPT_KEY)) + except ConfigNotFoundError: + return None + + +def store_analytics_script(analytics_script_upload: AnalyticsScriptUpload) -> None: + if ( + not _CUSTOM_ANALYTICS_SECRET_KEY + or analytics_script_upload.secret_key != _CUSTOM_ANALYTICS_SECRET_KEY + ): + raise ValueError("Invalid secret key") + + get_dynamic_config_store().store( + _CUSTOM_ANALYTICS_SCRIPT_KEY, analytics_script_upload.script + ) diff --git a/web/next.config.js b/web/next.config.js index bfd05d1c153e..f66bc3bd2cc3 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -27,8 +27,8 @@ const nextConfig = { }, // analytics / audit log pages { - source: "/admin/performance/analytics", - destination: "/ee/admin/performance/analytics", + source: "/admin/performance/usage", + destination: "/ee/admin/performance/usage", }, { source: "/admin/performance/query-history", @@ -43,6 +43,11 @@ const nextConfig = { source: "/admin/whitelabeling", destination: "/ee/admin/whitelabeling", }, + // custom analytics/tracking + { + source: "/admin/performance/custom-analytics", + destination: "/ee/admin/performance/custom-analytics", + }, ] : []; diff --git a/web/src/app/admin/settings/SettingsForm.tsx b/web/src/app/admin/settings/SettingsForm.tsx index 7873aada267d..782f0607992c 100644 --- a/web/src/app/admin/settings/SettingsForm.tsx +++ b/web/src/app/admin/settings/SettingsForm.tsx @@ -6,7 +6,7 @@ import { Settings } from "./interfaces"; import { useRouter } from "next/navigation"; import { DefaultDropdown, Option } from "@/components/Dropdown"; import { useContext } from "react"; -import { SettingsContext } from "@/components/settings/SettingsProviderClientSideHelper"; +import { SettingsContext } from "@/components/settings/SettingsProvider"; function Checkbox({ label, diff --git a/web/src/app/chat/ChatBanner.tsx b/web/src/app/chat/ChatBanner.tsx index b4d9330198ef..4158549574f7 100644 --- a/web/src/app/chat/ChatBanner.tsx +++ b/web/src/app/chat/ChatBanner.tsx @@ -1,7 +1,7 @@ "use client"; import ReactMarkdown from "react-markdown"; -import { SettingsContext } from "@/components/settings/SettingsProviderClientSideHelper"; +import { SettingsContext } from "@/components/settings/SettingsProvider"; import { useContext } from "react"; import remarkGfm from "remark-gfm"; @@ -15,11 +15,13 @@ export function ChatBanner() {
diff --git a/web/src/app/chat/ChatPopup.tsx b/web/src/app/chat/ChatPopup.tsx index 12e57864d826..ee4f47396d78 100644 --- a/web/src/app/chat/ChatPopup.tsx +++ b/web/src/app/chat/ChatPopup.tsx @@ -1,7 +1,7 @@ "use client"; import { Modal } from "@/components/Modal"; -import { SettingsContext } from "@/components/settings/SettingsProviderClientSideHelper"; +import { SettingsContext } from "@/components/settings/SettingsProvider"; import { Button } from "@tremor/react"; import { useContext, useEffect, useState } from "react"; import ReactMarkdown from "react-markdown"; diff --git a/web/src/app/ee/admin/performance/custom-analytics/CustomAnalyticsUpdateForm.tsx b/web/src/app/ee/admin/performance/custom-analytics/CustomAnalyticsUpdateForm.tsx new file mode 100644 index 000000000000..bb7372924f1d --- /dev/null +++ b/web/src/app/ee/admin/performance/custom-analytics/CustomAnalyticsUpdateForm.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { Label, SubLabel } from "@/components/admin/connectors/Field"; +import { usePopup } from "@/components/admin/connectors/Popup"; +import { SettingsContext } from "@/components/settings/SettingsProvider"; +import { Button, Text } from "@tremor/react"; +import { useContext, useState } from "react"; + +export function CustomAnalyticsUpdateForm() { + const settings = useContext(SettingsContext); + if (!settings) { + return null; + } + const customAnalyticsScript = settings.customAnalyticsScript; + + const [newCustomAnalyticsScript, setNewCustomAnalyticsScript] = + useState(customAnalyticsScript || ""); + const [secretKey, setSecretKey] = useState(""); + + const { popup, setPopup } = usePopup(); + + return ( +
+ {popup} +
+ + + Specify the Javascript that should run on page load in order to + initialize your custom tracking/analytics. + + + Do not include the{" "} + <script></script> tags. + If you upload a script below but you are not recieving any events in + your analytics platform, try removing all extra whitespace before each + line of JavaScript. + +