Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions io/www/src/fragment/transformers/customize.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ function countMatchedPznTokens(tags, tokens) {
return n;
}

/**
* Region match beats country match when resolving ties (applies to both pzn and promo variations).
* @param {{ region?: boolean, country?: boolean }|null|undefined} geo
* @returns {number}
*/
function geoMatchScore(geo) {
if (!geo) return 0;
return (geo.region ? 2 : 0) + (geo.country ? 1 : 0);
}

/**
* Non-zero score means this variation applies. Higher is better: each matched request pzn token
* dominates; region and country matches add smaller tie-break weight.
Expand All @@ -137,7 +147,7 @@ function personalizationMatchScore(pznTags, { regionLocale, country, pzn }) {
if (matchedTokens === 0 && !geo) {
return 0;
}
return matchedTokens * 100 + (geo?.region ? 20 : 0) + (geo?.country ? 10 : 0);
return matchedTokens * 100 + geoMatchScore(geo) * 10;
}

function findPersonalizationVariation(variations, customizeContext) {
Expand Down Expand Up @@ -179,6 +189,8 @@ function promoProjectLabel(project) {
}

// Upper bound for probing suffixed promo variation paths (`-2`, `-3`, ...) per fragment.
// Kept in sync by hand with the same constant + `-N` suffix convention in
// studio/src/promotions/promotion-variations.js (separate runtime, no shared import).
const MAX_PROMO_VARIATIONS_PER_FRAGMENT = 50;

// Collects same fragment geo promos to select the best match.
Expand Down Expand Up @@ -208,7 +220,7 @@ function selectBestPromoVariation(candidates, { regionLocale, country }) {
}
const geo = matchesGeo(pznTags, { regionLocale, country });
if (!geo) continue;
const score = (geo.region ? 2 : 0) + (geo.country ? 1 : 0);
const score = geoMatchScore(geo);
if (score > bestScore) {
bestScore = score;
best = candidate;
Expand Down
57 changes: 57 additions & 0 deletions studio/src/common/utils/selectable-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Shared building blocks for search + select-all + indeterminate-checkbox list UIs
* (e.g. mas-promo-variation-geos, mas-translation-languages).
*/

/**
* Extracts the new search query value from a search input/change event.
* @param {Event} e
* @returns {string}
*/
export function handleSearchInput(e) {
return e.target.value;
}

/**
* Filters items by a case-insensitive substring match against extracted searchable text.
* @param {Array} items
* @param {string} searchQuery
* @param {(item: any) => string} getSearchableText
* @returns {Array}
*/
export function filterBySearchQuery(items, searchQuery, getSearchableText) {
if (!searchQuery) return items;
const query = searchQuery.toLowerCase();
return items.filter((item) => getSearchableText(item).toLowerCase().includes(query));
}

/**
* @param {number} selectableCount
* @param {number} selectedCount
* @returns {boolean}
*/
export function computeSelectAllChecked(selectableCount, selectedCount) {
return selectableCount > 0 && selectedCount === selectableCount;
}

/**
* @param {number} selectableCount
* @param {number} selectedCount
* @returns {boolean}
*/
export function computeSelectAllIndeterminate(selectableCount, selectedCount) {
return selectedCount > 0 && selectedCount < selectableCount;
}

/**
* Formats a "N <noun> selected" / "N <noun>" label, singularizing correctly at 1.
* @param {number} selectedCount
* @param {number} totalCount
* @param {string} singular
* @param {string} [plural]
* @returns {string}
*/
export function computeSelectionCountLabel(selectedCount, totalCount, singular, plural = `${singular}s`) {
if (selectedCount) return `${selectedCount} ${selectedCount === 1 ? singular : plural} selected`;
return `${totalCount} ${totalCount === 1 ? singular : plural}`;
}
6 changes: 3 additions & 3 deletions studio/src/mas-fragment-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -1833,10 +1833,10 @@ export default class MasFragmentEditor extends LitElement {
</div>`;
}

displayPromoVariationGeos(clazz) {
get promoVariationGeosTemplate() {
const geoCodes = this.#promoVariationGeoCodes();
if (!geoCodes.length) return nothing;
return html`<div class="${clazz}">
return html`<div class="locale-variation-header">
<span>Geos: <strong>${geoCodes.join(', ')}</strong></span>
</div>`;
}
Expand Down Expand Up @@ -1870,7 +1870,7 @@ export default class MasFragmentEditor extends LitElement {
return nothing;
}
if (this.isPromoVariationFragment()) {
return this.displayPromoVariationGeos('locale-variation-header');
return this.promoVariationGeosTemplate;
}
if (!this.editorContextStore.isVariation(this.fragment.id)) {
return nothing;
Expand Down
27 changes: 15 additions & 12 deletions studio/src/promotions/mas-promo-variation-geos.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { LitElement, html, nothing } from 'lit';
import { styles } from './mas-promo-variation-geos.css.js';
import {
handleSearchInput,
filterBySearchQuery,
computeSelectAllChecked,
computeSelectAllIndeterminate,
computeSelectionCountLabel,
} from '../common/utils/selectable-list.js';

class MasPromoVariationGeos extends LitElement {
static styles = styles;
Expand Down Expand Up @@ -28,23 +35,23 @@ class MasPromoVariationGeos extends LitElement {
}

get filteredGeos() {
if (!this.searchQuery) return this.geos;
const query = this.searchQuery.toLowerCase();
return this.geos.filter((geo) => geo.toLowerCase().includes(query));
return filterBySearchQuery(this.geos, this.searchQuery, (geo) => geo);
}

handleSearch(e) {
this.searchQuery = handleSearchInput(e);
}

get selectAllChecked() {
return this.selectableGeos.length > 0 && this.value.length === this.selectableGeos.length;
return computeSelectAllChecked(this.selectableGeos.length, this.value.length);
}

get selectAllIndeterminate() {
return this.value.length > 0 && this.value.length < this.selectableGeos.length;
return computeSelectAllIndeterminate(this.selectableGeos.length, this.value.length);
}

get numberOfGeos() {
const count = this.value.length;
if (count) return `${count} ${count === 1 ? 'geo' : 'geos'} selected`;
return `${this.geos.length} ${this.geos.length === 1 ? 'geo' : 'geos'}`;
return computeSelectionCountLabel(this.value.length, this.geos.length, 'geo');
}

get showInheritHint() {
Expand All @@ -60,10 +67,6 @@ class MasPromoVariationGeos extends LitElement {
return geo.split('/').pop() || geo;
}

handleSearch(e) {
this.searchQuery = e.target.value;
}

selectAll(e) {
e.stopPropagation();
this.emitChange(e.target.checked ? [...this.selectableGeos] : []);
Expand Down
11 changes: 8 additions & 3 deletions studio/src/promotions/mas-promotions-items-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ class MasPromotionsItemsTable extends LitElement {
async #syncExistingPromoVariations(items, signal) {
if (signal.aborted) return;
const promoTag = this.#promotionTagId;
if (!promoTag || !this.repository?.aem?.sites?.cf?.fragments?.getByPath) {
if (!promoTag || !this.repository?.aem?.sites?.cf?.fragments?.search) {
if (signal.aborted) return;
this.existingPromoVariationGeosByPath = new Map();
this.existingPromoVariationsByPath = new Map();
Expand Down Expand Up @@ -429,8 +429,13 @@ class MasPromotionsItemsTable extends LitElement {
try {
this.createPromoVariationLoading = true;
showToast('Creating promo variation...');
const created = await createPromoVariation(this.repository.aem, item.id, promoTag, geoTags, (store) =>
this.repository.refreshFragment(store),
const created = await createPromoVariation(
this.repository.aem,
item.id,
promoTag,
geoTags,
(store) => this.repository.refreshFragment(store),
() => this.repository.loadPromotions(),
);
showToast('Promo variation created', 'positive');
const previousGeos = this.existingPromoVariationGeosByPath.get(item.path) || [];
Expand Down
Loading
Loading