Skip to content

Commit cbc46d7

Browse files
feat: new bug reporting (#9658)
* feat: bug reporting services (Sentry feedback + Intercom proxy tickets) Adds the service layer for the in-client bug report flow: - SentryUserFeedbackService (Utility assembly, next to SentryReportHandler): sends a Sentry User Feedback entry carrying the user's image as its single attachment and the client log tail on a linked event, then returns a deep link built from the feedback event id. The link is obtained through HubAdapter.Instance because the core SentrySdk is internal in the bundled Sentry.dll and the Unity facade discards the id. - DCL.BugReporting (folded into DCL.ApiServices via asmref): IntercomTicketClient posts to the intercom-proxy lambda through Signed Fetch with the allowlisted Origin header; IntercomTicketPayload builds the ticket_type_id + ticket_attributes body; BugReportService orchestrates feedback first, ticket second, inlining the Sentry link in the ticket description (the Evidence files attribute rejects API writes and stays reserved for the image the lambda will inline later). - SignedFetchPostAsync overload taking WebRequestHeadersInfo, and the IntercomTickets entry in DecentralandUrl/DecentralandUrlsSource. - EditMode tests for the payload shape and description composition. The user image is not sent to the proxy yet: its upload envelope is pending on the lambda side. * fix: review feedback on bug reporting services - Drop the SignedFetchPostAsync overload: PostAsync already accepts headersInfo and signInfo (same pattern as PlacesAPIClient), so the Intercom client signs and adds the Origin header through it directly. - Pin the intercom proxy url to .org, the lambda has no zone deployment. - Neutral diagnostics label in the ticket description. - Check cancellation right after the feedback submission and before the degradation warning: the feedback service is exception-free and reports cancellation through its result rather than throwing. - Document the proxy passthrough contract on IntercomTicketResponse. * feat: bug reporting UI (modal MVC, plugin wiring, /bug chat command) BugReportController/View popup driving BugReportService, feature-flag gated BugReportPlugin registered from DynamicWorldContainer, and a /bug chat command as the code-only entry point. The screenshot picker is an unimplemented seam (IBugReportImageProvider): the section stays hidden until the picker UX is decided. Prefab and Plugin Settings entry are still authored in the editor. * fix: qualify Arch.Core.World in BugReportPlugin A World namespace is visible in the plugins assembly, so the bare type name resolves to it (CS0118). Matches InWorldCameraPlugin's style. * fix: drop unused UnityEngine using that made Avatar ambiguous in test * refactor: bug report flow reduced to form + success popup The design has no submitting or error states: submit shows the success popup immediately and the upload runs detached on a controller-lifetime token, logging failures through ReportHub. * refactor: drop the /bug chat command Not part of the required scope; the modal will get its entry point from the UI instead. * feat: bug report entry points and prefab scaffold Sidebar gets a feature-gated bug report button below help that issues the BugReportController popup. The attach button now opens the OS file browser (File Browser PRO, as Communities does) through OsFileBrowserBugReportImageProvider: png/jpg/jpeg, 10 MB cap, preview texture destroyed on remove/close/dispose. A one-shot editor menu item (Decentraland/UI/Scaffold BugReportView Prefab) builds the grey-box BugReportView prefab with an overlay canvas root like BlockedScreen and every serialized field wired; the generated prefab is included. * feat: enable bug report feature by default in editor * chore: add missing meta for Bug.png * fix: block gameplay input while the bug report form is open Typing in the description field was triggering shortcut-bound panels. The controller now disables the Shortcuts, InWorldCamera, Camera and Player input maps on show and restores them on close, following FriendRequestController. * fix: send only default pseudo-attributes on the intercom ticket Intercom rejected the probe-derived attribute names (Issue Type, OS, GPU, RAM, Client version) with 400 "Extra attributes provided that are not found on ticket type" when going through the new proxy. Only the _default_title_ and _default_description_ pseudo-attributes exist on every ticket type, so the machine context, coordinates and Sentry link now ride inside the description body. * feat: ticket evidence image, session context and performance prompt - send the attached image as top-level evidence for the proxy to host and inline (3MB cap, an oversized image travels to Sentry only) - add SDK version, Launcher Version and Meets Minimum Requirements attributes, resolved at submit time through IBugReportSessionContext - offer the bug report form when a hiccup or sustained low FPS is detected: once per session, permanent opt-out, disabled until the prompt prefab is assigned in the plugin settings - MultilineInputField keeps Enter inserting new lines in the shared TextInput prefab; description gains a focus-only char counter - styled BugReportView prefab (addressable), sidebar button art and success art, plugin settings entry - editor-only Sentry DSN fallback from the DCL_SENTRY_DSN env var * feat: debug button to trigger the performance issue prompt - new Bug Report debug panel widget, registered by the prompt system: Show Performance Prompt fires the popup with a synthetic 2.5s hiccup, bypassing the detector and the one-per-session and opt-out guards - scaffolded PerformanceIssuePrompt prefab, marked addressable and assigned in the plugin settings, which switches the prompt on * feat: preselect the Performance issue type on prompt-opened reports - BugReportParams carries an optional prefilled issue type next to the prefilled description; unknown or absent falls back to no selection - the performance prompt passes the new BugReportIssueTypes.PERFORMANCE constant, so the form opens ready to submit once consent is given * feat: bug report entry points on the login and loading screens - nullable BugReportButton on the auth and loading screen views, shown only while the bug report feature is enabled - the loading screen lives on the Overlay layer, above every popup, so BugReportParams gained ShowAboveOverlays: the form stays a popup in the window stack but re-applies its canvas draw order above overlays - the performance issue prompt pauses detection while the loading screen is on (IsLoadingScreenOn through the plugin): loading hiccups used to consume the once-per-session prompt behind the overlay * fix: always attach the client log and harden the report inputs The logs toggle is a required agreement rather than a choice, so the ShareLogs flag disappears from the draft, the input and the Sentry report: every report ships the log. Also: - lower the description cap to 2500, since the proxy applies its 10,000 character limit to the HTML escaped and formatted text, not the raw one - validate the picked image by decoding it and derive the content type from the bytes instead of the file extension - destroy the picked preview when the view is already gone - refuse Sentry submission on an already cancelled token, and report success once the feedback is captured, since a cancellation cannot recall it - skip the performance prompt while the window is unfocused and on the first frame after any pause, whose delta is not gameplay evidence * feat: resolve the Intercom proxy url and origin per environment The proxy is now deployed on dev (intercom-proxy.decentraland.zone) and allowlists the web client origin of its own environment, so the client resolves both the endpoint and the Origin header through {ENV} instead of hardcoding .org. * feat: route the debug performance hiccup through the real detection pipeline The debug button now feeds a synthetic 2.5s frame to the detector on the next unpaused Update instead of showing the prompt directly, so the hiccup classification, the pause guards, the one-offer-per-session rule and the don't-show-again opt-out are all exercised as in production. * feat: repeat the performance prompt per detection and add an opt-out reset button The prompt now re-offers on every detected issue until the user marks "do not show again"; the one-per-session guard is gone. An in-flight show is guarded so a second prompt cannot queue, and the persisted opt-out is re-read when the prompt closes. A new debug button clears the stored opt-out so the prompt can be tested again. * fix: adapt the bug report test to the UserId profile constructor * fix: initialize FeaturesRegistry in bug report tests and clear new lint warnings The BugReportControllerShould fixture now sets up the FeaturesRegistry singleton, which Profile name validation reads, so the profile-name test no longer throws. Lint fixes to bring the warning count back under the CI baseline: - BugReporting views expose serialized properties as public, the view Awake became an Initialize called by the controller, and BugReportIssueType implements IEquatable - AuthenticationScreenController events, fsm and audio are nullable; CurrentRequestID renamed to CurrentRequestId - SidebarView public camelCase properties renamed to PascalCase with FormerlySerializedAs keeping the prefab wiring, call sites updated - SidebarController passes UserId.Value instead of the nullable string conversion - SentryReportHandler matches the base nullability and drops a redundant using; Web3AuthenticationPlugin drops the unused appArgs; LoadingScreenPlugin factory local is non-nullable; the obsolete GPUI flag mapping is wrapped in a scoped pragma * refactor: address review feedback, trim comment noise Remove or compress the comments that narrated self-explanatory code, keeping one-liners only where they state a real external constraint (platform quirks, Intercom and proxy wire rules, Sentry API behavior). Also from the review: - AuthenticationScreenController removes the bug report button listener on dispose - the now-dead InternalsVisibleTo for the prefab scaffolder is dropped, the view members it wired are public
1 parent 2e4c9c8 commit cbc46d7

111 files changed

Lines changed: 12167 additions & 59 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Explorer/Assets/AddressableAssetsData/AssetGroups/Essentials.asset

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ MonoBehaviour:
105105
m_ReadOnly: 0
106106
m_SerializedLabels: []
107107
FlaggedDuringContentUpdateRestriction: 0
108+
- m_GUID: 46abfd8d700191d43b8892c99421c5a7
109+
m_Address: PerformanceIssuePrompt
110+
m_ReadOnly: 0
111+
m_SerializedLabels: []
112+
FlaggedDuringContentUpdateRestriction: 0
108113
- m_GUID: 4ae49825f39c564478515b4c2ae201af
109114
m_Address: Assets/DCL/AvatarRendering/AvatarShape/Assets/Avatar_Toon.mat
110115
m_ReadOnly: 0
@@ -270,6 +275,11 @@ MonoBehaviour:
270275
m_ReadOnly: 0
271276
m_SerializedLabels: []
272277
FlaggedDuringContentUpdateRestriction: 0
278+
- m_GUID: ab23199c8104ca44e837d8ac570fee44
279+
m_Address: BugReportView
280+
m_ReadOnly: 0
281+
m_SerializedLabels: []
282+
FlaggedDuringContentUpdateRestriction: 0
273283
- m_GUID: ace53bd2bc5e2b8439785bb3122b12e3
274284
m_Address: Assets/DCL/InWorldCamera/InWorldCamera/Prefabs/InWorldCamera.FollowTarget.prefab
275285
m_ReadOnly: 0

Explorer/Assets/DCL/AuthenticationScreenFlow/Assets/Prefabs/Authentication.MainScreen.prefab

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,81 @@ MonoBehaviour:
139139
m_hasFontAssetChanged: 0
140140
m_baseMaterial: {fileID: 0}
141141
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
142+
--- !u!1 &905826442250835330
143+
GameObject:
144+
m_ObjectHideFlags: 0
145+
m_CorrespondingSourceObject: {fileID: 0}
146+
m_PrefabInstance: {fileID: 0}
147+
m_PrefabAsset: {fileID: 0}
148+
serializedVersion: 6
149+
m_Component:
150+
- component: {fileID: 7312226253269487411}
151+
- component: {fileID: 739876128597536228}
152+
- component: {fileID: 787601352420706680}
153+
m_Layer: 0
154+
m_Name: Icon
155+
m_TagString: Untagged
156+
m_Icon: {fileID: 0}
157+
m_NavMeshLayer: 0
158+
m_StaticEditorFlags: 0
159+
m_IsActive: 1
160+
--- !u!224 &7312226253269487411
161+
RectTransform:
162+
m_ObjectHideFlags: 0
163+
m_CorrespondingSourceObject: {fileID: 0}
164+
m_PrefabInstance: {fileID: 0}
165+
m_PrefabAsset: {fileID: 0}
166+
m_GameObject: {fileID: 905826442250835330}
167+
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
168+
m_LocalPosition: {x: 0, y: 0, z: 0}
169+
m_LocalScale: {x: 1, y: 1, z: 1}
170+
m_ConstrainProportionsScale: 0
171+
m_Children: []
172+
m_Father: {fileID: 9083280592308593920}
173+
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
174+
m_AnchorMin: {x: 0.5, y: 0.5}
175+
m_AnchorMax: {x: 0.5, y: 0.5}
176+
m_AnchoredPosition: {x: 0, y: 0}
177+
m_SizeDelta: {x: 24, y: 24}
178+
m_Pivot: {x: 0.5, y: 0.5}
179+
--- !u!222 &739876128597536228
180+
CanvasRenderer:
181+
m_ObjectHideFlags: 0
182+
m_CorrespondingSourceObject: {fileID: 0}
183+
m_PrefabInstance: {fileID: 0}
184+
m_PrefabAsset: {fileID: 0}
185+
m_GameObject: {fileID: 905826442250835330}
186+
m_CullTransparentMesh: 1
187+
--- !u!114 &787601352420706680
188+
MonoBehaviour:
189+
m_ObjectHideFlags: 0
190+
m_CorrespondingSourceObject: {fileID: 0}
191+
m_PrefabInstance: {fileID: 0}
192+
m_PrefabAsset: {fileID: 0}
193+
m_GameObject: {fileID: 905826442250835330}
194+
m_Enabled: 1
195+
m_EditorHideFlags: 0
196+
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
197+
m_Name:
198+
m_EditorClassIdentifier:
199+
m_Material: {fileID: 0}
200+
m_Color: {r: 1, g: 1, b: 1, a: 0.5019608}
201+
m_RaycastTarget: 1
202+
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
203+
m_Maskable: 1
204+
m_OnCullStateChanged:
205+
m_PersistentCalls:
206+
m_Calls: []
207+
m_Sprite: {fileID: 21300000, guid: a311b44ef38bac5489f8e499b48822ea, type: 3}
208+
m_Type: 0
209+
m_PreserveAspect: 1
210+
m_FillCenter: 1
211+
m_FillMethod: 4
212+
m_FillAmount: 1
213+
m_FillClockwise: 1
214+
m_FillOrigin: 0
215+
m_UseSpriteMesh: 0
216+
m_PixelsPerUnitMultiplier: 1
142217
--- !u!1 &2696552889365763863
143218
GameObject:
144219
m_ObjectHideFlags: 0
@@ -276,6 +351,150 @@ MonoBehaviour:
276351
m_hasFontAssetChanged: 0
277352
m_baseMaterial: {fileID: 0}
278353
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
354+
--- !u!1 &3263457773076965550
355+
GameObject:
356+
m_ObjectHideFlags: 0
357+
m_CorrespondingSourceObject: {fileID: 0}
358+
m_PrefabInstance: {fileID: 0}
359+
m_PrefabAsset: {fileID: 0}
360+
serializedVersion: 6
361+
m_Component:
362+
- component: {fileID: 9083280592308593920}
363+
- component: {fileID: 3997775608221144549}
364+
- component: {fileID: 567627120390738114}
365+
- component: {fileID: 3153501696729633973}
366+
- component: {fileID: 5990919100451904776}
367+
m_Layer: 0
368+
m_Name: ReportABugButton
369+
m_TagString: Untagged
370+
m_Icon: {fileID: 0}
371+
m_NavMeshLayer: 0
372+
m_StaticEditorFlags: 0
373+
m_IsActive: 1
374+
--- !u!224 &9083280592308593920
375+
RectTransform:
376+
m_ObjectHideFlags: 0
377+
m_CorrespondingSourceObject: {fileID: 0}
378+
m_PrefabInstance: {fileID: 0}
379+
m_PrefabAsset: {fileID: 0}
380+
m_GameObject: {fileID: 3263457773076965550}
381+
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
382+
m_LocalPosition: {x: 0, y: 0, z: 0}
383+
m_LocalScale: {x: 1, y: 1, z: 1}
384+
m_ConstrainProportionsScale: 0
385+
m_Children:
386+
- {fileID: 7312226253269487411}
387+
m_Father: {fileID: 9199866600602126572}
388+
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
389+
m_AnchorMin: {x: 0, y: 0}
390+
m_AnchorMax: {x: 0, y: 0}
391+
m_AnchoredPosition: {x: 202, y: 40}
392+
m_SizeDelta: {x: 36, y: 36}
393+
m_Pivot: {x: 0, y: 0}
394+
--- !u!222 &3997775608221144549
395+
CanvasRenderer:
396+
m_ObjectHideFlags: 0
397+
m_CorrespondingSourceObject: {fileID: 0}
398+
m_PrefabInstance: {fileID: 0}
399+
m_PrefabAsset: {fileID: 0}
400+
m_GameObject: {fileID: 3263457773076965550}
401+
m_CullTransparentMesh: 1
402+
--- !u!114 &567627120390738114
403+
MonoBehaviour:
404+
m_ObjectHideFlags: 0
405+
m_CorrespondingSourceObject: {fileID: 0}
406+
m_PrefabInstance: {fileID: 0}
407+
m_PrefabAsset: {fileID: 0}
408+
m_GameObject: {fileID: 3263457773076965550}
409+
m_Enabled: 1
410+
m_EditorHideFlags: 0
411+
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
412+
m_Name:
413+
m_EditorClassIdentifier:
414+
m_Material: {fileID: 0}
415+
m_Color: {r: 1, g: 1, b: 1, a: 1}
416+
m_RaycastTarget: 1
417+
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
418+
m_Maskable: 1
419+
m_OnCullStateChanged:
420+
m_PersistentCalls:
421+
m_Calls: []
422+
m_Sprite: {fileID: 21300000, guid: 12dd1efc4e826764f9b02be515a9a033, type: 3}
423+
m_Type: 1
424+
m_PreserveAspect: 0
425+
m_FillCenter: 1
426+
m_FillMethod: 4
427+
m_FillAmount: 1
428+
m_FillClockwise: 1
429+
m_FillOrigin: 0
430+
m_UseSpriteMesh: 0
431+
m_PixelsPerUnitMultiplier: 2.2
432+
--- !u!114 &3153501696729633973
433+
MonoBehaviour:
434+
m_ObjectHideFlags: 0
435+
m_CorrespondingSourceObject: {fileID: 0}
436+
m_PrefabInstance: {fileID: 0}
437+
m_PrefabAsset: {fileID: 0}
438+
m_GameObject: {fileID: 3263457773076965550}
439+
m_Enabled: 1
440+
m_EditorHideFlags: 0
441+
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
442+
m_Name:
443+
m_EditorClassIdentifier:
444+
m_Navigation:
445+
m_Mode: 3
446+
m_WrapAround: 0
447+
m_SelectOnUp: {fileID: 0}
448+
m_SelectOnDown: {fileID: 0}
449+
m_SelectOnLeft: {fileID: 0}
450+
m_SelectOnRight: {fileID: 0}
451+
m_Transition: 1
452+
m_Colors:
453+
m_NormalColor: {r: 1, g: 1, b: 1, a: 0.03137255}
454+
m_HighlightedColor: {r: 1, g: 1, b: 1, a: 0.101960786}
455+
m_PressedColor: {r: 1, g: 1, b: 1, a: 0.03137255}
456+
m_SelectedColor: {r: 1, g: 1, b: 1, a: 0.03137255}
457+
m_DisabledColor: {r: 1, g: 1, b: 1, a: 0.03137255}
458+
m_ColorMultiplier: 1
459+
m_FadeDuration: 0.1
460+
m_SpriteState:
461+
m_HighlightedSprite: {fileID: 0}
462+
m_PressedSprite: {fileID: 0}
463+
m_SelectedSprite: {fileID: 0}
464+
m_DisabledSprite: {fileID: 0}
465+
m_AnimationTriggers:
466+
m_NormalTrigger: Normal
467+
m_HighlightedTrigger: Highlighted
468+
m_PressedTrigger: Pressed
469+
m_SelectedTrigger: Selected
470+
m_DisabledTrigger: Disabled
471+
m_Interactable: 1
472+
m_TargetGraphic: {fileID: 567627120390738114}
473+
m_OnClick:
474+
m_PersistentCalls:
475+
m_Calls: []
476+
--- !u!114 &5990919100451904776
477+
MonoBehaviour:
478+
m_ObjectHideFlags: 0
479+
m_CorrespondingSourceObject: {fileID: 0}
480+
m_PrefabInstance: {fileID: 0}
481+
m_PrefabAsset: {fileID: 0}
482+
m_GameObject: {fileID: 3263457773076965550}
483+
m_Enabled: 1
484+
m_EditorHideFlags: 0
485+
m_Script: {fileID: 11500000, guid: e464818fbc7e5f8409c74f49d431b70b, type: 3}
486+
m_Name:
487+
m_EditorClassIdentifier:
488+
<Button>k__BackingField: {fileID: 3153501696729633973}
489+
<images>k__BackingField: []
490+
<text>k__BackingField: {fileID: 0}
491+
interactableColor: {r: 1, g: 1, b: 1, a: 1}
492+
hoverColor: {r: 0.54, g: 0.54, b: 0.54, a: 1}
493+
<ButtonPressedAudio>k__BackingField: {fileID: 11400000, guid: cbbd6a003fc75e24da47c13feacd92c7, type: 2}
494+
<ButtonHoverAudio>k__BackingField: {fileID: 0}
495+
icon: {fileID: 787601352420706680}
496+
mutedSprite: {fileID: 21300000, guid: 1114383cfa9184596977815484fda1cb, type: 3}
497+
unmutedSprite: {fileID: 21300000, guid: cf3b810861e624ba5b76f05e7b1297f0, type: 3}
279498
--- !u!1 &3499071352042108546
280499
GameObject:
281500
m_ObjectHideFlags: 0
@@ -680,6 +899,7 @@ RectTransform:
680899
- {fileID: 4355605922863739472}
681900
- {fileID: 1031898781866329336}
682901
- {fileID: 5645583596339375076}
902+
- {fileID: 9083280592308593920}
683903
- {fileID: 3704401661058903746}
684904
- {fileID: 3992252487768445545}
685905
m_Father: {fileID: 0}
@@ -778,6 +998,7 @@ MonoBehaviour:
778998
<DiscordButton>k__BackingField: {fileID: 1710274344204595558}
779999
<ExitButton>k__BackingField: {fileID: 6752555971229295883}
7801000
<MuteButton>k__BackingField: {fileID: 2664898282194929885}
1001+
<BugReportButton>k__BackingField: {fileID: 3153501696729633973}
7811002
--- !u!1 &6109353712123470193
7821003
GameObject:
7831004
m_ObjectHideFlags: 0

0 commit comments

Comments
 (0)