From 00c44fb38a9349f29f919b47f7e9018b35242b3a Mon Sep 17 00:00:00 2001 From: Vri Date: Sat, 3 Jun 2023 12:30:00 +0000 Subject: [PATCH 01/28] Translated using Weblate (German) Currently translated at 100.0% (139 of 139 strings) Translation: Element Call/element-call Translate-URL: https://translate.element.io/projects/element-call/element-call/de/ --- public/locales/de/app.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/public/locales/de/app.json b/public/locales/de/app.json index 560f74f1..119ad31f 100644 --- a/public/locales/de/app.json +++ b/public/locales/de/app.json @@ -132,5 +132,10 @@ "Developer Settings": "Entwicklereinstellungen", "By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy and our <5>Cookie Policy.": "Mit der Teilnahme an der Beta akzeptierst du die Sammlung von anonymen Daten, die wir zur Verbesserung des Produkts verwenden. Weitere Informationen zu den von uns erhobenen Daten findest du in unserer <2>Datenschutzerklärung und unseren <5>Cookie-Richtlinien.", "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0><1>Du kannst deine Zustimmung durch Abwählen dieses Kästchens zurückziehen. Falls du dich aktuell in einem Anruf befindest, wird diese Einstellung nach dem Ende des Anrufs wirksam.", - "Feedback": "Rückmeldung" + "Feedback": "Rückmeldung", + "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Falls du auf Probleme stößt oder einfach nur eine Rückmeldung geben möchtest, sende uns bitte eine kurze Beschreibung.", + "Your feedback": "Deine Rückmeldung", + "Thanks, we received your feedback!": "Danke, wir haben deine Rückmeldung erhalten!", + "Submitting…": "Sende …", + "Submit": "Absenden" } From 48cf604bd1b8137a718fa1bdef4cd8954aa93c98 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Fri, 2 Jun 2023 17:30:17 +0000 Subject: [PATCH 02/28] Translated using Weblate (Ukrainian) Currently translated at 100.0% (139 of 139 strings) Translation: Element Call/element-call Translate-URL: https://translate.element.io/projects/element-call/element-call/uk/ --- public/locales/uk/app.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/public/locales/uk/app.json b/public/locales/uk/app.json index 75ce8628..b02bcf8c 100644 --- a/public/locales/uk/app.json +++ b/public/locales/uk/app.json @@ -39,7 +39,6 @@ "Sending debug logs…": "Надсилання журналу налагодження…", "Send debug logs": "Надіслати журнал налагодження", "Select an option": "Вибрати опцію", - "Save": "Зберегти", "Return to home screen": "Повернутися на екран домівки", "Remove": "Вилучити", "Release to stop": "Відпустіть, щоб закінчити", @@ -132,5 +131,11 @@ "Expose developer settings in the settings window.": "Відкрийте налаштування розробника у вікні налаштувань.", "Developer Settings": "Налаштування розробника", "By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy and our <5>Cookie Policy.": "Користуючись дочасним доступом, ви даєте згоду на збір анонімних даних, які ми використовуємо для вдосконалення продукту. Ви можете знайти більше інформації про те, які дані ми відстежуємо в нашій <2>Політиці Приватності і нашій <5>Політиці про куки.", - "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0><1>Ви можете відкликати згоду, прибравши цей прапорець. Якщо ви зараз розмовляєте, це налаштування застосується після завершення виклику." + "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0><1>Ви можете відкликати згоду, прибравши цей прапорець. Якщо ви зараз розмовляєте, це налаштування застосується після завершення виклику.", + "Your feedback": "Ваш відгук", + "Thanks, we received your feedback!": "Дякуємо, ми отримали ваш відгук!", + "Submitting…": "Надсилання…", + "Submit": "Надіслати", + "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Якщо у вас виникли проблеми або ви просто хочете залишити відгук, надішліть нам короткий опис нижче.", + "Feedback": "Відгук" } From a0da11ea7871b99d7055d3f492d536c43d6df779 Mon Sep 17 00:00:00 2001 From: phardyle Date: Sat, 3 Jun 2023 13:03:01 +0000 Subject: [PATCH 03/28] Translated using Weblate (Chinese (Simplified)) Currently translated at 90.6% (126 of 139 strings) Translation: Element Call/element-call Translate-URL: https://translate.element.io/projects/element-call/element-call/zh_Hans/ --- public/locales/zh-Hans/app.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/zh-Hans/app.json b/public/locales/zh-Hans/app.json index 0b494e1f..82bf38d7 100644 --- a/public/locales/zh-Hans/app.json +++ b/public/locales/zh-Hans/app.json @@ -124,6 +124,6 @@ "Call link copied": "链接已复制", "By clicking \"Join call now\", you agree to our <2>Terms and conditions": "点击“现在加入”则表示同意我们的<2>条款与条件<2>", "Avatar": "头像", - "<0>Oops, something's gone wrong.": "", + "<0>Oops, something's gone wrong.": "<0>哎哟,出问题了。", "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "" } From 9f924aef64f6c47ed326214b0cadab2f1936c20e Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Fri, 2 Jun 2023 20:32:18 +0000 Subject: [PATCH 04/28] Translated using Weblate (Slovak) Currently translated at 100.0% (139 of 139 strings) Translation: Element Call/element-call Translate-URL: https://translate.element.io/projects/element-call/element-call/sk/ --- public/locales/sk/app.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/public/locales/sk/app.json b/public/locales/sk/app.json index e8b2ec4b..407680de 100644 --- a/public/locales/sk/app.json +++ b/public/locales/sk/app.json @@ -20,7 +20,6 @@ "Sending debug logs…": "Odosielanie záznamov o ladení…", "Send debug logs": "Odoslať záznamy o ladení", "Select an option": "Vyberte možnosť", - "Save": "Uložiť", "Return to home screen": "Návrat na domovskú obrazovku", "Remove": "Odstrániť", "Release spacebar key to stop": "Pustite medzerník pre ukončenie", @@ -132,5 +131,11 @@ "Expose developer settings in the settings window.": "Zobraziť nastavenia pre vývojárov v okne nastavení.", "Developer Settings": "Nastavenia pre vývojárov", "By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy and our <5>Cookie Policy.": "Účasťou v tejto beta verzii súhlasíte so zhromažďovaním anonymných údajov, ktoré použijeme na zlepšenie produktu. Viac informácií o tom, ktoré údaje sledujeme, nájdete v našich <2>Zásadách ochrany osobných údajov a <5>Zásadách používania súborov cookie.", - "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0><1>Súhlas môžete odvolať zrušením označenia tohto políčka. Ak práve prebieha hovor, toto nastavenie nadobudne platnosť po skončení hovoru." + "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0><1>Súhlas môžete odvolať zrušením označenia tohto políčka. Ak práve prebieha hovor, toto nastavenie nadobudne platnosť po skončení hovoru.", + "Your feedback": "Vaša spätná väzba", + "Thanks, we received your feedback!": "Ďakujeme, dostali sme vašu spätnú väzbu!", + "Submitting…": "Odosielanie…", + "Submit": "Odoslať", + "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Ak máte problémy alebo jednoducho chcete poskytnúť spätnú väzbu, pošlite nám krátky popis nižšie.", + "Feedback": "Spätná väzba" } From 5af7c9e7c7de875e38b0d3506cd30fcf2f8688fc Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 4 Jun 2023 13:07:31 +0000 Subject: [PATCH 05/28] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Element Call/element-call Translate-URL: https://translate.element.io/projects/element-call/element-call/ --- public/locales/bg/app.json | 1 - public/locales/cs/app.json | 1 - public/locales/es/app.json | 1 - public/locales/id/app.json | 1 - public/locales/ja/app.json | 1 - public/locales/pl/app.json | 1 - public/locales/tr/app.json | 1 - 7 files changed, 7 deletions(-) diff --git a/public/locales/bg/app.json b/public/locales/bg/app.json index b0eaff4a..a62b6b06 100644 --- a/public/locales/bg/app.json +++ b/public/locales/bg/app.json @@ -73,7 +73,6 @@ "Release to stop": "Отпуснете за да спрете", "Remove": "Премахни", "Return to home screen": "Връщане на началния екран", - "Saving…": "Запазване…", "Select an option": "Изберете опция", "Send debug logs": "Изпратете debug логове", "Sending…": "Изпращане…", diff --git a/public/locales/cs/app.json b/public/locales/cs/app.json index 143203ff..dabaa042 100644 --- a/public/locales/cs/app.json +++ b/public/locales/cs/app.json @@ -46,7 +46,6 @@ "Sending debug logs…": "Posílání ladícího záznamu…", "Send debug logs": "Poslat ladící záznam", "Select an option": "Vyberte možnost", - "Save": "Uložit", "Return to home screen": "Vrátit se na domácí obrazovku", "Remove": "Odstranit", "Registering…": "Registrování…", diff --git a/public/locales/es/app.json b/public/locales/es/app.json index 011cb4ab..104fee3a 100644 --- a/public/locales/es/app.json +++ b/public/locales/es/app.json @@ -47,7 +47,6 @@ "Sending debug logs…": "Enviando registros de depuración…", "Send debug logs": "Enviar registros de depuración", "Select an option": "Selecciona una opción", - "Save": "Guardar", "Return to home screen": "Volver a la pantalla de inicio", "Remove": "Eliminar", "Release to stop": "Suelta para parar", diff --git a/public/locales/id/app.json b/public/locales/id/app.json index 0bafaaee..6d6ba5d8 100644 --- a/public/locales/id/app.json +++ b/public/locales/id/app.json @@ -73,7 +73,6 @@ "Release to stop": "Lepaskan untuk berhenti", "Remove": "Hapus", "Return to home screen": "Kembali ke layar beranda", - "Saving…": "Menyimpan…", "Select an option": "Pilih sebuah opsi", "Send debug logs": "Kirim catatan pengawakutuan", "Sending…": "Mengirimkan…", diff --git a/public/locales/ja/app.json b/public/locales/ja/app.json index 577faac3..69f7411f 100644 --- a/public/locales/ja/app.json +++ b/public/locales/ja/app.json @@ -68,7 +68,6 @@ "Settings": "設定", "Sending…": "送信しています…", "Sending debug logs…": "デバッグログを送信しています…", - "Save": "保存", "Return to home screen": "ホーム画面に戻る", "Registering…": "登録しています…", "Register": "登録", diff --git a/public/locales/pl/app.json b/public/locales/pl/app.json index 9ef51443..32b5e09a 100644 --- a/public/locales/pl/app.json +++ b/public/locales/pl/app.json @@ -40,7 +40,6 @@ "Sending debug logs…": "Wysyłanie dzienników debugowania…", "Send debug logs": "Wyślij dzienniki debugowania", "Select an option": "Wybierz opcję", - "Save": "Zapisz", "Return to home screen": "Powróć do strony głównej", "Remove": "Usuń", "Release to stop": "Puść przycisk, aby zatrzymać", diff --git a/public/locales/tr/app.json b/public/locales/tr/app.json index e60ed7f3..f1870e3a 100644 --- a/public/locales/tr/app.json +++ b/public/locales/tr/app.json @@ -67,7 +67,6 @@ "Release to stop": "Kesmek için bırakın", "Remove": "Çıkar", "Return to home screen": "Ev ekranına geri dön", - "Saving…": "Kaydediliyor…", "Select an option": "Bir seçenek seç", "Send debug logs": "Hata ayıklama kütüğünü gönder", "Sending…": "Gönderiliyor…", From e129e90dd893a480afa718bee9180dbe1c5bbed8 Mon Sep 17 00:00:00 2001 From: Someone Date: Mon, 5 Jun 2023 04:16:05 +0000 Subject: [PATCH 06/28] Added translation using Weblate (Vietnamese) --- public/locales/vi/app.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 public/locales/vi/app.json diff --git a/public/locales/vi/app.json b/public/locales/vi/app.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/public/locales/vi/app.json @@ -0,0 +1 @@ +{} From 5ef0486eff9955af440255c5a812395b7b6a806f Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 5 Jun 2023 15:52:05 -0400 Subject: [PATCH 07/28] Add a URL parameter for allowing fallback ICE servers --- src/UrlParams.ts | 6 ++++++ src/widget.ts | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/UrlParams.ts b/src/UrlParams.ts index df62aefa..27b99cce 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -79,6 +79,11 @@ export interface UrlParams { * The Posthog analytics ID. It is only available if the user has given consent for sharing telemetry in element web. */ analyticsID: string | null; + /** + * Whether the app is allowed to use fallback STUN servers for ICE in case the + * user's homeserver doesn't provide any. + */ + allowIceFallback: boolean; } /** @@ -135,6 +140,7 @@ export const getUrlParams = ( fonts: getAllParams("font"), fontScale: Number.isNaN(fontScale) ? null : fontScale, analyticsID: getParam("analyticsID"), + allowIceFallback: hasParam("allowIceFallback"), }; }; diff --git a/src/widget.ts b/src/widget.ts index 6a38f9dd..0bee23f0 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -101,7 +101,14 @@ export const widget: WidgetHelpers | null = (() => { // We need to do this now rather than later because it has capabilities to // request, and is responsible for starting the transport (should it be?) - const { roomId, userId, deviceId, baseUrl, e2eEnabled } = getUrlParams(); + const { + roomId, + userId, + deviceId, + baseUrl, + e2eEnabled, + allowIceFallback, + } = getUrlParams(); if (!roomId) throw new Error("Room ID must be supplied"); if (!userId) throw new Error("User ID must be supplied"); if (!deviceId) throw new Error("Device ID must be supplied"); @@ -148,6 +155,7 @@ export const widget: WidgetHelpers | null = (() => { deviceId, timelineSupport: true, useE2eForGroupCall: e2eEnabled, + fallbackICEServerAllowed: allowIceFallback, } ); const clientPromise = client.startClient().then(() => client); From f0a6f5919e421fff88c7795fb8d6d3a4e21f4119 Mon Sep 17 00:00:00 2001 From: Enrico Schwendig Date: Tue, 6 Jun 2023 08:28:53 +0200 Subject: [PATCH 08/28] move webrtc etc. events from groupCall to matrix.call span (#1080) * add new linked span for connection stats * move stats span under call span and add user attribute * Update matrix-js-sdk --- package.json | 2 +- src/otel/OTelGroupCallMembership.ts | 111 ++++++++------- src/otel/ObjectFlattener.ts | 12 +- test/otel/ObjectFlattener-test.ts | 205 ++++++++++++++++------------ yarn.lock | 4 +- 5 files changed, 184 insertions(+), 150 deletions(-) diff --git a/package.json b/package.json index 8d9773bb..0d5325b5 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "i18next-browser-languagedetector": "^6.1.8", "i18next-http-backend": "^1.4.4", "lodash": "^4.17.21", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#a7b1dcaf9514b2e424a387e266c6f383a5909927", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#e70a1a1effe59e6754f9a10cc2df8eef81638c7d", "matrix-widget-api": "^1.3.1", "mermaid": "^8.13.8", "normalize.css": "^8.0.1", diff --git a/src/otel/OTelGroupCallMembership.ts b/src/otel/OTelGroupCallMembership.ts index 60ae2b34..a8d93b6a 100644 --- a/src/otel/OTelGroupCallMembership.ts +++ b/src/otel/OTelGroupCallMembership.ts @@ -333,22 +333,70 @@ export class OTelGroupCallMembership { public onConnectionStatsReport( statsReport: GroupCallStatsReport ) { - if (!ElementCallOpenTelemetry.instance) return; - - const type = OTelStatsReportType.ConnectionReport; - const data = - ObjectFlattener.flattenConnectionStatsReportObject(statsReport); - this.buildStatsEventSpan({ type, data }); + this.buildCallStatsSpan( + OTelStatsReportType.ConnectionReport, + statsReport.report + ); } public onByteSentStatsReport( statsReport: GroupCallStatsReport ) { - if (!ElementCallOpenTelemetry.instance) return; + this.buildCallStatsSpan( + OTelStatsReportType.ByteSentReport, + statsReport.report + ); + } - const type = OTelStatsReportType.ByteSentReport; - const data = ObjectFlattener.flattenByteSentStatsReportObject(statsReport); - this.buildStatsEventSpan({ type, data }); + public buildCallStatsSpan( + type: OTelStatsReportType, + report: ByteSentStatsReport | ConnectionStatsReport + ): void { + if (!ElementCallOpenTelemetry.instance) return; + let call: OTelCall | undefined; + const callId = report?.callId; + + if (callId) { + call = this.callsByCallId.get(callId); + } + + if (!call) { + this.callMembershipSpan?.addEvent(type + "_unknown_callid", { + "call.callId": callId, + "call.opponentMemberId": report.opponentMemberId + ? report.opponentMemberId + : "unknown", + }); + logger.error(`Received ${type} with unknown call ID: ${callId}`); + return; + } + const data = ObjectFlattener.flattenReportObject(type, report); + const ctx = opentelemetry.trace.setSpan( + opentelemetry.context.active(), + call.span + ); + + const options = { + links: [ + { + context: call.span.spanContext(), + }, + ], + }; + + const span = ElementCallOpenTelemetry.instance.tracer.startSpan( + type, + options, + ctx + ); + + span.setAttribute("matrix.callId", callId); + span.setAttribute( + "matrix.opponentMemberId", + report.opponentMemberId ? report.opponentMemberId : "unknown" + ); + span.addEvent("matrix.call.connection_stats_event", data); + span.end(); } public onSummaryStatsReport( @@ -381,45 +429,6 @@ export class OTelGroupCallMembership { span.end(); } } - - private buildStatsEventSpan(event: OTelStatsReportEvent): void { - // @ TODO: fix this - Because on multiple calls we receive multiple stats report spans. - // This could be break if stats arrived in same time from different call objects. - if (this.statsReportSpan.span === undefined && this.callMembershipSpan) { - const ctx = setSpan( - opentelemetry.context.active(), - this.callMembershipSpan - ); - this.statsReportSpan.span = - ElementCallOpenTelemetry.instance?.tracer.startSpan( - "matrix.groupCallMembership.statsReport", - undefined, - ctx - ); - if (this.statsReportSpan.span === undefined) { - return; - } - this.statsReportSpan.span.setAttribute( - "matrix.confId", - this.groupCall.groupCallId - ); - this.statsReportSpan.span.setAttribute("matrix.userId", this.myUserId); - this.statsReportSpan.span.setAttribute( - "matrix.displayName", - this.myMember ? this.myMember.name : "unknown-name" - ); - - this.statsReportSpan.span.addEvent(event.type, event.data); - this.statsReportSpan.stats.push(event); - } else if ( - this.statsReportSpan.span !== undefined && - this.callMembershipSpan - ) { - this.statsReportSpan.span.addEvent(event.type, event.data); - this.statsReportSpan.span.end(); - this.statsReportSpan = { span: undefined, stats: [] }; - } - } } interface OTelStatsReportEvent { @@ -428,7 +437,7 @@ interface OTelStatsReportEvent { } enum OTelStatsReportType { - ConnectionReport = "matrix.stats.connection", - ByteSentReport = "matrix.stats.byteSent", + ConnectionReport = "matrix.call.stats.connection", + ByteSentReport = "matrix.call.stats.byteSent", SummaryReport = "matrix.stats.summary", } diff --git a/src/otel/ObjectFlattener.ts b/src/otel/ObjectFlattener.ts index 321c45ee..652f9056 100644 --- a/src/otel/ObjectFlattener.ts +++ b/src/otel/ObjectFlattener.ts @@ -23,16 +23,12 @@ import { } from "matrix-js-sdk/src/webrtc/stats/statsReport"; export class ObjectFlattener { - public static flattenConnectionStatsReportObject( - statsReport: GroupCallStatsReport + public static flattenReportObject( + prefix: string, + report: ConnectionStatsReport | ByteSentStatsReport ): Attributes { const flatObject = {}; - ObjectFlattener.flattenObjectRecursive( - statsReport.report, - flatObject, - "matrix.stats.conn.", - 0 - ); + ObjectFlattener.flattenObjectRecursive(report, flatObject, `${prefix}.`, 0); return flatObject; } diff --git a/test/otel/ObjectFlattener-test.ts b/test/otel/ObjectFlattener-test.ts index e3709d20..b2fdce37 100644 --- a/test/otel/ObjectFlattener-test.ts +++ b/test/otel/ObjectFlattener-test.ts @@ -1,6 +1,7 @@ import { GroupCallStatsReport } from "matrix-js-sdk/src/webrtc/groupCall"; import { AudioConcealment, + ByteSentStatsReport, ConnectionStatsReport, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; import { ObjectFlattener } from "../../src/otel/ObjectFlattener"; @@ -28,6 +29,8 @@ describe("ObjectFlattener", () => { const statsReport: GroupCallStatsReport = { report: { + callId: "callId", + opponentMemberId: "opponentMemberId", bandwidth: { upload: 426, download: 0 }, bitrate: { upload: 426, @@ -116,21 +119,25 @@ describe("ObjectFlattener", () => { ObjectFlattener.flattenObjectRecursive( statsReport.report.resolution, flatObject, - "matrix.stats.conn.resolution.", + "matrix.call.stats.connection.resolution.", 0 ); expect(flatObject).toEqual({ - "matrix.stats.conn.resolution.local.LOCAL_AUDIO_TRACK_ID.height": -1, - "matrix.stats.conn.resolution.local.LOCAL_AUDIO_TRACK_ID.width": -1, + "matrix.call.stats.connection.resolution.local.LOCAL_AUDIO_TRACK_ID.height": + -1, + "matrix.call.stats.connection.resolution.local.LOCAL_AUDIO_TRACK_ID.width": + -1, - "matrix.stats.conn.resolution.local.LOCAL_VIDEO_TRACK_ID.height": 460, - "matrix.stats.conn.resolution.local.LOCAL_VIDEO_TRACK_ID.width": 780, + "matrix.call.stats.connection.resolution.local.LOCAL_VIDEO_TRACK_ID.height": 460, + "matrix.call.stats.connection.resolution.local.LOCAL_VIDEO_TRACK_ID.width": 780, - "matrix.stats.conn.resolution.remote.REMOTE_AUDIO_TRACK_ID.height": -1, - "matrix.stats.conn.resolution.remote.REMOTE_AUDIO_TRACK_ID.width": -1, + "matrix.call.stats.connection.resolution.remote.REMOTE_AUDIO_TRACK_ID.height": + -1, + "matrix.call.stats.connection.resolution.remote.REMOTE_AUDIO_TRACK_ID.width": + -1, - "matrix.stats.conn.resolution.remote.REMOTE_VIDEO_TRACK_ID.height": 960, - "matrix.stats.conn.resolution.remote.REMOTE_VIDEO_TRACK_ID.width": 1080, + "matrix.call.stats.connection.resolution.remote.REMOTE_VIDEO_TRACK_ID.height": 960, + "matrix.call.stats.connection.resolution.remote.REMOTE_VIDEO_TRACK_ID.width": 1080, }); }); it("should flatter an Array object", () => { @@ -138,106 +145,128 @@ describe("ObjectFlattener", () => { ObjectFlattener.flattenObjectRecursive( statsReport.report.transport, flatObject, - "matrix.stats.conn.transport.", + "matrix.call.stats.connection.transport.", 0 ); expect(flatObject).toEqual({ - "matrix.stats.conn.transport.0.ip": "ff11::5fa:abcd:999c:c5c5:50000", - "matrix.stats.conn.transport.0.type": "udp", - "matrix.stats.conn.transport.0.localIp": + "matrix.call.stats.connection.transport.0.ip": + "ff11::5fa:abcd:999c:c5c5:50000", + "matrix.call.stats.connection.transport.0.type": "udp", + "matrix.call.stats.connection.transport.0.localIp": "2aaa:9999:2aaa:999:8888:2aaa:2aaa:7777:50000", - "matrix.stats.conn.transport.0.isFocus": true, - "matrix.stats.conn.transport.0.localCandidateType": "host", - "matrix.stats.conn.transport.0.remoteCandidateType": "host", - "matrix.stats.conn.transport.0.networkType": "ethernet", - "matrix.stats.conn.transport.0.rtt": "NaN", - "matrix.stats.conn.transport.1.ip": "10.10.10.2:22222", - "matrix.stats.conn.transport.1.type": "tcp", - "matrix.stats.conn.transport.1.localIp": "10.10.10.100:33333", - "matrix.stats.conn.transport.1.isFocus": true, - "matrix.stats.conn.transport.1.localCandidateType": "srfx", - "matrix.stats.conn.transport.1.remoteCandidateType": "srfx", - "matrix.stats.conn.transport.1.networkType": "ethernet", - "matrix.stats.conn.transport.1.rtt": "null", + "matrix.call.stats.connection.transport.0.isFocus": true, + "matrix.call.stats.connection.transport.0.localCandidateType": "host", + "matrix.call.stats.connection.transport.0.remoteCandidateType": "host", + "matrix.call.stats.connection.transport.0.networkType": "ethernet", + "matrix.call.stats.connection.transport.0.rtt": "NaN", + "matrix.call.stats.connection.transport.1.ip": "10.10.10.2:22222", + "matrix.call.stats.connection.transport.1.type": "tcp", + "matrix.call.stats.connection.transport.1.localIp": + "10.10.10.100:33333", + "matrix.call.stats.connection.transport.1.isFocus": true, + "matrix.call.stats.connection.transport.1.localCandidateType": "srfx", + "matrix.call.stats.connection.transport.1.remoteCandidateType": "srfx", + "matrix.call.stats.connection.transport.1.networkType": "ethernet", + "matrix.call.stats.connection.transport.1.rtt": "null", }); }); }); - describe("on flattenConnectionStatsReportObject", () => { + describe("on flattenReportObject Connection Stats", () => { it("should flatten a Report to otel Attributes Object", () => { expect( - ObjectFlattener.flattenConnectionStatsReportObject(statsReport) + ObjectFlattener.flattenReportObject( + "matrix.call.stats.connection", + statsReport.report + ) ).toEqual({ - "matrix.stats.conn.bandwidth.download": 0, - "matrix.stats.conn.bandwidth.upload": 426, - "matrix.stats.conn.bitrate.audio.download": 0, - "matrix.stats.conn.bitrate.audio.upload": 124, - "matrix.stats.conn.bitrate.download": 0, - "matrix.stats.conn.bitrate.upload": 426, - "matrix.stats.conn.bitrate.video.download": 0, - "matrix.stats.conn.bitrate.video.upload": 302, - "matrix.stats.conn.codec.local.LOCAL_AUDIO_TRACK_ID": "opus", - "matrix.stats.conn.codec.local.LOCAL_VIDEO_TRACK_ID": "v8", - "matrix.stats.conn.codec.remote.REMOTE_AUDIO_TRACK_ID": "opus", - "matrix.stats.conn.codec.remote.REMOTE_VIDEO_TRACK_ID": "v9", - "matrix.stats.conn.framerate.local.LOCAL_AUDIO_TRACK_ID": 0, - "matrix.stats.conn.framerate.local.LOCAL_VIDEO_TRACK_ID": 30, - "matrix.stats.conn.framerate.remote.REMOTE_AUDIO_TRACK_ID": 0, - "matrix.stats.conn.framerate.remote.REMOTE_VIDEO_TRACK_ID": 60, - "matrix.stats.conn.jitter.REMOTE_AUDIO_TRACK_ID": 2, - "matrix.stats.conn.jitter.REMOTE_VIDEO_TRACK_ID": 50, - "matrix.stats.conn.packetLoss.download": 0, - "matrix.stats.conn.packetLoss.total": 0, - "matrix.stats.conn.packetLoss.upload": 0, - "matrix.stats.conn.resolution.local.LOCAL_AUDIO_TRACK_ID.height": -1, - "matrix.stats.conn.resolution.local.LOCAL_AUDIO_TRACK_ID.width": -1, - "matrix.stats.conn.resolution.local.LOCAL_VIDEO_TRACK_ID.height": 460, - "matrix.stats.conn.resolution.local.LOCAL_VIDEO_TRACK_ID.width": 780, - "matrix.stats.conn.resolution.remote.REMOTE_AUDIO_TRACK_ID.height": -1, - "matrix.stats.conn.resolution.remote.REMOTE_AUDIO_TRACK_ID.width": -1, - "matrix.stats.conn.resolution.remote.REMOTE_VIDEO_TRACK_ID.height": 960, - "matrix.stats.conn.resolution.remote.REMOTE_VIDEO_TRACK_ID.width": 1080, - "matrix.stats.conn.transport.0.ip": "ff11::5fa:abcd:999c:c5c5:50000", - "matrix.stats.conn.transport.0.type": "udp", - "matrix.stats.conn.transport.0.localIp": + "matrix.call.stats.connection.callId": "callId", + "matrix.call.stats.connection.opponentMemberId": "opponentMemberId", + "matrix.call.stats.connection.bandwidth.download": 0, + "matrix.call.stats.connection.bandwidth.upload": 426, + "matrix.call.stats.connection.bitrate.audio.download": 0, + "matrix.call.stats.connection.bitrate.audio.upload": 124, + "matrix.call.stats.connection.bitrate.download": 0, + "matrix.call.stats.connection.bitrate.upload": 426, + "matrix.call.stats.connection.bitrate.video.download": 0, + "matrix.call.stats.connection.bitrate.video.upload": 302, + "matrix.call.stats.connection.codec.local.LOCAL_AUDIO_TRACK_ID": "opus", + "matrix.call.stats.connection.codec.local.LOCAL_VIDEO_TRACK_ID": "v8", + "matrix.call.stats.connection.codec.remote.REMOTE_AUDIO_TRACK_ID": + "opus", + "matrix.call.stats.connection.codec.remote.REMOTE_VIDEO_TRACK_ID": "v9", + "matrix.call.stats.connection.framerate.local.LOCAL_AUDIO_TRACK_ID": 0, + "matrix.call.stats.connection.framerate.local.LOCAL_VIDEO_TRACK_ID": 30, + "matrix.call.stats.connection.framerate.remote.REMOTE_AUDIO_TRACK_ID": 0, + "matrix.call.stats.connection.framerate.remote.REMOTE_VIDEO_TRACK_ID": 60, + "matrix.call.stats.connection.jitter.REMOTE_AUDIO_TRACK_ID": 2, + "matrix.call.stats.connection.jitter.REMOTE_VIDEO_TRACK_ID": 50, + "matrix.call.stats.connection.packetLoss.download": 0, + "matrix.call.stats.connection.packetLoss.total": 0, + "matrix.call.stats.connection.packetLoss.upload": 0, + "matrix.call.stats.connection.resolution.local.LOCAL_AUDIO_TRACK_ID.height": + -1, + "matrix.call.stats.connection.resolution.local.LOCAL_AUDIO_TRACK_ID.width": + -1, + "matrix.call.stats.connection.resolution.local.LOCAL_VIDEO_TRACK_ID.height": 460, + "matrix.call.stats.connection.resolution.local.LOCAL_VIDEO_TRACK_ID.width": 780, + "matrix.call.stats.connection.resolution.remote.REMOTE_AUDIO_TRACK_ID.height": + -1, + "matrix.call.stats.connection.resolution.remote.REMOTE_AUDIO_TRACK_ID.width": + -1, + "matrix.call.stats.connection.resolution.remote.REMOTE_VIDEO_TRACK_ID.height": 960, + "matrix.call.stats.connection.resolution.remote.REMOTE_VIDEO_TRACK_ID.width": 1080, + "matrix.call.stats.connection.transport.0.ip": + "ff11::5fa:abcd:999c:c5c5:50000", + "matrix.call.stats.connection.transport.0.type": "udp", + "matrix.call.stats.connection.transport.0.localIp": "2aaa:9999:2aaa:999:8888:2aaa:2aaa:7777:50000", - "matrix.stats.conn.transport.0.isFocus": true, - "matrix.stats.conn.transport.0.localCandidateType": "host", - "matrix.stats.conn.transport.0.remoteCandidateType": "host", - "matrix.stats.conn.transport.0.networkType": "ethernet", - "matrix.stats.conn.transport.0.rtt": "NaN", - "matrix.stats.conn.transport.1.ip": "10.10.10.2:22222", - "matrix.stats.conn.transport.1.type": "tcp", - "matrix.stats.conn.transport.1.localIp": "10.10.10.100:33333", - "matrix.stats.conn.transport.1.isFocus": true, - "matrix.stats.conn.transport.1.localCandidateType": "srfx", - "matrix.stats.conn.transport.1.remoteCandidateType": "srfx", - "matrix.stats.conn.transport.1.networkType": "ethernet", - "matrix.stats.conn.transport.1.rtt": "null", - "matrix.stats.conn.audioConcealment.REMOTE_AUDIO_TRACK_ID.concealedAudio": 0, - "matrix.stats.conn.audioConcealment.REMOTE_AUDIO_TRACK_ID.totalAudioDuration": 0, - "matrix.stats.conn.audioConcealment.REMOTE_VIDEO_TRACK_ID.concealedAudio": 0, - "matrix.stats.conn.audioConcealment.REMOTE_VIDEO_TRACK_ID.totalAudioDuration": 0, - "matrix.stats.conn.totalAudioConcealment.concealedAudio": 0, - "matrix.stats.conn.totalAudioConcealment.totalAudioDuration": 0, + "matrix.call.stats.connection.transport.0.isFocus": true, + "matrix.call.stats.connection.transport.0.localCandidateType": "host", + "matrix.call.stats.connection.transport.0.remoteCandidateType": "host", + "matrix.call.stats.connection.transport.0.networkType": "ethernet", + "matrix.call.stats.connection.transport.0.rtt": "NaN", + "matrix.call.stats.connection.transport.1.ip": "10.10.10.2:22222", + "matrix.call.stats.connection.transport.1.type": "tcp", + "matrix.call.stats.connection.transport.1.localIp": + "10.10.10.100:33333", + "matrix.call.stats.connection.transport.1.isFocus": true, + "matrix.call.stats.connection.transport.1.localCandidateType": "srfx", + "matrix.call.stats.connection.transport.1.remoteCandidateType": "srfx", + "matrix.call.stats.connection.transport.1.networkType": "ethernet", + "matrix.call.stats.connection.transport.1.rtt": "null", + "matrix.call.stats.connection.audioConcealment.REMOTE_AUDIO_TRACK_ID.concealedAudio": 0, + "matrix.call.stats.connection.audioConcealment.REMOTE_AUDIO_TRACK_ID.totalAudioDuration": 0, + "matrix.call.stats.connection.audioConcealment.REMOTE_VIDEO_TRACK_ID.concealedAudio": 0, + "matrix.call.stats.connection.audioConcealment.REMOTE_VIDEO_TRACK_ID.totalAudioDuration": 0, + "matrix.call.stats.connection.totalAudioConcealment.concealedAudio": 0, + "matrix.call.stats.connection.totalAudioConcealment.totalAudioDuration": 0, }); }); }); describe("on flattenByteSendStatsReportObject", () => { - const byteSent = { - report: new Map([ - ["4aa92608-04c6-428e-8312-93e17602a959", 132093], - ["a08e4237-ee30-4015-a932-b676aec894b1", 913448], - ]), - }; + const byteSentStatsReport = new Map< + string, + number + >() as ByteSentStatsReport; + byteSentStatsReport.callId = "callId"; + byteSentStatsReport.opponentMemberId = "opponentMemberId"; + byteSentStatsReport.set("4aa92608-04c6-428e-8312-93e17602a959", 132093); + byteSentStatsReport.set("a08e4237-ee30-4015-a932-b676aec894b1", 913448); + it("should flatten a Report to otel Attributes Object", () => { expect( - ObjectFlattener.flattenByteSentStatsReportObject(byteSent) + ObjectFlattener.flattenReportObject( + "matrix.call.stats.bytesSend", + byteSentStatsReport + ) ).toEqual({ - "matrix.stats.bytesSent.4aa92608-04c6-428e-8312-93e17602a959": 132093, - "matrix.stats.bytesSent.a08e4237-ee30-4015-a932-b676aec894b1": 913448, + "matrix.call.stats.bytesSend.4aa92608-04c6-428e-8312-93e17602a959": 132093, + "matrix.call.stats.bytesSend.a08e4237-ee30-4015-a932-b676aec894b1": 913448, }); + expect(byteSentStatsReport.callId).toEqual("callId"); + expect(byteSentStatsReport.opponentMemberId).toEqual("opponentMemberId"); }); }); }); diff --git a/yarn.lock b/yarn.lock index 2e8e7b4c..c9c9a44e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10557,9 +10557,9 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#a7b1dcaf9514b2e424a387e266c6f383a5909927": +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#e70a1a1effe59e6754f9a10cc2df8eef81638c7d": version "25.1.1" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/a7b1dcaf9514b2e424a387e266c6f383a5909927" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/e70a1a1effe59e6754f9a10cc2df8eef81638c7d" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.9" From 66a582dd5f9a5a4e1a34965d312243cbd3c9adb7 Mon Sep 17 00:00:00 2001 From: Linerly Date: Mon, 5 Jun 2023 06:21:30 +0000 Subject: [PATCH 09/28] Translated using Weblate (Indonesian) Currently translated at 100.0% (139 of 139 strings) Translation: Element Call/element-call Translate-URL: http://translate.element.io/projects/element-call/element-call/id/ --- public/locales/id/app.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/public/locales/id/app.json b/public/locales/id/app.json index 6d6ba5d8..85b141d0 100644 --- a/public/locales/id/app.json +++ b/public/locales/id/app.json @@ -131,5 +131,11 @@ "Expose developer settings in the settings window.": "Ekspos pengaturan pengembang dalam jendela pengaturan.", "Developer Settings": "Pengaturan Pengembang", "By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy and our <5>Cookie Policy.": "Dengan bergabung dalam beta ini, Anda mengizinkan kami untuk mengumpulkan data anonim, yang kami gunakan untuk meningkatkan produk ini. Anda dapat mempelajari lebih lanjut tentang data apa yang kami lacak dalam <2>Kebijakan Privasi dan <5>Kebijakan Kuki kami.", - "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0><1>Anda dapat mengurungkan kembali izin dengan mencentang kotak ini. Jika Anda saat ini dalam panggilan, pengaturan ini akan diterapkan di akhir panggilan." + "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0><1>Anda dapat mengurungkan kembali izin dengan mencentang kotak ini. Jika Anda saat ini dalam panggilan, pengaturan ini akan diterapkan di akhir panggilan.", + "Feedback": "Masukan", + "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "Jika Anda mengalami masalah atau hanya ingin memberikan masukan, silakan kirimkan kami deskripsi pendek di bawah.", + "Submit": "Kirim", + "Submitting…": "Mengirim", + "Thanks, we received your feedback!": "Terima kasih, kami telah menerima masukan Anda!", + "Your feedback": "Masukan Anda" } From cc2402e61c7dd9cd47baede2b62b289506ae29f7 Mon Sep 17 00:00:00 2001 From: Someone Date: Mon, 5 Jun 2023 04:21:23 +0000 Subject: [PATCH 10/28] Translated using Weblate (Vietnamese) Currently translated at 25.8% (36 of 139 strings) Translation: Element Call/element-call Translate-URL: http://translate.element.io/projects/element-call/element-call/vi/ --- public/locales/vi/app.json | 39 +++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/public/locales/vi/app.json b/public/locales/vi/app.json index 0967ef42..81961343 100644 --- a/public/locales/vi/app.json +++ b/public/locales/vi/app.json @@ -1 +1,38 @@ -{} +{ + "Login": "Đăng nhập", + "{{count}} people connected|other": "{{count}} người đã kết nối", + "{{name}} (Waiting for video...)": "{{name}} (Đang đợi truyền hình...)", + "Join call": "Tham gia cuộc gọi", + "Mute microphone": "Tắt micrô", + "Password": "Mật khẩu", + "Settings": "Cài đặt", + "Sending…": "Đang gửi…", + "Sign in": "Đăng nhập", + "Submit": "Gửi", + "Video call name": "Tên cuộc gọi truyền hình", + "Video call": "Gọi truyền hình", + "Video": "Truyền hình", + "Username": "Tên người dùng", + "Yes, join call": "Vâng, tham gia cuộc gọi", + "Your feedback": "Phản hồi của bạn", + "{{count}} people connected|one": "{{count}} người đã kết nối", + "{{displayName}}, your call is now ended": "{{displayName}}, cuộc gọi của bạn đã kết thúc", + "{{name}} (Connecting...)": "{{name}} (Đang kết nối...)", + "Your recent calls": "Cuộc gọi gần đây", + "You can't talk at the same time": "Bạn không thể nói cùng thời điểm", + "WebRTC is not supported or is being blocked in this browser.": "WebRTC không được hỗ trợ hay bị chặn trong trình duyệt này.", + "Waiting for network": "Đang đợi kết nối mạng", + "Waiting for other participants…": "Đang đợi những người khác…", + "Version: {{version}}": "Phiên bản: {{version}}", + "Turn on camera": "Bật máy quay", + "Turn off camera": "Tắt máy quay", + "Submit feedback": "Gửi phản hồi", + "Stop sharing screen": "Ngừng chia sẻ màn hình", + "Speaker": "Loa", + "Sign out": "Đăng xuất", + "Share screen": "Chia sẻ màn hình", + "No": "Không", + "Invite people": "Mời mọi người", + "Join call now": "Tham gia cuộc gọi", + "Create account": "Tạo tài khoản" +} From eff8847586fbe617f6a1b4d30033aaf2f22cb675 Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Wed, 7 Jun 2023 15:59:42 +0200 Subject: [PATCH 11/28] add splitbrain params to MediaReceived event (#1089) Signed-off-by: Timo K --- src/analytics/PosthogSpanProcessor.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/analytics/PosthogSpanProcessor.ts b/src/analytics/PosthogSpanProcessor.ts index 1edb5a2f..effa6436 100644 --- a/src/analytics/PosthogSpanProcessor.ts +++ b/src/analytics/PosthogSpanProcessor.ts @@ -126,6 +126,11 @@ export class PosthogSpanProcessor implements SpanProcessor { const maxPacketLoss = `${attributes["matrix.stats.summary.maxPacketLoss"]}`; const peerConnections = `${attributes["matrix.stats.summary.peerConnections"]}`; const percentageConcealedAudio = `${attributes["matrix.stats.summary.percentageConcealedAudio"]}`; + const opponentUsersInCall = `${attributes["matrix.stats.summary.opponentUsersInCall"]}`; + const opponentDevicesInCall = `${attributes["matrix.stats.summary.opponentDevicesInCall"]}`; + const diffDevicesToPeerConnections = `${attributes["matrix.stats.summary.diffDevicesToPeerConnections"]}`; + const ratioPeerConnectionToDevices = `${attributes["matrix.stats.summary.ratioPeerConnectionToDevices"]}`; + PosthogAnalytics.instance.trackEvent( { eventName: "MediaReceived", @@ -137,6 +142,10 @@ export class PosthogSpanProcessor implements SpanProcessor { maxPacketLoss: maxPacketLoss, peerConnections: peerConnections, percentageConcealedAudio: percentageConcealedAudio, + opponentUsersInCall: opponentUsersInCall, + opponentDevicesInCall: opponentDevicesInCall, + diffDevicesToPeerConnections: diffDevicesToPeerConnections, + ratioPeerConnectionToDevices: ratioPeerConnectionToDevices, }, // Send instantly because the window might be closing { send_instantly: true } From 2a6981c58dd57183cc878145b44ea1b2e7b0416c Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Wed, 7 Jun 2023 16:22:44 +0200 Subject: [PATCH 12/28] Add quality survey at the end of the call (#1084) Signed-off-by: Timo K Co-authored-by: Robin --- public/locales/en-GB/app.json | 7 +- src/analytics/PosthogAnalytics.ts | 2 + src/analytics/PosthogEvents.ts | 18 ++++ src/icons/StarSelected.svg | 3 + src/icons/StarUnselected.svg | 4 + src/input/FeedbackInput.module.css | 23 ++++ src/input/StarRatingInput.module.css | 41 ++++++++ src/input/StarRatingInput.tsx | 85 +++++++++++++++ src/room/CallEndedView.module.css | 15 ++- src/room/CallEndedView.tsx | 151 ++++++++++++++++++++++----- src/room/GroupCallView.tsx | 16 ++- src/settings/FeedbackSettingsTab.tsx | 3 + 12 files changed, 338 insertions(+), 30 deletions(-) create mode 100644 src/icons/StarSelected.svg create mode 100644 src/icons/StarUnselected.svg create mode 100644 src/input/FeedbackInput.module.css create mode 100644 src/input/StarRatingInput.module.css create mode 100644 src/input/StarRatingInput.tsx diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index ccd111b8..987402a0 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -1,7 +1,9 @@ { "{{count}} people connected|one": "{{count}} person connected", "{{count}} people connected|other": "{{count}} people connected", - "{{displayName}}, your call is now ended": "{{displayName}}, your call is now ended", + "{{count}} stars|one": "{{count}} star", + "{{count}} stars|other": "{{count}} stars", + "{{displayName}}, your call has ended.": "{{displayName}}, your call has ended.", "{{name}} (Connecting...)": "{{name}} (Connecting...)", "{{name}} (Waiting for video...)": "{{name}} (Waiting for video...)", "{{name}} is presenting": "{{name}} is presenting", @@ -14,6 +16,8 @@ "<0>Join call now<1>Or<2>Copy call link and join later": "<0>Join call now<1>Or<2>Copy call link and join later", "<0>Oops, something's gone wrong.": "<0>Oops, something's gone wrong.", "<0>Submitting debug logs will help us track down the problem.": "<0>Submitting debug logs will help us track down the problem.", + "<0>Thanks for your feedback!": "<0>Thanks for your feedback!", + "<0>We'd love to hear your feedback so we can improve your experience.": "<0>We'd love to hear your feedback so we can improve your experience.", "<0>Why not finish by setting up a password to keep your account?<1>You'll be able to keep your name and set an avatar for use on future calls": "<0>Why not finish by setting up a password to keep your account?<1>You'll be able to keep your name and set an avatar for use on future calls", "Accept camera/microphone permissions to join the call.": "Accept camera/microphone permissions to join the call.", "Accept microphone permissions to join the call.": "Accept microphone permissions to join the call.", @@ -53,6 +57,7 @@ "Go": "Go", "Grid layout menu": "Grid layout menu", "Home": "Home", + "How did it go?": "How did it go?", "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.", "Include debug logs": "Include debug logs", "Incompatible versions": "Incompatible versions", diff --git a/src/analytics/PosthogAnalytics.ts b/src/analytics/PosthogAnalytics.ts index ed8ada35..fad315b1 100644 --- a/src/analytics/PosthogAnalytics.ts +++ b/src/analytics/PosthogAnalytics.ts @@ -29,6 +29,7 @@ import { MuteCameraTracker, MuteMicrophoneTracker, UndecryptableToDeviceEventTracker, + QualitySurveyEventTracker, } from "./PosthogEvents"; import { Config } from "../config/Config"; import { getUrlParams } from "../UrlParams"; @@ -431,4 +432,5 @@ export class PosthogAnalytics { public eventMuteMicrophone = new MuteMicrophoneTracker(); public eventMuteCamera = new MuteCameraTracker(); public eventUndecryptableToDevice = new UndecryptableToDeviceEventTracker(); + public eventQualitySurvey = new QualitySurveyEventTracker(); } diff --git a/src/analytics/PosthogEvents.ts b/src/analytics/PosthogEvents.ts index aa8aa329..f2fecb4e 100644 --- a/src/analytics/PosthogEvents.ts +++ b/src/analytics/PosthogEvents.ts @@ -163,3 +163,21 @@ export class UndecryptableToDeviceEventTracker { }); } } + +interface QualitySurveyEvent { + eventName: "QualitySurvey"; + callId: string; + feedbackText: string; + stars: number; +} + +export class QualitySurveyEventTracker { + track(callId: string, feedbackText: string, stars: number) { + PosthogAnalytics.instance.trackEvent({ + eventName: "QualitySurvey", + callId, + feedbackText, + stars, + }); + } +} diff --git a/src/icons/StarSelected.svg b/src/icons/StarSelected.svg new file mode 100644 index 00000000..69a8ce80 --- /dev/null +++ b/src/icons/StarSelected.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/icons/StarUnselected.svg b/src/icons/StarUnselected.svg new file mode 100644 index 00000000..be281947 --- /dev/null +++ b/src/icons/StarUnselected.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/input/FeedbackInput.module.css b/src/input/FeedbackInput.module.css new file mode 100644 index 00000000..75647939 --- /dev/null +++ b/src/input/FeedbackInput.module.css @@ -0,0 +1,23 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.feedback textarea { + height: 75px; + border-radius: 8px; +} +.feedback { + border-radius: 8px; +} diff --git a/src/input/StarRatingInput.module.css b/src/input/StarRatingInput.module.css new file mode 100644 index 00000000..08a65d11 --- /dev/null +++ b/src/input/StarRatingInput.module.css @@ -0,0 +1,41 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.starIcon { + cursor: pointer; +} + +.starRating { + display: flex; + justify-content: center; + flex: 1; +} + +.inputContainer { + display: inline-block; +} + +.hideElement { + border: 0; + clip-path: content-box; + height: 0px; + width: 0px; + margin: -1px; + overflow: hidden; + padding: 0; + width: 1px; + display: inline-block; +} diff --git a/src/input/StarRatingInput.tsx b/src/input/StarRatingInput.tsx new file mode 100644 index 00000000..34f01202 --- /dev/null +++ b/src/input/StarRatingInput.tsx @@ -0,0 +1,85 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; + +import styles from "./StarRatingInput.module.css"; +import { ReactComponent as StarSelected } from "../icons/StarSelected.svg"; +import { ReactComponent as StarUnselected } from "../icons/StarUnselected.svg"; + +interface Props { + starCount: number; + onChange: (stars: number) => void; + required?: boolean; +} + +export function StarRatingInput({ + starCount, + onChange, + required, +}: Props): JSX.Element { + const [rating, setRating] = useState(0); + const [hover, setHover] = useState(0); + const { t } = useTranslation(); + return ( +
+ {[...Array(starCount)].map((_star, index) => { + index += 1; + return ( +
setHover(index)} + onMouseLeave={() => setHover(rating)} + key={index} + > + { + setRating(index); + onChange(index); + }} + required + /> + + +
+ ); + })} +
+ ); +} diff --git a/src/room/CallEndedView.module.css b/src/room/CallEndedView.module.css index dcf11f04..fd9bad99 100644 --- a/src/room/CallEndedView.module.css +++ b/src/room/CallEndedView.module.css @@ -17,20 +17,31 @@ limitations under the License. .headline { text-align: center; margin-bottom: 60px; + white-space: pre; } .callEndedContent { text-align: center; - max-width: 360px; + max-width: 450px; +} +.callEndedContent p { + font-size: var(--font-size-subtitle); } - .callEndedContent h3 { margin-bottom: 32px; } .callEndedButton { + margin-top: 54px; + margin-left: 30px; + margin-right: 30px !important; +} + +.submitButton { width: 100%; margin-top: 54px; + margin-left: 30px; + margin-right: 30px !important; } .container { diff --git a/src/room/CallEndedView.tsx b/src/room/CallEndedView.tsx index 7543d669..e36d06c3 100644 --- a/src/room/CallEndedView.tsx +++ b/src/room/CallEndedView.tsx @@ -14,19 +14,130 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { FormEventHandler, useCallback, useState } from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { Trans, useTranslation } from "react-i18next"; +import { useHistory } from "react-router-dom"; import styles from "./CallEndedView.module.css"; -import { LinkButton } from "../button"; +import feedbackStyle from "../input/FeedbackInput.module.css"; +import { Button, LinkButton } from "../button"; import { useProfile } from "../profile/useProfile"; -import { Subtitle, Body, Link, Headline } from "../typography/Typography"; +import { Body, Link, Headline } from "../typography/Typography"; import { Header, HeaderLogo, LeftNav, RightNav } from "../Header"; +import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; +import { FieldRow, InputField } from "../input/Input"; +import { StarRatingInput } from "../input/StarRatingInput"; -export function CallEndedView({ client }: { client: MatrixClient }) { +export function CallEndedView({ + client, + isPasswordlessUser, + endedCallId, +}: { + client: MatrixClient; + isPasswordlessUser: boolean; + endedCallId: string; +}) { const { t } = useTranslation(); + const history = useHistory(); + const { displayName } = useProfile(client); + const [surveySubmitted, setSurverySubmitted] = useState(false); + const [starRating, setStarRating] = useState(0); + const [submitting, setSubmitting] = useState(false); + const [submitDone, setSubmitDone] = useState(false); + const submitSurvery: FormEventHandler = useCallback( + (e) => { + e.preventDefault(); + const data = new FormData(e.target as HTMLFormElement); + const feedbackText = data.get("feedbackText") as string; + + PosthogAnalytics.instance.eventQualitySurvey.track( + endedCallId, + feedbackText, + starRating + ); + + setSubmitting(true); + + setTimeout(() => { + setSubmitDone(true); + + setTimeout(() => { + if (isPasswordlessUser) { + // setting this renders the callEndedView with the invitation to create an account + setSurverySubmitted(true); + } else { + // if the user already has an account immediately go back to the home screen + history.push("/"); + } + }, 1000); + }, 1000); + }, + [endedCallId, history, isPasswordlessUser, starRating] + ); + const createAccountDialog = isPasswordlessUser && ( +
+ +

Why not finish by setting up a password to keep your account?

+

+ You'll be able to keep your name and set an avatar for use on future + calls +

+
+ + {t("Create account")} + +
+ ); + + const qualitySurveyDialog = ( +
+ +

+ We'd love to hear your feedback so we can improve your experience. +

+
+
+ + + + + + {" "} + + {submitDone ? ( + +

Thanks for your feedback!

+
+ ) : ( + + )} +
+
+
+ ); return ( <> @@ -39,27 +150,19 @@ export function CallEndedView({ client }: { client: MatrixClient }) {
- {t("{{displayName}}, your call is now ended", { displayName })} + {surveySubmitted + ? t("{{displayName}}, your call has ended.", { + displayName, + }) + : t("{{displayName}}, your call has ended.", { + displayName, + }) + + "\n" + + t("How did it go?")} -
- - - Why not finish by setting up a password to keep your account? - - - You'll be able to keep your name and set an avatar for use on - future calls - - - - {t("Create account")} - -
+ {!surveySubmitted && PosthogAnalytics.instance.isEnabled() + ? qualitySurveyDialog + : createAccountDialog}
diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 722686ca..595bc3fd 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -203,7 +203,11 @@ export function GroupCallView({ widget.api.transport.send(ElementWidgetActions.HangupCall, {}); } - if (!isPasswordlessUser && !isEmbedded) { + if ( + !isPasswordlessUser && + !isEmbedded && + !PosthogAnalytics.instance.isEnabled() + ) { history.push("/"); } }, [groupCall, leave, isPasswordlessUser, isEmbedded, history]); @@ -268,8 +272,14 @@ export function GroupCallView({ ); } } else if (left) { - if (isPasswordlessUser) { - return ; + if (isPasswordlessUser || PosthogAnalytics.instance.isEnabled()) { + return ( + + ); } else { // If the user is a regular user, we'll have sent them back to the homepage, // so just sit here & do nothing: otherwise we would (briefly) mount the diff --git a/src/settings/FeedbackSettingsTab.tsx b/src/settings/FeedbackSettingsTab.tsx index da40678f..b57f0f78 100644 --- a/src/settings/FeedbackSettingsTab.tsx +++ b/src/settings/FeedbackSettingsTab.tsx @@ -23,6 +23,7 @@ import { FieldRow, InputField, ErrorMessage } from "../input/Input"; import { useSubmitRageshake, useRageshakeRequest } from "./submit-rageshake"; import { Body } from "../typography/Typography"; import styles from "../input/SelectInput.module.css"; +import feedbackStyles from "../input/FeedbackInput.module.css"; interface Props { roomId?: string; @@ -68,9 +69,11 @@ export function FeedbackSettingsTab({ roomId }: Props) {
From 8f8dd5f803d247451520aea6e58dc501c45ead35 Mon Sep 17 00:00:00 2001 From: Enrico Schwendig Date: Wed, 7 Jun 2023 16:40:47 +0200 Subject: [PATCH 13/28] Display active tracks in OTel metrics (#1085) * Add track, feed and transceiver spans under call span --- package.json | 2 +- src/otel/OTelCall.ts | 78 +++++++++++++++++++ src/otel/OTelCallAbstractMediaStreamSpan.ts | 62 +++++++++++++++ src/otel/OTelCallFeedMediaStreamSpan.ts | 57 ++++++++++++++ src/otel/OTelCallMediaStreamTrackSpan.ts | 62 +++++++++++++++ .../OTelCallTransceiverMediaStreamSpan.ts | 54 +++++++++++++ src/otel/OTelGroupCallMembership.ts | 33 +++++++- src/room/useGroupCall.ts | 16 ++++ yarn.lock | 6 +- 9 files changed, 365 insertions(+), 5 deletions(-) create mode 100644 src/otel/OTelCallAbstractMediaStreamSpan.ts create mode 100644 src/otel/OTelCallFeedMediaStreamSpan.ts create mode 100644 src/otel/OTelCallMediaStreamTrackSpan.ts create mode 100644 src/otel/OTelCallTransceiverMediaStreamSpan.ts diff --git a/package.json b/package.json index 0d5325b5..9923b6b3 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "i18next-browser-languagedetector": "^6.1.8", "i18next-http-backend": "^1.4.4", "lodash": "^4.17.21", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#e70a1a1effe59e6754f9a10cc2df8eef81638c7d", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#3cfad3cdeb7b19b8e0e7015784efd803cb9542f1", "matrix-widget-api": "^1.3.1", "mermaid": "^8.13.8", "normalize.css": "^8.0.1", diff --git a/src/otel/OTelCall.ts b/src/otel/OTelCall.ts index 79cc38d5..eae1f347 100644 --- a/src/otel/OTelCall.ts +++ b/src/otel/OTelCall.ts @@ -17,13 +17,33 @@ limitations under the License. import { Span } from "@opentelemetry/api"; import { MatrixCall } from "matrix-js-sdk"; import { CallEvent } from "matrix-js-sdk/src/webrtc/call"; +import { + TransceiverStats, + CallFeedStats, +} from "matrix-js-sdk/src/webrtc/stats/statsReport"; import { ObjectFlattener } from "./ObjectFlattener"; +import { ElementCallOpenTelemetry } from "./otel"; +import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan"; +import { OTelCallTransceiverMediaStreamSpan } from "./OTelCallTransceiverMediaStreamSpan"; +import { OTelCallFeedMediaStreamSpan } from "./OTelCallFeedMediaStreamSpan"; + +type StreamId = string; +type MID = string; /** * Tracks an individual call within a group call, either to a full-mesh peer or a focus */ export class OTelCall { + private readonly trackFeedSpan = new Map< + StreamId, + OTelCallAbstractMediaStreamSpan + >(); + private readonly trackTransceiverSpan = new Map< + MID, + OTelCallAbstractMediaStreamSpan + >(); + constructor( public userId: string, public deviceId: string, @@ -116,4 +136,62 @@ export class OTelCall { this.span.addEvent("matrix.call.iceCandidateError", flatObject); }; + + public onCallFeedStats(callFeeds: CallFeedStats[]): void { + let prvFeeds: StreamId[] = [...this.trackFeedSpan.keys()]; + + callFeeds.forEach((feed) => { + if (!this.trackFeedSpan.has(feed.stream)) { + this.trackFeedSpan.set( + feed.stream, + new OTelCallFeedMediaStreamSpan( + ElementCallOpenTelemetry.instance, + this.span, + feed + ) + ); + } + this.trackFeedSpan.get(feed.stream)?.update(feed); + prvFeeds = prvFeeds.filter((prvStreamId) => prvStreamId !== feed.stream); + }); + + prvFeeds.forEach((prvStreamId) => { + this.trackFeedSpan.get(prvStreamId)?.end(); + this.trackFeedSpan.delete(prvStreamId); + }); + } + + public onTransceiverStats(transceiverStats: TransceiverStats[]): void { + let prvTransSpan: MID[] = [...this.trackTransceiverSpan.keys()]; + + transceiverStats.forEach((transStats) => { + if (!this.trackTransceiverSpan.has(transStats.mid)) { + this.trackTransceiverSpan.set( + transStats.mid, + new OTelCallTransceiverMediaStreamSpan( + ElementCallOpenTelemetry.instance, + this.span, + transStats + ) + ); + } + this.trackTransceiverSpan.get(transStats.mid)?.update(transStats); + prvTransSpan = prvTransSpan.filter( + (prvStreamId) => prvStreamId !== transStats.mid + ); + }); + + prvTransSpan.forEach((prvMID) => { + this.trackTransceiverSpan.get(prvMID)?.end(); + this.trackTransceiverSpan.delete(prvMID); + }); + } + + public end(): void { + this.trackFeedSpan.forEach((feedSpan) => feedSpan.end()); + this.trackTransceiverSpan.forEach((transceiverSpan) => + transceiverSpan.end() + ); + this.span.end(); + } } diff --git a/src/otel/OTelCallAbstractMediaStreamSpan.ts b/src/otel/OTelCallAbstractMediaStreamSpan.ts new file mode 100644 index 00000000..aa77051d --- /dev/null +++ b/src/otel/OTelCallAbstractMediaStreamSpan.ts @@ -0,0 +1,62 @@ +import opentelemetry, { Span } from "@opentelemetry/api"; +import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport"; + +import { ElementCallOpenTelemetry } from "./otel"; +import { OTelCallMediaStreamTrackSpan } from "./OTelCallMediaStreamTrackSpan"; + +type TrackId = string; + +export abstract class OTelCallAbstractMediaStreamSpan { + protected readonly trackSpans = new Map< + TrackId, + OTelCallMediaStreamTrackSpan + >(); + public readonly span; + + public constructor( + readonly oTel: ElementCallOpenTelemetry, + readonly callSpan: Span, + protected readonly type: string + ) { + const ctx = opentelemetry.trace.setSpan( + opentelemetry.context.active(), + callSpan + ); + const options = { + links: [ + { + context: callSpan.spanContext(), + }, + ], + }; + this.span = oTel.tracer.startSpan(this.type, options, ctx); + } + + protected upsertTrackSpans(tracks: TrackStats[]) { + let prvTracks: TrackId[] = [...this.trackSpans.keys()]; + tracks.forEach((t) => { + if (!this.trackSpans.has(t.id)) { + this.trackSpans.set( + t.id, + new OTelCallMediaStreamTrackSpan(this.oTel, this.span, t) + ); + } + this.trackSpans.get(t.id)?.update(t); + prvTracks = prvTracks.filter((prvTrackId) => prvTrackId !== t.id); + }); + + prvTracks.forEach((prvTrackId) => { + this.trackSpans.get(prvTrackId)?.end(); + this.trackSpans.delete(prvTrackId); + }); + } + + public abstract update(data: Object): void; + + public end(): void { + this.trackSpans.forEach((tSpan) => { + tSpan.end(); + }); + this.span.end(); + } +} diff --git a/src/otel/OTelCallFeedMediaStreamSpan.ts b/src/otel/OTelCallFeedMediaStreamSpan.ts new file mode 100644 index 00000000..6023fa65 --- /dev/null +++ b/src/otel/OTelCallFeedMediaStreamSpan.ts @@ -0,0 +1,57 @@ +import { Span } from "@opentelemetry/api"; +import { + CallFeedStats, + TrackStats, +} from "matrix-js-sdk/src/webrtc/stats/statsReport"; + +import { ElementCallOpenTelemetry } from "./otel"; +import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan"; + +export class OTelCallFeedMediaStreamSpan extends OTelCallAbstractMediaStreamSpan { + private readonly prev: { isAudioMuted: boolean; isVideoMuted: boolean }; + + constructor( + readonly oTel: ElementCallOpenTelemetry, + readonly callSpan: Span, + callFeed: CallFeedStats + ) { + const postFix = + callFeed.type === "local" && callFeed.prefix === "from-call-feed" + ? "(clone)" + : ""; + super(oTel, callSpan, `matrix.call.feed.${callFeed.type}${postFix}`); + this.span.setAttribute("feed.streamId", callFeed.stream); + this.span.setAttribute("feed.type", callFeed.type); + this.span.setAttribute("feed.readFrom", callFeed.prefix); + this.span.setAttribute("feed.purpose", callFeed.purpose); + this.prev = { + isAudioMuted: callFeed.isAudioMuted, + isVideoMuted: callFeed.isVideoMuted, + }; + this.span.addEvent("matrix.call.feed.initState", this.prev); + } + + public update(callFeed: CallFeedStats): void { + if (this.prev.isAudioMuted !== callFeed.isAudioMuted) { + this.span.addEvent("matrix.call.feed.audioMuted", { + isAudioMuted: callFeed.isAudioMuted, + }); + this.prev.isAudioMuted = callFeed.isAudioMuted; + } + if (this.prev.isVideoMuted !== callFeed.isVideoMuted) { + this.span.addEvent("matrix.call.feed.isVideoMuted", { + isVideoMuted: callFeed.isVideoMuted, + }); + this.prev.isVideoMuted = callFeed.isVideoMuted; + } + + const trackStats: TrackStats[] = []; + if (callFeed.video) { + trackStats.push(callFeed.video); + } + if (callFeed.audio) { + trackStats.push(callFeed.audio); + } + this.upsertTrackSpans(trackStats); + } +} diff --git a/src/otel/OTelCallMediaStreamTrackSpan.ts b/src/otel/OTelCallMediaStreamTrackSpan.ts new file mode 100644 index 00000000..935e22fc --- /dev/null +++ b/src/otel/OTelCallMediaStreamTrackSpan.ts @@ -0,0 +1,62 @@ +import { TrackStats } from "matrix-js-sdk/src/webrtc/stats/statsReport"; +import opentelemetry, { Span } from "@opentelemetry/api"; + +import { ElementCallOpenTelemetry } from "./otel"; + +export class OTelCallMediaStreamTrackSpan { + private readonly span: Span; + private prev: TrackStats; + + public constructor( + readonly oTel: ElementCallOpenTelemetry, + readonly streamSpan: Span, + data: TrackStats + ) { + const ctx = opentelemetry.trace.setSpan( + opentelemetry.context.active(), + streamSpan + ); + const options = { + links: [ + { + context: streamSpan.spanContext(), + }, + ], + }; + const type = `matrix.call.track.${data.label}.${data.kind}`; + this.span = oTel.tracer.startSpan(type, options, ctx); + this.span.setAttribute("track.trackId", data.id); + this.span.setAttribute("track.kind", data.kind); + this.span.setAttribute("track.constrainDeviceId", data.constrainDeviceId); + this.span.setAttribute("track.settingDeviceId", data.settingDeviceId); + this.span.setAttribute("track.label", data.label); + + this.span.addEvent("matrix.call.track.initState", { + readyState: data.readyState, + muted: data.muted, + enabled: data.enabled, + }); + this.prev = data; + } + + public update(data: TrackStats): void { + if (this.prev.muted !== data.muted) { + this.span.addEvent("matrix.call.track.muted", { muted: data.muted }); + } + if (this.prev.enabled !== data.enabled) { + this.span.addEvent("matrix.call.track.enabled", { + enabled: data.enabled, + }); + } + if (this.prev.readyState !== data.readyState) { + this.span.addEvent("matrix.call.track.readyState", { + readyState: data.readyState, + }); + } + this.prev = data; + } + + public end(): void { + this.span.end(); + } +} diff --git a/src/otel/OTelCallTransceiverMediaStreamSpan.ts b/src/otel/OTelCallTransceiverMediaStreamSpan.ts new file mode 100644 index 00000000..97006cd8 --- /dev/null +++ b/src/otel/OTelCallTransceiverMediaStreamSpan.ts @@ -0,0 +1,54 @@ +import { Span } from "@opentelemetry/api"; +import { + TrackStats, + TransceiverStats, +} from "matrix-js-sdk/src/webrtc/stats/statsReport"; + +import { ElementCallOpenTelemetry } from "./otel"; +import { OTelCallAbstractMediaStreamSpan } from "./OTelCallAbstractMediaStreamSpan"; + +export class OTelCallTransceiverMediaStreamSpan extends OTelCallAbstractMediaStreamSpan { + private readonly prev: { + direction: string; + currentDirection: string; + }; + + constructor( + readonly oTel: ElementCallOpenTelemetry, + readonly callSpan: Span, + stats: TransceiverStats + ) { + super(oTel, callSpan, `matrix.call.transceiver.${stats.mid}`); + this.span.setAttribute("transceiver.mid", stats.mid); + + this.prev = { + direction: stats.direction, + currentDirection: stats.currentDirection, + }; + this.span.addEvent("matrix.call.transceiver.initState", this.prev); + } + + public update(stats: TransceiverStats): void { + if (this.prev.currentDirection !== stats.currentDirection) { + this.span.addEvent("matrix.call.transceiver.currentDirection", { + currentDirection: stats.currentDirection, + }); + this.prev.currentDirection = stats.currentDirection; + } + if (this.prev.direction !== stats.direction) { + this.span.addEvent("matrix.call.transceiver.direction", { + direction: stats.direction, + }); + this.prev.direction = stats.direction; + } + + const trackStats: TrackStats[] = []; + if (stats.sender) { + trackStats.push(stats.sender); + } + if (stats.receiver) { + trackStats.push(stats.receiver); + } + this.upsertTrackSpans(trackStats); + } +} diff --git a/src/otel/OTelGroupCallMembership.ts b/src/otel/OTelGroupCallMembership.ts index a8d93b6a..fa1e164e 100644 --- a/src/otel/OTelGroupCallMembership.ts +++ b/src/otel/OTelGroupCallMembership.ts @@ -38,6 +38,7 @@ import { ConnectionStatsReport, ByteSentStatsReport, SummaryStatsReport, + CallFeedReport, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; import { setSpan } from "@opentelemetry/api/build/esm/trace/context-utils"; @@ -174,7 +175,7 @@ export class OTelGroupCallMembership { userCalls.get(callTrackingInfo.deviceId).callId !== callTrackingInfo.call.callId ) { - callTrackingInfo.span.end(); + callTrackingInfo.end(); this.callsByCallId.delete(callTrackingInfo.call.callId); } } @@ -330,6 +331,35 @@ export class OTelGroupCallMembership { }); } + public onCallFeedStatsReport(report: GroupCallStatsReport) { + if (!ElementCallOpenTelemetry.instance) return; + let call: OTelCall | undefined; + const callId = report.report?.callId; + + if (callId) { + call = this.callsByCallId.get(callId); + } + + if (!call) { + this.callMembershipSpan?.addEvent( + OTelStatsReportType.CallFeedReport + "_unknown_callId", + { + "call.callId": callId, + "call.opponentMemberId": report.report?.opponentMemberId + ? report.report?.opponentMemberId + : "unknown", + } + ); + logger.error( + `Received ${OTelStatsReportType.CallFeedReport} with unknown call ID: ${callId}` + ); + return; + } else { + call.onCallFeedStats(report.report.callFeeds); + call.onTransceiverStats(report.report.transceiver); + } + } + public onConnectionStatsReport( statsReport: GroupCallStatsReport ) { @@ -440,4 +470,5 @@ enum OTelStatsReportType { ConnectionReport = "matrix.call.stats.connection", ByteSentReport = "matrix.call.stats.byteSent", SummaryReport = "matrix.stats.summary", + CallFeedReport = "matrix.stats.call_feed", } diff --git a/src/room/useGroupCall.ts b/src/room/useGroupCall.ts index 0126e1cc..153bb186 100644 --- a/src/room/useGroupCall.ts +++ b/src/room/useGroupCall.ts @@ -34,6 +34,7 @@ import { ByteSentStatsReport, ConnectionStatsReport, SummaryStatsReport, + CallFeedReport, } from "matrix-js-sdk/src/webrtc/stats/statsReport"; import { usePageUnload } from "./usePageUnload"; @@ -363,6 +364,12 @@ export function useGroupCall( groupCallOTelMembership?.onSummaryStatsReport(report); } + function onCallFeedStatsReport( + report: GroupCallStatsReport + ): void { + groupCallOTelMembership?.onCallFeedStatsReport(report); + } + groupCall.on(GroupCallEvent.GroupCallStateChanged, onGroupCallStateChanged); groupCall.on(GroupCallEvent.UserMediaFeedsChanged, onUserMediaFeedsChanged); groupCall.on( @@ -387,6 +394,11 @@ export function useGroupCall( onByteSentStatsReport ); groupCall.on(GroupCallStatsReportEvent.SummaryStats, onSummaryStatsReport); + groupCall.on( + GroupCallStatsReportEvent.CallFeedStats, + onCallFeedStatsReport + ); + groupCall.room.currentState.on( RoomStateEvent.Update, checkForParallelCalls @@ -450,6 +462,10 @@ export function useGroupCall( GroupCallStatsReportEvent.SummaryStats, onSummaryStatsReport ); + groupCall.removeListener( + GroupCallStatsReportEvent.CallFeedStats, + onCallFeedStatsReport + ); groupCall.room.currentState.off( RoomStateEvent.Update, checkForParallelCalls diff --git a/yarn.lock b/yarn.lock index c9c9a44e..1b5602a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10557,9 +10557,9 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#e70a1a1effe59e6754f9a10cc2df8eef81638c7d": - version "25.1.1" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/e70a1a1effe59e6754f9a10cc2df8eef81638c7d" +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#3cfad3cdeb7b19b8e0e7015784efd803cb9542f1": + version "26.0.0" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3cfad3cdeb7b19b8e0e7015784efd803cb9542f1" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.9" From efc25fd4ec7cc127c02e79d1a682c28d9a986890 Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Wed, 7 Jun 2023 17:12:24 +0200 Subject: [PATCH 14/28] hotfix Quality survey button interaction (#1091) Signed-off-by: Timo K --- src/input/StarRatingInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/input/StarRatingInput.tsx b/src/input/StarRatingInput.tsx index 34f01202..53620782 100644 --- a/src/input/StarRatingInput.tsx +++ b/src/input/StarRatingInput.tsx @@ -60,7 +60,7 @@ export function StarRatingInput({