0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-15 03:01:37 -05:00

Improved display of recipient filter posts list sends column tooltip

refs https://github.com/TryGhost/Team/issues/1025

- added `{{humanize-recipient-filter}}` helper that converts an NQL recipient filter into a more readable format
- updated posts list to use the new helper in the sends column tooltip shown when hovering with the mouse
This commit is contained in:
Kevin Ansfield 2022-09-08 11:03:13 +01:00
parent be70064716
commit 4ad040b4aa
3 changed files with 127 additions and 1 deletions

View file

@ -5,6 +5,7 @@
...attributes
>
{{!-- Title column --}}
{{#if (and this.session.user.isContributor @post.isPublished)}}
<a href={{@post.url}} class="permalink gh-list-data gh-post-list-title" target="_blank" rel="noopener noreferrer">
<h3 class="gh-content-entry-title">
@ -129,12 +130,14 @@
{{/if}}
{{/if}}
{{!-- Sends/Opens columns --}}
{{#if (and (not-eq this.settings.membersSignupAccess "none") (not-eq this.settings.editorDefaultEmailRecipients "disabled") (not this.session.user.isContributor))}}
{{#if (and this.feature.emailAnalytics (eq @post.displayName "post"))}}
{{!-- Sends column --}}
<LinkTo @route="editor.edit" @models={{array @post.displayName @post.id}} class="permalink gh-list-data gh-post-list-recipients">
<div class="flex fw4">
{{#if (eq @post.email.status "submitted")}}
<span class="flex" data-tooltip="{{capitalize @post.email.recipientFilter}} members">
<span class="flex" data-tooltip={{humanize-recipient-filter @post.email.recipientFilter}}>
<span class="darkgrey fw5 gh-content-email-stats">{{format-number @post.email.emailCount}}</span>
<span class="midgrey-l2 fw4 gh-content-email-stats-mobile">{{gh-pluralize @post.email.emailCount "send"}}</span>
</span>
@ -144,6 +147,7 @@
</div>
</LinkTo>
{{!-- Opens column --}}
<LinkTo @route="editor.edit" @models={{array @post.displayName @post.id}} class="permalink gh-list-data gh-post-list-opens">
{{#if (and @post.email.trackOpens (eq @post.email.status "submitted"))}}
<div class="flex">

View file

@ -0,0 +1,53 @@
import {capitalize} from '@ember/string';
import {helper} from '@ember/component/helper';
import {pluralize} from 'ember-inflector';
// NOTE: this only works for the limited set of filters that can be generated
// via the publishing UI. Falls back to outputting the raw filter
export default helper(function humanizeRecipientFilter([filter = '']/*, hash*/) {
const parts = filter.split(',');
if (parts.includes('status:free') && parts.includes('status:-free')) {
return 'All members';
}
let outputParts = [];
if (parts.includes('status:free')) {
outputParts.push('Free members');
} else if (parts.includes('status:-free')) {
outputParts.push('Paid members');
}
const labelsArrayRegex = /labels:\[(.*?)\]/;
const labelRegex = /label:(.*?)(?:,|$)/g;
if (labelsArrayRegex.test(filter)) {
const [, labelsList] = filter.match(labelsArrayRegex);
const labels = labelsList.split(',');
outputParts.push(`${pluralize(labels.length, 'Label', {withoutCount: true})}: ${labels.map(capitalize).join(', ')}`);
} else if (labelRegex.test(filter)) {
filter.match(labelRegex); // weird JS thing, `matchAll` doesn't pick up all matches without this
const labels = [...filter.matchAll(labelRegex)].map(([, label]) => label);
outputParts.push(`${pluralize(labels.length, 'Label', {withoutCount: true})}: ${labels.map(capitalize).join(', ')}`);
}
const productsArrayRegex = /products:\[(.*?)\]/;
const productRegex = /product:(.*?)(?:,|$)/g;
if (productsArrayRegex.test(filter)) {
const [, productsList] = filter.match(productsArrayRegex);
const products = productsList.split(',');
outputParts.push(`${pluralize(products.length, 'Product', {withoutCount: true})}: ${products.map(capitalize).join(', ')}`);
} else if (productRegex.test(filter)) {
filter.match(productRegex); // weird JS thing, `matchAll` doesn't pick up all matches without this
const products = [...filter.matchAll(productRegex)].map(([, product]) => product);
outputParts.push(`${pluralize(products.length, 'Product', {withoutCount: true})}: ${products.map(capitalize).join(', ')}`);
}
if (!outputParts.length) {
return filter;
}
return outputParts.join(' & ');
});

View file

@ -0,0 +1,69 @@
import {describe, it} from 'mocha';
import {expect} from 'chai';
import {hbs} from 'ember-cli-htmlbars';
import {render} from '@ember/test-helpers';
import {setupRenderingTest} from 'ember-mocha';
describe('Integration: Helper: humanize-recipient-filter', function () {
setupRenderingTest();
it('handles all members', async function () {
await render(hbs`{{humanize-recipient-filter "status:free,status:-free"}}`);
expect(this.element.textContent.trim()).to.equal('All members');
});
it('handles free members', async function () {
await render(hbs`{{humanize-recipient-filter "status:free"}}`);
expect(this.element.textContent.trim()).to.equal('Free members');
});
it('handles paid members', async function () {
await render(hbs`{{humanize-recipient-filter "status:-free"}}`);
expect(this.element.textContent.trim()).to.equal('Paid members');
});
it('handles free members and labels array', async function () {
await render(hbs`{{humanize-recipient-filter "status:free,labels:[one,two]"}}`);
expect(this.element.textContent.trim()).to.equal('Free members & Labels: One, Two');
});
it('handles free members and individual labels', async function () {
await render(hbs`{{humanize-recipient-filter "status:free,label:one,label:two"}}`);
expect(this.element.textContent.trim()).to.equal('Free members & Labels: One, Two');
});
it('handles paid members and labels array', async function () {
await render(hbs`{{humanize-recipient-filter "status:-free,labels:[one,two]"}}`);
expect(this.element.textContent.trim()).to.equal('Paid members & Labels: One, Two');
});
it('handles paid members and individual labels', async function () {
await render(hbs`{{humanize-recipient-filter "status:-free,label:one,label:two"}}`);
expect(this.element.textContent.trim()).to.equal('Paid members & Labels: One, Two');
});
it('handles just labels', async function () {
await render(hbs`{{humanize-recipient-filter "label:one,label:two"}}`);
expect(this.element.textContent.trim()).to.equal('Labels: One, Two');
});
it('handles paid members and products array', async function () {
await render(hbs`{{humanize-recipient-filter "status:-free,products:[one,two]"}}`);
expect(this.element.textContent.trim()).to.equal('Paid members & Products: One, Two');
});
it('handles paid members and individual products', async function () {
await render(hbs`{{humanize-recipient-filter "status:-free,product:one,product:two"}}`);
expect(this.element.textContent.trim()).to.equal('Paid members & Products: One, Two');
});
it('handles just products', async function () {
await render(hbs`{{humanize-recipient-filter "product:one,product:two"}}`);
expect(this.element.textContent.trim()).to.equal('Products: One, Two');
});
it('handles labels and products', async function () {
await render(hbs`{{humanize-recipient-filter "label:one,product:two"}}`);
expect(this.element.textContent.trim()).to.equal('Label: One & Product: Two');
});
});