Compare commits

..

3 Commits

Author SHA1 Message Date
lebaudantoine c6a45ce58a 📈(frontend) track user interactions from left side panel
Add analytics tracking for actions initiated from the sidebar to monitor
feature usage patterns and user engagement metrics.
2025-05-25 01:07:25 +02:00
lebaudantoine 3cfcdcf036 📈(frontend) add tracking for AI actions usage and success rates
Implement analytics tracking on AI actions to collect insights on usage
patterns and monitor error/success rates. Enables data-driven improvements.

Temporary solution until backend implements more precise tracking for
comprehensive AI feature analytics.
2025-05-25 01:07:19 +02:00
lebaudantoine 69006f0ef0 (frontend) add capture method support to PostHogAnalytics wrapper
Implement capture method in PostHogAnalytics wrapper to support direct
event tracking through PostHog SDK. Enables more granular analytics
event handling within the application's analytics abstraction layer.
2025-05-25 01:00:57 +02:00
12 changed files with 48 additions and 175 deletions
-2
View File
@@ -1,2 +0,0 @@
web: bin/buildpack_start.sh
postdeploy: python manage.py migrate
-15
View File
@@ -1,15 +0,0 @@
#!/bin/bash
set -o errexit # always exit on error
set -o pipefail # don't ignore exit codes when piping output
echo "-----> Running post-compile script"
rm -rf docker docs env.d gitlint src/frontend/apps/e2e
rm -rf src/frontend/apps
rm -rf src/frontend/packages
# Remove some of the larger packages required by the frontend only
rm -rf src/frontend/node_modules/@next src/frontend/node_modules/next src/frontend/node_modules/react-icons src/frontend/node_modules/@gouvfr-lasuite
# du -ch | sort -rh | head -n 100
-15
View File
@@ -1,15 +0,0 @@
#!/bin/bash
set -o errexit # always exit on error
set -o pipefail # don't ignore exit codes when piping output
echo "-----> Running post-frontend script"
# Move the frontend build to the nginx root and clean up
mkdir -p build/
mv src/frontend/apps/impress/out build/frontend-out
mv src/backend/* ./
mv src/nginx/* ./
echo "3.13" > .python-version
-18
View File
@@ -1,18 +0,0 @@
#!/bin/bash
# Start the Django backend server
gunicorn -b :8000 impress.wsgi:application --log-file - &
# Start the Y provider service
cd src/frontend/servers/y-provider && PORT=4444 ../../.scalingo/node/bin/node dist/start-server.js &
# Start the Nginx server
bin/run &
# if the current shell is killed, also terminate all its children
trap "pkill SIGTERM -P $$" SIGTERM
# wait for a single child to finish,
wait -n
# then kill all the other tasks
pkill -P $$
+1 -4
View File
@@ -16,7 +16,6 @@ from socket import gethostbyname, gethostname
from django.utils.translation import gettext_lazy as _
import dj_database_url
import sentry_sdk
from configurations import Configuration, values
from sentry_sdk.integrations.django import DjangoIntegration
@@ -75,9 +74,7 @@ class Base(Configuration):
# Database
DATABASES = {
"default": dj_database_url.config()
if os.environ.get("DATABASE_URL")
else {
"default": {
"ENGINE": values.Value(
"django.db.backends.postgresql_psycopg2",
environ_name="DB_ENGINE",
-1
View File
@@ -29,7 +29,6 @@ dependencies = [
"boto3==1.38.18",
"Brotli==1.1.0",
"celery[redis]==5.5.2",
"dj-database-url==2.3.0",
"django-configurations==2.5.1",
"django-cors-headers==4.7.0",
"django-countries==7.6.1",
@@ -16,6 +16,7 @@ import { useTranslation } from 'react-i18next';
import { isAPIError } from '@/api';
import { Box, Icon } from '@/components';
import { useDocOptions, useDocStore } from '@/docs/doc-management/';
import { useAnalytics } from '@/libs';
import {
AITransformActions,
@@ -217,22 +218,44 @@ const AIMenuItemTransform = ({
children,
icon,
}: PropsWithChildren<AIMenuItemTransform>) => {
const { trackEvent } = useAnalytics();
const { mutateAsync: requestAI, isPending } = useDocAITransform();
const editor = useBlockNoteEditor();
const requestAIAction = async (selectedBlocks: Block[]) => {
const text = await editor.blocksToMarkdownLossy(selectedBlocks);
const requestStartTime = performance.now();
const responseAI = await requestAI({
text,
action,
docId,
});
const requestDuration = performance.now() - requestStartTime;
const eventProperties = {
eventName: 'requestAIAction',
action: action,
docId: docId,
requestLength: text.length,
numberBlocks: selectedBlocks.length,
requestDuration: requestDuration,
};
if (!responseAI?.answer) {
trackEvent({
...eventProperties,
status: 'error',
});
throw new Error('No response from AI');
}
trackEvent({
...eventProperties,
status: 'success',
responseLength: String(responseAI.answer),
});
const markdown = await editor.tryParseMarkdownToBlocks(responseAI.answer);
editor.replaceBlocks(selectedBlocks, markdown);
};
@@ -8,12 +8,14 @@ import { useCreateDoc } from '@/docs/doc-management';
import { DocSearchModal } from '@/docs/doc-search';
import { useAuth } from '@/features/auth';
import { useCmdK } from '@/hook/useCmdK';
import { useAnalytics } from '@/libs';
import { useLeftPanelStore } from '../stores';
export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
const router = useRouter();
const { authenticated } = useAuth();
const { trackEvent } = useAnalytics();
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
const openSearchModal = useCallback(() => {
@@ -23,8 +25,10 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
return;
}
trackEvent({ eventName: 'openSearchModal', position: 'LeftPanelHeader' });
setIsSearchModalOpen(true);
}, []);
}, [trackEvent]);
const closeSearchModal = useCallback(() => {
setIsSearchModalOpen(false);
@@ -46,6 +50,7 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
};
const createNewDoc = () => {
trackEvent({ eventName: 'createNewDoc', position: 'LeftPanelHeader' });
createDoc();
};
@@ -9,7 +9,15 @@ type AnalyticEventUser = {
email: string;
};
export type AnalyticEvent = AnalyticEventClick | AnalyticEventUser;
export type AnalyticEventGeneric = {
eventName: string;
[key: string]: string | number;
};
export type AnalyticEvent =
| AnalyticEventClick
| AnalyticEventUser
| AnalyticEventGeneric;
export abstract class AbstractAnalytic {
public constructor() {
@@ -3,7 +3,7 @@ import posthog from 'posthog-js';
import { PostHogProvider as PHProvider } from 'posthog-js/react';
import { JSX, PropsWithChildren, ReactNode, useEffect } from 'react';
import { AbstractAnalytic, AnalyticEvent } from '@/libs/';
import { AbstractAnalytic, AnalyticEvent, AnalyticEventGeneric } from '@/libs/';
export class PostHogAnalytic extends AbstractAnalytic {
private conf?: PostHogConf = undefined;
@@ -19,8 +19,14 @@ export class PostHogAnalytic extends AbstractAnalytic {
}
public trackEvent(evt: AnalyticEvent): void {
if (evt.eventName === 'user') {
posthog.identify(evt.id, { email: evt.email });
switch (evt.eventName) {
case 'user':
posthog.identify(evt.id as string, { email: evt.email });
break;
default:
const { eventName, ...properties } = evt as AnalyticEventGeneric;
posthog.capture(eventName, properties);
break;
}
}
-1
View File
@@ -19,7 +19,6 @@
"app:build": "yarn APP_IMPRESS run build",
"app:test": "yarn APP_IMPRESS run test",
"ci:build": "yarn APP_IMPRESS run build:ci",
"build": "yarn APP_IMPRESS run build && yarn COLLABORATION_SERVER run build",
"e2e:test": "yarn APP_E2E run test",
"lint": "yarn APP_IMPRESS run lint && yarn APP_E2E run lint && yarn workspace eslint-config-impress run lint && yarn I18N run lint && yarn COLLABORATION_SERVER run lint",
"i18n:extract": "yarn I18N run extract-translation",
-114
View File
@@ -1,114 +0,0 @@
# ERB templated nginx configuration
# see https://doc.scalingo.com/platform/deployment/buildpacks/nginx
upstream backend_server {
server localhost:8000 fail_timeout=0;
}
upstream collaboration_server {
server localhost:4444 fail_timeout=0;
}
server {
listen <%= ENV["PORT"] %>;
server_name _;
root /app/build/frontend-out;
error_page 404 /404.html;
location /collaboration/api/ {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_pass http://collaboration_server;
}
location /collaboration/ws/ {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
# Set appropriate timeout for WebSocketAdd commentMore actions
proxy_read_timeout 86400;
proxy_send_timeout 86400;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_pass http://collaboration_server;
}
# Django rest framework
location ^~ /api/ {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_pass http://backend_server;
}
# Django admin
location ^~ /admin/ {
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_pass http://backend_server;
}
# Proxy auth for media
location /media/ {
# Auth request configuration
auth_request /media-auth;
auth_request_set $authHeader $upstream_http_authorization;
auth_request_set $authDate $upstream_http_x_amz_date;
auth_request_set $authContentSha256 $upstream_http_x_amz_content_sha256;
# Pass specific headers from the auth response
proxy_set_header Authorization $authHeader;
proxy_set_header X-Amz-Date $authDate;
proxy_set_header X-Amz-Content-SHA256 $authContentSha256;
# Get resource from Object Storage
proxy_pass <%= ENV["AWS_S3_ENDPOINT_URL"] %>/<%= ENV["AWS_STORAGE_BUCKET_NAME"] %>/;
proxy_set_header Host <%= ENV["AWS_S3_ENDPOINT_URL"].split("://")[1] %>;
add_header Content-Security-Policy "default-src 'none'" always;
}
location /media-auth {
proxy_pass http://backend_server/api/v1.0/documents/media-auth/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Original-URL $request_uri;
# Prevent the body from being passed
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-Method $request_method;
}
location / {
try_files $uri index.html $uri/ =404;
}
location ~ "^/docs/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/?$" {
try_files $uri /docs/[id]/index.html;
}
location = /404.html {
internal;
}
}