Compare commits

..

1 Commits

Author SHA1 Message Date
lebaudantoine 39d3f6dd8e ♻️(frontend) wrap MuteEveryoneButton with AdminOrOwnerOnly
Avoids mounting the button's hooks and rendering its logic for users who
are not admin or owner, which previously happened on every re-render
of the parent.
2026-07-07 18:15:29 +02:00
27 changed files with 1012 additions and 2058 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "18"
- name: Restore the mail templates
uses: actions/cache@v5
-17
View File
@@ -8,17 +8,6 @@ and this project adheres to
## [Unreleased]
### Changed
- 🗑️(settings) deprecate SUMMARY_SERVICE_VERSION=1
- ⬆️(mail) update mjml to v5 and @html-to/text-cli
### Fixed
- 🩹(backend) identify externally provisioned users to PostHog
## [1.23.0] - 2026-07-08
### Added
- ✨(backend) extend analytics module to support feature flags
@@ -34,12 +23,6 @@ and this project adheres to
- ♻️(backend) refactor analytics backend from Protocol to abstract class
- 🔥(summary) remove call to summary enabled feature flag
- ♻️(frontend) wrap MuteEveryoneButton with AdminOrOwnerOnly
- ⬆️(frontend) upgrade livekit-client from 2.19.0 to 2.19.2
- ⬆️(frontend) upgrade posthog-js from 1.386.5 to 1.387.0
- ⬆️(frontend) upgrade @tanstack/react-query from 5.100.14 to 5.101.0
- ⬆️(frontend) update the frontend build image to Node 22
- 🔒️(frontend) update docker image to nginx-unprivileged:1.30.3-alpine3.23
- ✨(summary) more precise analytics events
### Fixed
-12
View File
@@ -15,15 +15,3 @@ the following command inside your docker container:
(Note : in your development environment, you can `make migrate`.)
## [Unreleased]
## v1.23.0
As part of the 1.23.0 release, the legacy `api/v1` implementation has been removed from the _experimental_ Summary service and Meet has been migrated to the new `api/v2`.
**To avoid a breaking change, the Meet backend continues to use the Summary service's v1-compatible API format by default (`SUMMARY_SERVICE_VERSION` setting defaults to `1`).**
If you are deploying both Meet and Summary from this repository, you must configure the Meet backend to use the v2 API by setting the following environment variable `SUMMARY_SERVICE_VERSION=2`.
If you are upgrading only the Meet deployment while keeping an older Summary v1 compatible deployment, no action is required, as the v1-compatible API remains the default.
Note that we plan on removing the legacy `v1` summary compatibility in a future major version. If you have your own implementation for the summary service, we recommend updating its API contract and setting `SUMMARY_SERVICE_VERSION=2`.
-6
View File
@@ -113,12 +113,6 @@ update_python_version "summary"
# Update agents pyproject.toml
update_python_version "agents"
# Run uv lock in agents
print_info "Running uv lock in agents..."
cd "src/agents"
uv lock
cd -
# Update CHANGELOG
print_info "Updating CHANGELOG..."
+2 -2
View File
@@ -1,5 +1,5 @@
# ---- Front-end image ----
FROM node:22-alpine AS frontend-deps
FROM node:20-alpine AS frontend-deps
WORKDIR /home/frontend/
@@ -54,7 +54,7 @@ RUN npx webpack --mode production
# ---- Front-end image ----
FROM nginxinc/nginx-unprivileged:1.30.3-alpine3.23 AS frontend-production
FROM nginxinc/nginx-unprivileged:alpine3.23 AS frontend-production
USER root
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "agents"
version = "1.23.0"
version = "1.22.0"
requires-python = ">=3.12"
dependencies = [
"livekit-agents==1.6.4",
+1 -1
View File
@@ -9,7 +9,7 @@ resolution-markers = [
[[package]]
name = "agents"
version = "1.23.0"
version = "1.22.0"
source = { virtual = "." }
dependencies = [
{ name = "livekit-agents" },
@@ -215,6 +215,5 @@ class RoomViewSet(
"client_id": client_id,
"external_api": True,
"auth_method": auth_method,
"$set": {"email": self.request.user.email},
},
)
-15
View File
@@ -13,7 +13,6 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
# pylint: disable=too-many-lines
import json
import warnings
from os import path
from socket import gethostbyname, gethostname
@@ -1129,20 +1128,6 @@ class Base(Configuration):
"FILE_UPLOAD_TMP_PATH cannot be the same as FILE_UPLOAD_PATH"
)
if (
cls.SUMMARY_SERVICE_VERSION == 1
and cls.SUMMARY_SERVICE_ENDPOINT is not None
):
warnings.warn(
"SUMMARY_SERVICE_VERSION=1 is deprecated. "
"The legacy v1 API has been removed from the experimental "
"Summary service. Please update your Summary service deployment to "
"the v2 API and set SUMMARY_SERVICE_VERSION=2.",
# We use UserWarning to make sure it shows up in production deployment
UserWarning,
stacklevel=2,
)
# The SENTRY_DSN setting should be available to activate sentry for an environment
if cls.SENTRY_DSN is not None:
sentry_sdk.init(
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "uv_build"
[project]
name = "meet"
version = "1.23.0"
version = "1.22.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
+1 -1
View File
@@ -1187,7 +1187,7 @@ wheels = [
[[package]]
name = "meet"
version = "1.23.0"
version = "1.22.0"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
+2 -2
View File
@@ -1,4 +1,4 @@
FROM node:22-alpine AS frontend-deps
FROM node:20-alpine AS frontend-deps
USER node
@@ -39,7 +39,7 @@ ENV VITE_API_BASE_URL=${VITE_API_BASE_URL}
RUN npm run build
# ---- Front-end image ----
FROM nginxinc/nginx-unprivileged:1.30.3-alpine3.23 AS frontend-production
FROM nginxinc/nginx-unprivileged:alpine3.23 AS frontend-production
USER root
+20 -20
View File
@@ -1,12 +1,12 @@
{
"name": "meet",
"version": "1.23.0",
"version": "1.22.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "meet",
"version": "1.23.0",
"version": "1.22.0",
"dependencies": {
"@fontsource-variable/atkinson-hyperlegible-next": "5.2.6",
"@fontsource-variable/lexend": "5.2.11",
@@ -18,7 +18,7 @@
"@pandacss/preset-panda": "1.11.3",
"@react-types/overlays": "3.10.0",
"@remixicon/react": "4.9.0",
"@tanstack/react-query": "5.101.0",
"@tanstack/react-query": "5.100.14",
"@timephy/rnnoise-wasm": "1.0.0",
"crisp-sdk-web": "1.1.2",
"derive-valtio": "0.2.0",
@@ -28,8 +28,8 @@
"i18next-browser-languagedetector": "8.2.1",
"i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.19.2",
"posthog-js": "1.387.0",
"livekit-client": "2.19.0",
"posthog-js": "1.386.5",
"react": "18.3.1",
"react-aria": "3.49.0",
"react-aria-components": "1.18.0",
@@ -2357,9 +2357,9 @@
}
},
"node_modules/@tanstack/query-core": {
"version": "5.101.0",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.0.tgz",
"integrity": "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==",
"version": "5.100.14",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.14.tgz",
"integrity": "sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2378,12 +2378,12 @@
}
},
"node_modules/@tanstack/react-query": {
"version": "5.101.0",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.0.tgz",
"integrity": "sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg==",
"version": "5.100.14",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.14.tgz",
"integrity": "sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.101.0"
"@tanstack/query-core": "5.100.14"
},
"funding": {
"type": "github",
@@ -8035,9 +8035,9 @@
"license": "MIT"
},
"node_modules/livekit-client": {
"version": "2.19.2",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.19.2.tgz",
"integrity": "sha512-Kvk07QYDWRAbmYNLRll04ZIuxMQobW/oLPYnmR1kCy8GGHpU0gqyHf704Rz+29zfy8IJZRjKqeVbzGSKn9sumw==",
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.19.0.tgz",
"integrity": "sha512-aolY1XDAtx0nHKBNm29W9OhzBnSz1CP5kq3phvRhFfi1NbvMXs8tcACjAkZTnIKgihkp+BiJScZZ3tZv0Gz8sA==",
"license": "Apache-2.0",
"dependencies": {
"@livekit/mutex": "1.1.1",
@@ -9033,13 +9033,13 @@
"license": "MIT"
},
"node_modules/posthog-js": {
"version": "1.387.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.387.0.tgz",
"integrity": "sha512-Pv1jUMySMN62zoAxdJBJPV8n62lkHdjuWhpeU7izczc5Dqbx3hhqO2hkrNTI8Yx1ezmWk2qUHZs03FuOBubdFQ==",
"version": "1.386.5",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.386.5.tgz",
"integrity": "sha512-ASejQQf5Xw0XolMwH/KCLZlZtoyLK6VsvORwGagAtfa8/ElIOF76BMQspkDsRTybEI+uzHqRDm2m/na1Dki2mA==",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@posthog/core": "^1.33.0",
"@posthog/types": "^1.387.0",
"@posthog/core": "^1.32.3",
"@posthog/types": "^1.386.3",
"core-js": "^3.38.1",
"dompurify": "^3.3.2",
"fflate": "^0.4.8",
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "meet",
"private": true,
"version": "1.23.0",
"version": "1.22.0",
"type": "module",
"scripts": {
"dev": "panda codegen && vite",
@@ -25,7 +25,7 @@
"@pandacss/preset-panda": "1.11.3",
"@react-types/overlays": "3.10.0",
"@remixicon/react": "4.9.0",
"@tanstack/react-query": "5.101.0",
"@tanstack/react-query": "5.100.14",
"@timephy/rnnoise-wasm": "1.0.0",
"crisp-sdk-web": "1.1.2",
"derive-valtio": "0.2.0",
@@ -35,8 +35,8 @@
"i18next-browser-languagedetector": "8.2.1",
"i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.19.2",
"posthog-js": "1.387.0",
"livekit-client": "2.19.0",
"posthog-js": "1.386.5",
"react": "18.3.1",
"react-aria": "3.49.0",
"react-aria-components": "1.18.0",
+1 -1
View File
@@ -6,4 +6,4 @@ DIR_MAILS="../backend/core/templates/mail/html/"
if [ ! -d "${DIR_MAILS}" ]; then
mkdir -p "${DIR_MAILS}";
fi
mjml mjml/*.mjml -o "${DIR_MAILS}" --config.allowIncludes true;
mjml mjml/*.mjml -o "${DIR_MAILS}";
+2 -7
View File
@@ -2,11 +2,6 @@
<mj-include path="./partial/header.mjml" />
<mj-body mj-class="bg--blue-100">
<!--
We load django tags here so they appear in the body of the HTML output.
This ensures html-to-text also includes them in the plain text template.
-->
<mj-raw>{% load i18n static extra_tags %}</mj-raw>
<mj-wrapper css-class="wrapper" padding="0 40px 40px 40px">
<mj-section css-class="wrapper-logo">
<mj-column>
@@ -64,7 +59,7 @@
</mj-column>
</mj-section>
</mj-wrapper>
<mj-include path="./partial/footer.mjml" />
</mj-body>
<mj-include path="./partial/footer.mjml" />
</mjml>
+9 -4
View File
@@ -1,16 +1,21 @@
<mj-head>
<mj-title>{{ title }}</mj-title>
<mj-preview>{{ title }}</mj-preview>
<mj-font name="Roboto" href="https://fonts.bunny.net/css?family=roboto:400,700,900" />
<mj-preview>
<!--
We load django tags here, in this way there are put within the body in html output
so the html-to-text command includes it within its output
-->
{% load i18n static extra_tags %}
{{ title }}
</mj-preview>
<mj-attributes>
<mj-font name="Roboto" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700;900&display=swap" />
<mj-all
font-family="Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif"
font-size="16px"
line-height="1.5em"
color="#3A3A3A"
/>
<mj-text font-family="Roboto, sans-serif" />
<mj-button font-family="Roboto, sans-serif" />
</mj-attributes>
<mj-style>
/* Reset */
-5
View File
@@ -2,11 +2,6 @@
<mj-include path="./partial/header.mjml" />
<mj-body mj-class="bg--blue-100">
<!--
We load django tags here so they appear in the body of the HTML output.
This ensures html-to-text also includes them in the plain text template.
-->
<mj-raw>{% load i18n static extra_tags %}</mj-raw>
<mj-wrapper css-class="wrapper" padding="5px 25px 0px 25px">
<mj-section css-class="wrapper-logo">
<mj-column>
+932 -1894
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,11 +1,11 @@
{
"name": "mail_mjml",
"version": "1.23.0",
"version": "1.22.0",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {
"@html-to/text-cli": "0.6.0",
"mjml": "5.4.0"
"@html-to/text-cli": "0.5.4",
"mjml": "4.18.0"
},
"private": true,
"scripts": {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "sdk",
"version": "1.23.0",
"version": "1.22.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sdk",
"version": "1.23.0",
"version": "1.22.0",
"license": "ISC",
"workspaces": [
"./library",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "sdk",
"version": "1.23.0",
"version": "1.22.0",
"author": "",
"license": "ISC",
"description": "",
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "summary"
version = "1.23.0"
version = "1.22.0"
dependencies = [
"fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0",
+14 -13
View File
@@ -59,16 +59,17 @@ async def create_transcribe_task_v2(
]
)
properties = {}
if request.user_email:
properties["$set"] = {"email": request.user_email}
# We track the request, this also properly initializes the user in the
# analytics system, so that later feature flags work properly
analytics.capture(
settings.posthog_transcript_request,
settings.posthog_event_request,
request.user_sub,
properties=properties,
properties={
"kind": "transcribe",
"$set": {
"email": request.user_email,
},
},
)
return TranscribeWebhookPendingPayload(job_id=task.id).model_dump()
@@ -92,15 +93,15 @@ async def create_summarize_task_v2(
# We track the request, this also properly initializes the user in the
# analytics system, so that later feature flags work properly
properties = {}
if request.user_email:
properties["$set"] = {"email": request.user_email}
analytics.capture(
settings.posthog_summary_request,
settings.posthog_event_request,
request.user_sub,
properties=properties,
properties={
"kind": "summarize",
"$set": {
"email": request.user_email,
},
},
)
return SummarizeWebhookPendingPayload(job_id=task.id).model_dump()
+6 -11
View File
@@ -11,7 +11,7 @@ from celery.utils.log import get_task_logger
from posthog import Posthog
from summary.core.config import get_settings
from summary.core.models import SummarizeTaskJob, TranscribeTaskJob
from summary.core.models import TranscribeTaskJob
logger = get_task_logger(__name__)
settings = get_settings()
@@ -109,15 +109,19 @@ class MetadataManager:
"""Check if task_id exists in tasks metadata cache."""
return self._redis.exists(self._get_redis_key(task_id))
def create(self, task_id: str, task_payload: TranscribeTaskJob | SummarizeTaskJob):
def create(self, task_id: str, task_payload: TranscribeTaskJob):
"""Create initial metadata entry for a new task."""
if self._is_disabled or self.has_task_id(task_id):
return
start_time = time.time()
parts = urlsplit(task_payload.cloud_storage_url)
clean_url = urlunsplit((parts.scheme, parts.netloc, parts.path, "", ""))
initial_metadata = {
"start_time": start_time,
"asr_model": settings.whisperx_asr_model,
"retries": 0,
"filename": clean_url,
"sub": task_payload.user_sub,
# avoid None in redis, it shouldn't happen anyway in prod
"email": task_payload.user_email or "",
@@ -125,15 +129,6 @@ class MetadataManager:
"queuing_time": round(start_time - task_payload.received_at.timestamp(), 2),
}
if isinstance(task_payload, TranscribeTaskJob):
parts = urlsplit(task_payload.cloud_storage_url)
clean_url = urlunsplit((parts.scheme, parts.netloc, parts.path, "", ""))
initial_metadata["source_url"] = clean_url
initial_metadata["asr_model"] = settings.whisperx_asr_model
elif isinstance(task_payload, SummarizeTaskJob):
initial_metadata["content_length"] = len(task_payload.content)
initial_metadata["llm_model"] = settings.llm_model
self._save_metadata(task_id, initial_metadata)
def retry(self, task_id):
+5 -26
View File
@@ -542,28 +542,28 @@ def process_audio_transcribe_v2_task(
call_webhook_v2_task.apply_async(
args=[success_payload.model_dump(), payload.tenant_id]
)
metadata_manager.capture(job_id, settings.posthog_transcript_success)
metadata_manager.capture(job_id, settings.posthog_event_success)
return success_payload.model_dump()
@signals.task_prerun.connect(sender=process_audio_transcribe_v2_task)
def task_started_transcript(task_id=None, task=None, args=None, **kwargs):
def task_started(task_id=None, task=None, args=None, **kwargs):
"""Signal handler called before task execution begins."""
if args:
metadata_manager.create(task_id, TranscribeTaskJob.model_validate(args[0]))
@signals.task_retry.connect(sender=process_audio_transcribe_v2_task)
def task_retry_handler_transcript(request=None, reason=None, einfo=None, **kwargs):
def task_retry_handler(request=None, reason=None, einfo=None, **kwargs):
"""Signal handler called when task execution retries."""
metadata_manager.retry(request.id)
@signals.task_failure.connect(sender=process_audio_transcribe_v2_task)
def task_failure_handler_transcript(task_id, exception=None, **kwargs):
def task_failure_handler(task_id, exception=None, **kwargs):
"""Signal handler called when task execution fails permanently."""
metadata_manager.capture(task_id, settings.posthog_transcript_failure)
metadata_manager.capture(task_id, settings.posthog_event_failure)
@signals.task_failure.connect(sender=process_audio_transcribe_v2_task)
@@ -648,30 +648,9 @@ def summarize_v2_task(
call_webhook_v2_task.apply_async(
args=[success_payload.model_dump(), payload.tenant_id]
)
metadata_manager.capture(job_id, settings.posthog_summary_success)
return success_payload.model_dump()
@signals.task_prerun.connect(sender=summarize_v2_task)
def task_started_summary(task_id=None, task=None, args=None, **kwargs):
"""Signal handler called before task execution begins."""
if args:
metadata_manager.create(task_id, SummarizeTaskJob.model_validate(args[0]))
@signals.task_retry.connect(sender=summarize_v2_task)
def task_retry_handler_summary(request=None, reason=None, einfo=None, **kwargs):
"""Signal handler called when task execution retries."""
metadata_manager.retry(request.id)
@signals.task_failure.connect(sender=summarize_v2_task)
def task_failure_handler_summary(task_id, exception=None, **kwargs):
"""Signal handler called when task execution fails permanently."""
metadata_manager.capture(task_id, settings.posthog_summary_failure)
@signals.task_failure.connect(sender=summarize_v2_task)
def handle_summarize_v2_failed(
sender,
+3 -6
View File
@@ -130,12 +130,9 @@ class Settings(BaseSettings):
posthog_enabled: bool = False
posthog_api_key: Optional[str] = None
posthog_api_host: Optional[str] = "https://eu.i.posthog.com"
posthog_transcript_request: str = "transcript-request"
posthog_transcript_failure: str = "transcript-failure"
posthog_transcript_success: str = "transcript-success"
posthog_summary_request: str = "summary-request"
posthog_summary_failure: str = "summary-failure"
posthog_summary_success: str = "summary-success"
posthog_event_failure: str = "transcript-failure"
posthog_event_success: str = "transcript-success"
posthog_event_request: str = "transcript-request"
# Langfuse (LLM Observability)
langfuse_enabled: bool = False