Skip to content

Ser 2915 dymanic parameters deeplinks - #7811

Open
coskunaydinoglu wants to merge 20 commits into
masterfrom
SER-2915-Dymanic-Parameters-Deeplinks
Open

Ser 2915 dymanic parameters deeplinks#7811
coskunaydinoglu wants to merge 20 commits into
masterfrom
SER-2915-Dymanic-Parameters-Deeplinks

Conversation

@coskunaydinoglu

Copy link
Copy Markdown
Contributor

No description provided.

coskunaydinoglu and others added 2 commits July 14, 2026 07:56
Adds a new 'dynamic-params' sidebar input type with the
cly-content-dynamic-params-input component: a repeater of
parameter name / user property / fallback rows used by the
content editor to attach dynamic query string parameters to
URL/deep-link button actions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
Replaces the parameter repeater with a popover matching the push
notifications Add User Property popover: internal/external property
tabs, the same cly-select-x property selector, "start with capital
letter" switch and fallback value. Confirming appends a visible
{property|fallback|c} placeholder to the URL/deeplink input.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
coskunaydinoglu and others added 9 commits July 14, 2026 12:01
Unlike push notifications, deep-link placeholders are query string
parameters, so the popover now also asks for the parameter name and
appends `name={property|fallback|c}` to the URL with the proper ?/&
separator (keeping any hash fragment at the end).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
Two additions to the deep-link parameter editor popover:

- a "Static Value" mode next to internal/external properties, for
  hardcoded parameters like param5=param5value (value URL-encoded,
  served as-is)
- every query parameter in the URL is now shown as a highlighted chip
  under the URL input; clicking a chip reopens the popover pre-filled
  (parameter name, property/fallback/capitalize or static value) and
  Confirm updates that parameter in place, while the chip's close icon
  removes it from the URL

Dynamic placeholder chips are highlighted differently from static
ones, and reserved characters ({}|&=?#) are stripped from fallbacks so
they cannot break URL parsing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
The URL/deeplink field is now a contenteditable editor that renders
every query parameter as a highlighted, non-editable token inside the
URL itself (blue for {property|fallback|c} placeholders, gray for
static values) instead of listing them under the input. Clicking a
token opens the Add User Property popover pre-filled so the parameter
can be changed or removed; the rest of the URL stays freely editable
text, with plain-text paste and single-line input enforced.

The separate plain URL input is removed — the editor takes its place
(and its "URL or deeplink" label) and edits action.value directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
- the Add User Property panel now floats over the canvas as a
  fixed-position popover (440px wide, right-aligned to the sidebar,
  scrollable within the viewport), so all three category tabs and the
  Cancel/Confirm buttons are always visible regardless of the narrow
  sidebar width
- the URL editor keeps a static width and wraps long URLs onto
  multiple lines (scrolling vertically past ~5 lines) instead of
  scrolling horizontally

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
Reverts the fixed-position floating popover — the Add User
Property/Edit Parameter panel opens in-flow under the button inside
the sidebar, as before. To keep everything visible in the narrow
pane, the category tabs wrap onto a second row when needed and the
wrapping URL editor (static width, overflowing downwards) keeps the
panel from being pushed off-screen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
The Internal/External/Static category tabs consumed too much of the
narrow sidebar pane — replaced with a full-width "Value type"
dropdown. The "start with capital letter" checkbox label now wraps
onto multiple lines so it fits the pane.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmXBxfgpFtGBFhDJrR7A8r
Copilot AI review requested due to automatic review settings July 24, 2026 12:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds dynamic parameter support for content builder deeplink URLs by introducing a dedicated sidebar input that can insert/edit query parameters as either static values or user-property placeholders.

Changes:

  • Add a new dynamic-params sidebar input type and implement cly-content-dynamic-params-input (contenteditable URL editor + add/edit/remove param popover).
  • Add new dashboard localization keys for the dynamic-params UI.
  • Add a changelog entry for journey_engine deeplink dynamic parameters.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
frontend/express/public/localization/dashboard/dashboard.properties Adds i18n strings for the dynamic deeplink parameter UI.
frontend/express/public/javascripts/countly/vue/templates/content/UI/content-dynamic-params-input.html New template for the dynamic-params URL editor + parameter popover.
frontend/express/public/javascripts/countly/vue/components/content.js Registers new sidebar input type and implements the dynamic-params component logic.
CHANGELOG.md Notes the new dynamic deeplink parameter support.

@savascountly savascountly left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your effort!

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 24, 2026 12:25
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (4)

frontend/express/public/javascripts/countly/vue/components/content.js:1099

  • openPanelForParam matches the placeholder regex against the raw query value. If the user pastes a URL where the placeholder is URL-encoded (e.g. %7Bprop%7D), the token is detected as dynamic (via safeDecode in createParamNode) but clicking it opens the panel in static mode because this match fails.
this.property.key = this.safeDecode(rawKey);

const decodedValue = this.safeDecode(rawValue);
const match = decodedValue.match(CONTENT_DYNAMIC_PARAM_PLACEHOLDER_RE);

frontend/express/public/javascripts/countly/vue/components/content.js:1085

  • onPropertySelect is not indented consistently with neighboring methods, which hurts readability and may violate formatting rules used in this file.
onPropertySelect(value) {
    const sanitized = (value === null || value === undefined) ? '' : String(value);
    // reserved characters would break placeholder/query string parsing
    this.property.value = sanitized.replace(/[{}|&=?#]/g, '');
},

frontend/express/public/javascripts/countly/vue/components/content.js:1065

  • document.execCommand('insertText', ...) is deprecated and unreliable in modern browsers. This paste handler can be implemented with Selection/Range APIs while still ensuring plain-text insertion.
                const text = clipboard ? (clipboard.getData('text/plain') || '').replace(/[\r\n]+/g, '') : '';

                if (text) {
                    document.execCommand('insertText', false, text);
                }

frontend/express/public/javascripts/countly/vue/templates/content/UI/content-dynamic-params-input.html:10

  • This template introduces multiple large inline style="..." blocks. Other content-builder templates in this folder use class-based styling (e.g. content-block-list-input.html) which is easier to maintain and less likely to break theming (dark mode). Consider moving these styles into the existing stylesheet/SASS pipeline and keeping only class names here.
    class="cly-vue-content-dynamic-params-input__editor"
    spellcheck="false"
    style="background: #FFFFFF; border: 1px solid #DBDDE1; border-radius: 4px; padding: 5px 12px; min-height: 30px; max-height: 110px; overflow-y: auto; font-size: 14px; line-height: 20px; color: #383A3F; white-space: pre-wrap; word-break: break-all; outline: none;"
    :contenteditable="!disabled"

Comment thread frontend/express/public/javascripts/countly/vue/components/content.js Outdated
Comment thread CHANGELOG.md
Copilot AI review requested due to automatic review settings July 24, 2026 12:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (5)

frontend/express/public/javascripts/countly/vue/components/content.js:972

  • createParamNode is mis-indented compared to the surrounding methods block, which hurts readability and is likely to fail the repo's lint/format expectations for this file.
createParamNode(pair) {
    const eqIndex = pair.indexOf('=');
    const rawValue = eqIndex === -1 ? '' : pair.slice(eqIndex + 1);
    const decodedValue = this.safeDecode(rawValue);
    const isDynamic = CONTENT_DYNAMIC_PARAM_PLACEHOLDER_RE.test(decodedValue);
    const node = document.createElement('span');

frontend/express/public/javascripts/countly/vue/components/content.js:1099

  • The body of openPanelForParam contains a large unindented block, which is inconsistent with the rest of this file and makes the control flow harder to read.
this.property.key = this.safeDecode(rawKey);

const decodedValue = this.safeDecode(rawValue);
const match = decodedValue.match(CONTENT_DYNAMIC_PARAM_PLACEHOLDER_RE);

frontend/express/public/javascripts/countly/vue/components/content.js:1065

  • document.execCommand('insertText', ...) is deprecated and can behave inconsistently across browsers. Prefer inserting plain text via Selection/Range APIs, then explicitly emitting the updated value.
            onEditorPaste(event) {
                // paste as plain text so foreign markup never enters the editor
                event.preventDefault();

                const clipboard = event.clipboardData || window.clipboardData;
                const text = clipboard ? (clipboard.getData('text/plain') || '').replace(/[\r\n]+/g, '') : '';

                if (text) {
                    document.execCommand('insertText', false, text);
                }

frontend/express/public/javascripts/countly/vue/templates/content/UI/content-dynamic-params-input.html:10

  • This template introduces extensive inline style="..." usage (hard-coded colors/layout). Other templates in the same directory (e.g. content-sidebar-input.html) rely on BEM classes instead, so these styles should be moved into the existing SCSS pipeline to keep theming/maintenance consistent.
    ref="editor"
    class="cly-vue-content-dynamic-params-input__editor"
    spellcheck="false"
    style="background: #FFFFFF; border: 1px solid #DBDDE1; border-radius: 4px; padding: 5px 12px; min-height: 30px; max-height: 110px; overflow-y: auto; font-size: 14px; line-height: 20px; color: #383A3F; white-space: pre-wrap; word-break: break-all; outline: none;"
    :contenteditable="!disabled"

frontend/express/public/javascripts/countly/vue/components/content.js:841

  • The dynamic/static token styling is currently embedded as hard-coded CSS strings (CONTENT_DYNAMIC_PARAM_NODE_STYLE / CONTENT_STATIC_PARAM_NODE_STYLE) and applied via node.style.cssText. This makes theming and future UI changes harder; prefer applying a modifier class (e.g. __param--dynamic / __param--static) and styling it in SCSS.
    const CONTENT_DYNAMIC_PARAM_NODE_CLASS = 'cly-vue-content-dynamic-params-input__param';
    const CONTENT_DYNAMIC_PARAM_NODE_STYLE = 'background: #E1EFFF; color: #0166D6; border-radius: 3px; padding: 0 3px; cursor: pointer;';
    const CONTENT_STATIC_PARAM_NODE_STYLE = 'background: #E2E4E8; color: #383A3F; border-radius: 3px; padding: 0 3px; cursor: pointer;';

Comment thread frontend/express/public/javascripts/countly/vue/components/content.js Outdated
Copilot AI review requested due to automatic review settings July 24, 2026 13:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

frontend/express/public/javascripts/countly/vue/components/content.js:1048

  • Clicking a query-param token identifies its index via params.indexOf(pair), which breaks when the same name=value pair occurs multiple times (it always edits the first match). Use the clicked node’s position among rendered param nodes instead.
                const pair = paramNode.textContent || '';
                const index = this.parseUrl(this.serializeEditor()).params.indexOf(pair);

                if (index !== -1) {
                    this.openPanelForParam(index, pair);
                }

Comment thread frontend/express/public/javascripts/countly/vue/components/content.js Outdated
Comment thread frontend/express/public/localization/dashboard/dashboard.properties Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 24, 2026 14:03
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

frontend/express/public/javascripts/countly/vue/components/content.js:1048

  • onEditorClick finds the clicked parameter index via params.indexOf(pair). If the URL contains duplicate query pairs (e.g. a=1&a=1), indexOf will always return the first match, so clicking the second token edits/removes the wrong parameter. Use the DOM position of the clicked token instead of indexOf to reliably identify which token was clicked.
                const pair = paramNode.textContent || '';
                const index = this.parseUrl(this.serializeEditor()).params.indexOf(pair);

                if (index !== -1) {
                    this.openPanelForParam(index, pair);
                }

Copilot AI review requested due to automatic review settings July 24, 2026 14:09
@coskunaydinoglu
coskunaydinoglu removed the request for review from Copilot July 24, 2026 14:13
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 24, 2026 14:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

frontend/express/public/javascripts/countly/vue/components/content.js:1044

  • Click-to-edit uses params.indexOf(pair) to find the clicked parameter. If there are duplicate key=value pairs, indexOf always returns the first match, so clicking the second duplicate opens/edits the wrong parameter. Derive the index from the clicked DOM node’s position instead of string matching.
                const pair = paramNode.textContent || '';
                const index = this.parseUrl(this.serializeEditor()).params.indexOf(pair);

frontend/express/public/javascripts/countly/vue/templates/content/UI/content-dynamic-params-input.html:10

  • This template introduces substantial inline styling on the editor/panel elements. In this codebase, similar Vue content-builder templates typically rely on BEM classes with styles defined in frontend/express/public/stylesheets/vue/clyvue.scss (e.g., content-block-list-input). Keeping these styles inline makes them harder to theme and maintain. Consider moving these styles into SCSS and leaving only class names here.
  <div
    ref="editor"
    class="cly-vue-content-dynamic-params-input__editor"
    spellcheck="false"
    style="background: #FFFFFF; border: 1px solid #DBDDE1; border-radius: 4px; padding: 5px 12px; min-height: 30px; max-height: 110px; overflow-y: auto; font-size: 14px; line-height: 20px; color: #383A3F; white-space: pre-wrap; word-break: break-all; outline: none;"
    :contenteditable="!disabled"

Comment thread frontend/express/public/localization/dashboard/dashboard.properties
Comment thread frontend/express/public/javascripts/countly/vue/components/content.js Outdated
Copilot AI review requested due to automatic review settings July 24, 2026 14:15
@coskunaydinoglu
coskunaydinoglu disabled auto-merge July 24, 2026 14:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (3)

frontend/express/public/javascripts/countly/vue/templates/content/UI/content-dynamic-params-input.html:10

  • This template introduces several large inline style attributes (e.g., the URL editor). In the content builder templates, styling is typically done via BEM classes (see frontend/express/public/javascripts/countly/vue/templates/content/UI/content-sidebar-input.html:1-118) rather than inline CSS. Please move these styles into the appropriate SCSS/CSS and keep the template focused on structure.
  <div
    ref="editor"
    class="cly-vue-content-dynamic-params-input__editor"
    spellcheck="false"
    style="background: #FFFFFF; border: 1px solid #DBDDE1; border-radius: 4px; padding: 5px 12px; min-height: 30px; max-height: 110px; overflow-y: auto; font-size: 14px; line-height: 20px; color: #383A3F; white-space: pre-wrap; word-break: break-all; outline: none;"
    :contenteditable="!disabled"

frontend/express/public/javascripts/countly/vue/components/content.js:841

  • The token styling is hard-coded as CSS strings in JS (CONTENT_DYNAMIC_PARAM_NODE_STYLE / CONTENT_STATIC_PARAM_NODE_STYLE). For maintainability and theming consistency, consider using CSS classes/modifiers (e.g., __param--dynamic / __param--static) and define styles in SCSS instead of style.cssText.
    const CONTENT_DYNAMIC_PARAM_NODE_CLASS = 'cly-vue-content-dynamic-params-input__param';
    const CONTENT_DYNAMIC_PARAM_NODE_STYLE = 'background: #E1EFFF; color: #0166D6; border-radius: 3px; padding: 0 3px; cursor: pointer;';
    const CONTENT_STATIC_PARAM_NODE_STYLE = 'background: #E2E4E8; color: #383A3F; border-radius: 3px; padding: 0 3px; cursor: pointer;';

frontend/express/public/javascripts/countly/vue/components/content.js:1066

  • onEditorPaste prevents the default paste and relies on document.execCommand('insertText'), which is deprecated and may fail in some browsers/environments—resulting in paste doing nothing. Add a fallback using the Selection/Range API when execCommand is unavailable or returns false.
            onEditorPaste(event) {
                // paste as plain text so foreign markup never enters the editor
                event.preventDefault();

                const clipboard = event.clipboardData || window.clipboardData;
                const text = clipboard ? (clipboard.getData('text/plain') || '').replace(/[\r\n]+/g, '') : '';

                if (text) {
                    document.execCommand('insertText', false, text);
                }
            },

Comment thread frontend/express/public/javascripts/countly/vue/components/content.js Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 28, 2026 10:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (3)

frontend/express/public/javascripts/countly/vue/components/content.js:1147

  • rebuildEditor should pass the query param index into createParamNode so each rendered token can keep a stable data-index for click handling (important when identical pairs exist).
                parsed.params.forEach((pair, index) => {
                    editor.appendChild(document.createTextNode(index === 0 ? '?' : '&'));
                    editor.appendChild(this.createParamNode(pair));
                });

frontend/express/public/javascripts/countly/vue/components/content.js:1048

  • onEditorClick determines the clicked token's index using params.indexOf(pair), which returns the first matching pair. With duplicate query pairs (e.g. a=1&a=1), clicking the second token will open/edit the wrong one. Prefer using a stable data-index stored on the rendered token.
                const pair = paramNode.textContent || '';
                const index = this.parseUrl(this.serializeEditor()).params.indexOf(pair);

                if (index !== -1) {
                    this.openPanelForParam(index, pair);
                }

frontend/express/public/javascripts/countly/vue/components/content.js:977

  • createParamNode doesn't persist the query parameter's position, so click handling has to fall back to indexOf(pair) (which breaks with duplicates). Store the index on the created node so it can be read back on click.

This issue also appears on line 1144 of the same file.

            createParamNode(pair) {
                const eqIndex = pair.indexOf('=');
                const rawValue = eqIndex === -1 ? '' : pair.slice(eqIndex + 1);
                const decodedValue = this.safeDecode(rawValue);
                const isDynamic = CONTENT_DYNAMIC_PARAM_PLACEHOLDER_RE.test(decodedValue);

Comment on lines +1176 to +1181
catch (error) {
return value;
}
}

serializeEditor() {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants