Appearance
Reuse the CMS Analytics widget
Editing a section
- Open a page editor and locate the content section.
- Expand the blue Analytics row with the insights icon.
- Check Page and Page ID to confirm the configuration scope.
- Enter a Component name, such as
Main bannerorSeptember promotion. A component has one name even if it records several interactions. - Select On or Off for the supported interactions.
- Select Save analytics and wait for the section-specific confirmation.
Analytics saves separately from page content. Failed saves keep the draft for retry. Article and clinic settings apply to every page of that type; the website identifies individual records automatically. Page-wide and global pause switches take precedence.
Names, identifiers and properties
| Property | Editable here | Meaning |
|---|---|---|
| Page | No | Readable title from the page catalog |
| Page ID | No | Stable page_key, for example home |
| Component name | Yes | Component configuration label, up to 120 UTF-8 bytes |
| Button Tracking ID | In the content form only | Existing button ID used by GTM, bmlytics and GA; Analytics displays a read-only copy |
| Section / carousel ID | No | Installed identity for section views and carousel navigation |
| Interactions | Yes | Selection from events supported by that component |
Renaming a component does not rename its identifier, change its CRM template, or rewrite historical events. Component names are stored in the existing configuration's label field. This form does not introduce a new component_name or page_name event property. Event payload identifiers remain unchanged. Raw autogenerated labels matching the component ID are presented as an empty field with a readable placeholder; nothing is migrated merely by opening the form.
Page titles come from the page catalog rather than separate, potentially contradictory names in every section. Article/clinic titles belong to their content records. Do not use editable names as database join keys.
Reusing the widget
dart
import 'package:bumame_cnc/modules/landing_cms/tracking/landing_section_analytics.dart';
BmSectionContainer(
title: 'Main banner',
child: bannerContentEditor,
footer: const LandingSectionAnalytics(
pageKey: 'home',
sectionTitle: 'Main banners',
componentTitles: {'home_hero': 'Main banner'},
interactions: [
SectionAnalyticsInteraction(
'home_hero',
'component_impression',
'Banner views',
'Count a banner when it becomes visible to a visitor.',
),
SectionAnalyticsInteraction(
'home_hero',
'component_click',
'Banner button clicks',
'Measure clicks on the banner call-to-action.',
),
],
),
)For collection sections, pass the widget through LandingCmsCollectionSection.analytics. Optional composition slots do not add a tracking dependency to common UI widgets.
pageKey: installed page type; never derive it from a mutable display name.sectionTitle: English title for the form and save confirmation.componentTitles: optional readable placeholders keyed by component ID. Placeholders are not saved values.interactions: explicit, source-verified capabilities. Repeated IDs are grouped under one name field.repository: optionalLandingCmsTrackingRepositorydependency for isolated previews/tests. Normal CMS usage obtainsApiServicefrom its existing provider.
Use the same component ID for its impressions and clicks when the website does. Do not invent identifiers or expose events the website cannot emit. Components shared by several sections must explain their shared scope; avoid separate independent forms for one component.
Code organization
landing_section_analytics.dart: reusable UI, lazy loading, controllers, draft state and lifecycle handling. It usesBmTextField,BmToggle,BmTextandBmButton.landing_section_analytics_models.dart: supported interaction definition and pure patch function. No Flutter UI dependency.landing_section_analytics_catalog.dart: explicit mappings for page sections. Home mappings currently live beside its content editor.landing_analytics_config.dart: existing wire model.landing_cms_tracking_repository.dart: existing GET/PUT transport.
Saving safely
- Load configuration when the form is first expanded.
- Keep name edits and interaction edits in separate draft maps.
- On Save, fetch the latest configuration.
- Validate that every renamed component belongs to this section and exists in the configuration. Validate the UTF-8 byte limit enforced by Go.
- Patch only changed fields. Preserve other components, other events, CRM template codes and global settings.
- PUT using the latest version. The server rejects a concurrent update after that read.
- On success, replace the baseline and clear the draft. On failure, retain edits for retry.
When re-enabling an event on a disabled component, retained events are cleared first so a single toggle cannot unintentionally enable unrelated interactions. Async responses from an obsolete widget scope are ignored. Text controllers are disposed with the widget.
Adding another property
Do not add arbitrary JSON inputs to this editor. Define a concrete property with a clear type, ownership and validation first. A new property needs coordinated support in the Go configuration contract, Flutter model, website runtime, event validation and warehouse transformation if it should be included in event data. A field that merely appears in the form is not end-to-end tracking support.
For another configuration property, add an explicit draft field and patch only that field after a fresh read. For runtime properties such as entity ID or slide position, populate them where the website interaction happens rather than asking CMS editors to type them.
Product detail tracking remains excluded. Engineers can reuse this widget for PDP after adding and validating its instrumentation and configuration.
Verification
Run flutter pub get with the existing local dependency overrides, then:
sh
flutter analyze --no-pub lib/modules/landing_cms/tracking
flutter test --no-pub test/modules/landing_cms/landing_section_analytics_test.dartTests cover stable IDs after renaming, unrelated settings and CRM preservation, invalid scope, UTF-8 limits, failed saves with retry, concurrent changes in another section, and form layout at 390 and 1100 pixels. Before release, verify save/reload in staging and restore temporary UAT changes.
Choosing which switches a section offers
The engineer declares the interactions list. Each entry creates one independent On/Off switch for a (componentId, event) pair. There are no separate widget arguments such as trackClicks: true or trackViews: true.
component_click: button/card/link clicks.component_impression: visibility events.carousel_navigation: slide changes.
For example, add a third SectionAnalyticsInteraction('home_hero', 'carousel_navigation', 'Slide changes', 'Measure visitors browsing the banners.') to the example above to expose the slide switch. Omit an interaction to omit its switch from this section. The component name remains shared across its switches.
The saved configuration has a component enabled boolean and an events list:
json
{
"home": {
"enabled": true,
"components": {
"home_hero": {
"label": "Main banner",
"enabled": true,
"events": ["component_click", "component_impression"]
}
}
}
}In this example clicks and views are On, and slide changes are Off. Global and page switches must also be enabled for tracking to run. Turning an individual switch Off removes that event from the list; it does not change the component ID.
Register the page/component in the API configuration and the allowed website tracking catalog first. Unknown components intentionally have disabled controls. A CMS switch only configures tracking: the matching website event listener and collector validation must already support the event. Adding a switch alone does not install click, visibility, or carousel instrumentation.
One Tracking ID per button
The existing content tracking_id (rendered as data-gtm-id) is also the button's data-bm-id and emitted component_id. Do not ask editors to enter another ID in Analytics. CTA Title is the visible button label; Component name is optional readable configuration metadata, not a second identity or captured button text.
- Set the button Tracking ID in its content form.
- Save the page/content record.
- Reopen Analytics to load the saved IDs. For collections, the list shows IDs of the saved items controlled by the section switch.
- Set the supported interaction switches and select Save analytics.
Use nonempty unique IDs for buttons that need analytics (letters, digits, underscore, dot, colon or hyphen; maximum 96 characters; first character alphanumeric). A blank ID leaves the button functional but does not record its click. Responsive copies of the same content item can share its ID; distinct content records must not share an ID on the same page.
The existing SectionAnalyticsInteraction.componentId argument addresses a saved configuration control. For a button, the Go resolver maps that control to Tracking IDs from the authoritative CMS content tables. Those internal keys are retained to preserve switch, name and CRM template settings; they are not emitted as a second button identifier. Adding a new CMS-backed button requires adding its content mapping in analytics_button_ids.go as well as its CMS control and website listener. UI-only switches cannot register arbitrary IDs.
Section views and carousel navigation keep their section/carousel IDs. For a banner, the click uses the banner item's Tracking ID while its impression and navigation events keep their section identity plus item/position context.
Read-only button_tracking_ids metadata comes from the API and is deliberately omitted from Flutter's settings update payload. The public configuration and transactional collectors resolve IDs from content; they preserve CRM templates and reject conflicting settings or duplicate IDs instead of guessing.
CMS-owned Tracking IDs
LandingAnalyticsComponent.trackingId serializes as tracking_id in the persisted JSON configuration. The section widget uses componentTrackingIds in applySectionAnalyticsChanges to patch only edited IDs on a fresh config version. Technical component keys stay stable so CMS changes do not require a website rebuild.
Existing content buttons retain their single ID input in the content form; Analytics displays that saved ID read-only. Internal controls expose Tracking ID. Components with both content clicks and section events expose Section Tracking ID for the non-click events. The API resolves both sources before ingestion and rejects collisions. Renaming a label does not change IDs; renaming an ID changes future event identity only.