From 247f24394d738e383a73617336d7999357c726ad Mon Sep 17 00:00:00 2001 From: Rishabh Date: Tue, 10 Aug 2021 17:41:22 +0530 Subject: [PATCH] Added dynamic columns to member list from filter UI refs https://github.com/TryGhost/Team/issues/943 - adds new columns to member list table based on selected filters in UI - handles dynamic columns in members list with formatted output like for labels - works behind the filtering feature flag --- .../app/components/gh-members-filter-labs.hbs | 3 +- .../app/components/gh-members-filter-labs.js | 40 ++++++----- .../gh-members-list-item-column-labs.hbs | 45 ++++++++++++ .../gh-members-list-item-column-labs.js | 12 ++++ .../components/gh-members-list-item-labs.hbs | 71 +++++++++++++++++++ ghost/admin/app/controllers/members.js | 20 +++++- ghost/admin/app/routes/members.js | 2 +- ghost/admin/app/templates/members.hbs | 52 +++++++------- 8 files changed, 199 insertions(+), 46 deletions(-) create mode 100644 ghost/admin/app/components/gh-members-list-item-column-labs.hbs create mode 100644 ghost/admin/app/components/gh-members-list-item-column-labs.js create mode 100644 ghost/admin/app/components/gh-members-list-item-labs.hbs diff --git a/ghost/admin/app/components/gh-members-filter-labs.hbs b/ghost/admin/app/components/gh-members-filter-labs.hbs index b90111e1c0..d54a41eb28 100644 --- a/ghost/admin/app/components/gh-members-filter-labs.hbs +++ b/ghost/admin/app/components/gh-members-filter-labs.hbs @@ -53,7 +53,8 @@ type="button" class="gh-btn gh-btn-text gh-btn-link green gh-btn-icon gh-add-filter" title="Delete filter" - {{on "click" (fn this.deleteFilter filter.id)}} + {{!-- {{on "click" (fn this.deleteFilter filter.id)}} --}} + {{action "deleteFilter" filter.id}} > {{svg-jar "close"}} diff --git a/ghost/admin/app/components/gh-members-filter-labs.js b/ghost/admin/app/components/gh-members-filter-labs.js index ded45c89a4..2c60eca7bc 100644 --- a/ghost/admin/app/components/gh-members-filter-labs.js +++ b/ghost/admin/app/components/gh-members-filter-labs.js @@ -8,34 +8,35 @@ const FILTER_PROPERTIES = [ // Basic {label: 'Name', name: 'name', group: 'Basic'}, {label: 'Email', name: 'email', group: 'Basic'}, - {label: 'Location', name: 'location', group: 'Basic'}, + // {label: 'Location', name: 'location', group: 'Basic'}, {label: 'Newsletter subscription status', name: 'subscribed', group: 'Basic'}, {label: 'Label', name: 'label', group: 'Basic'}, // Member subscription - {label: 'Member status', name: 'member-status', group: 'Subscription'}, - {label: 'Tier', name: 'tier', group: 'Subscription'}, - {label: 'Billing period', name: 'billing-period', group: 'Subscription'}, + {label: 'Member status', name: 'status', group: 'Subscription'}, + // {label: 'Tier', name: 'tier', group: 'Subscription'}, + // {label: 'Billing period', name: 'billing-period', group: 'Subscription'}, // Emails - {label: 'Emails sent (all time)', name: 'x', group: 'Email'}, - {label: 'Emails opened (all time)', name: 'x', group: 'Email'}, - {label: 'Open rate (all time)', name: 'x', group: 'Email'}, - {label: 'Emails sent (30 days)', name: 'x', group: 'Email'}, - {label: 'Emails opened (30 days)', name: 'x', group: 'Email'}, - {label: 'Open rate (30 days)', name: 'x', group: 'Email'}, - {label: 'Emails sent (60 days)', name: 'x', group: 'Email'}, - {label: 'Emails opened (60 days)', name: 'x', group: 'Email'}, - {label: 'Open rate (60 days)', name: 'x', group: 'Email'}, - {label: 'Stripe subscription status', name: 'status', group: 'Email'} + {label: 'Emails sent (all time)', name: 'email_count', group: 'Email'}, + {label: 'Emails opened (all time)', name: 'email_opened_count', group: 'Email'}, + {label: 'Open rate (all time)', name: 'email_open_rate', group: 'Email'}, + // {label: 'Emails sent (30 days)', name: 'x', group: 'Email'}, + // {label: 'Emails opened (30 days)', name: 'x', group: 'Email'}, + // {label: 'Open rate (30 days)', name: 'x', group: 'Email'}, + // {label: 'Emails sent (60 days)', name: 'x', group: 'Email'}, + // {label: 'Emails opened (60 days)', name: 'x', group: 'Email'}, + // {label: 'Open rate (60 days)', name: 'x', group: 'Email'}, + {label: 'Stripe subscription status', name: 'stripe-status', group: 'Email'} ]; const FILTER_RELATIONS = [ {label: 'is', name: 'is'}, {label: 'is not', name: 'is-not'}, - {label: 'contains', name: 'contains'}, - {label: 'exists', name: 'exists'}, - {label: 'does not exist', name: 'does-not-exist'} + {label: 'in', name: 'in'} + // {label: 'contains', name: 'contains'}, + // {label: 'exists', name: 'exists'}, + // {label: 'does not exist', name: 'does-not-exist'} ]; export default class GhMembersFilterLabsComponent extends Component { @@ -71,7 +72,8 @@ export default class GhMembersFilterLabsComponent extends Component { let query = ''; filters.forEach((filter) => { const relationStr = filter.relation === 'is-not' ? '-' : ''; - query += `${filter.type}:${relationStr}'${filter.value}',`; + const filterValue = filter.value.includes(' ') ? `'${filter.value}'` : filter.value; + query += `${filter.type}:${relationStr}${filterValue}+`; }); return query.slice(0, -1); } @@ -103,6 +105,6 @@ export default class GhMembersFilterLabsComponent extends Component { @action applyFilter() { const query = this.generateNqlFilter(this.filters); - this.args.onApplyFilter(query); + this.args.onApplyFilter(query, this.filters); } } diff --git a/ghost/admin/app/components/gh-members-list-item-column-labs.hbs b/ghost/admin/app/components/gh-members-list-item-column-labs.hbs new file mode 100644 index 0000000000..7ba9fbdcb6 --- /dev/null +++ b/ghost/admin/app/components/gh-members-list-item-column-labs.hbs @@ -0,0 +1,45 @@ +{{#if (eq @filterColumn 'label')}} + + {{labels}} + +{{/if}} + +{{#if (eq @filterColumn 'status')}} + + {{#if (not (is-empty @member.status))}} + {{capitalize @member.status}} + {{else}} + - + {{/if}} + +{{/if}} + +{{#if (eq @filterColumn 'email_count')}} + + {{#if (not (is-empty @member.emailCount))}} + {{@member.emailCount}} + {{else}} + - + {{/if}} + +{{/if}} + +{{#if (eq @filterColumn 'email_opened_count')}} + + {{#if (not (is-empty @member.emailOpenedCount))}} + {{@member.emailOpenedCount}} + {{else}} + - + {{/if}} + +{{/if}} + +{{#if (eq @filterColumn 'subscribed')}} + + {{#if (not (is-empty @member.subscribed))}} + {{@member.subscribed}} + {{else}} + - + {{/if}} + +{{/if}} diff --git a/ghost/admin/app/components/gh-members-list-item-column-labs.js b/ghost/admin/app/components/gh-members-list-item-column-labs.js new file mode 100644 index 0000000000..6b093e5c66 --- /dev/null +++ b/ghost/admin/app/components/gh-members-list-item-column-labs.js @@ -0,0 +1,12 @@ +import Component from '@glimmer/component'; + +export default class GhMembersListItemColumnLabs extends Component { + constructor(...args) { + super(...args); + } + + get labels() { + const labelData = this.args.member.get('labels') || []; + return labelData.map(label => label.name).join(','); + } +} diff --git a/ghost/admin/app/components/gh-members-list-item-labs.hbs b/ghost/admin/app/components/gh-members-list-item-labs.hbs new file mode 100644 index 0000000000..ab1a45fd09 --- /dev/null +++ b/ghost/admin/app/components/gh-members-list-item-labs.hbs @@ -0,0 +1,71 @@ + + {{#if @member.is_loading}} +
+
+
+
+
+
+
+ {{#each @filterColumns as |filterColumn|}} +
+ {{/each}} + {{!--
+
+
+
+
+
--}} + {{else}} + +
+ +
+

{{or @member.name @member.email}}

+ {{#if @member.name}} +

{{@member.email}}

+ {{/if}} +
+
+
+ + {{#if (feature "emailAnalytics")}} + + {{#if (not (is-empty @member.emailOpenRate))}} + {{@member.emailOpenRate}}% + {{else}} + N/A + {{/if}} + + {{/if}} + + + {{#if (and @member.geolocation @member.geolocation.country)}} + {{#if (and (eq @member.geolocation.country_code "US") @member.geolocation.region)}} + {{@member.geolocation.region}}, US + {{else}} + {{#if @member.geolocation.country}} + {{@member.geolocation.country}} + {{else}} + Unknown + {{/if}} + {{/if}} + {{else}} + Unknown + {{/if}} + + + + {{#if @member.createdAtUTC}} +
{{moment-format @member.createdAtUTC "D MMM YYYY"}}
+
{{moment-from-now @member.createdAtUTC}}
+ {{/if}} +
+ {{#each @filterColumns as |filterColumn|}} + + {{!-- + Unknown + --}} + {{/each}} + {{/if}} + diff --git a/ghost/admin/app/controllers/members.js b/ghost/admin/app/controllers/members.js index 170805de72..b374d44f9d 100644 --- a/ghost/admin/app/controllers/members.js +++ b/ghost/admin/app/controllers/members.js @@ -5,6 +5,7 @@ import ghostPaths from 'ghost-admin/utils/ghost-paths'; import moment from 'moment'; import {A} from '@ember/array'; import {action} from '@ember/object'; +import {capitalize} from '@ember/string'; import {ghPluralize} from 'ghost-admin/helpers/gh-pluralize'; import {resetQueryParams} from 'ghost-admin/helpers/reset-query-params'; import {inject as service} from '@ember/service'; @@ -51,6 +52,7 @@ export default class MembersController extends Controller { @tracked modalLabel = null; @tracked showLabelModal = false; @tracked showDeleteMembersModal = false; + @tracked filters = A([]); @tracked _availableLabels = A([]); @@ -151,6 +153,21 @@ export default class MembersController extends Controller { return !!(this.label || this.paidParam || this.searchParam || this.filterParam); } + get filterColumns() { + const defaultColumns = ['name', 'email']; + return this.filters.map((filter) => { + return filter.type; + }).filter((f, idx, arr) => { + return arr.indexOf(f) === idx; + }).filter(d => !defaultColumns.includes(d)); + } + + get filterColumnLabels() { + return this.filterColumns.map((d) => { + return capitalize(d.replace(/_/g, ' ')); + }); + } + getApiQueryObject({params, extraFilters = []} = {}) { let {label, paidParam, searchParam, filterParam} = params ? params : this; @@ -195,7 +212,8 @@ export default class MembersController extends Controller { } @action - applyFilter(filterStr) { + applyFilter(filterStr, filters) { + this.filters = filters; this.filterParam = filterStr || null; } diff --git a/ghost/admin/app/routes/members.js b/ghost/admin/app/routes/members.js index e20a0af848..3c86b907e8 100644 --- a/ghost/admin/app/routes/members.js +++ b/ghost/admin/app/routes/members.js @@ -9,7 +9,7 @@ export default class MembersRoute extends AuthenticatedRoute { searchParam: {refreshModel: true, replace: true}, paidParam: {refreshModel: true}, orderParam: {refreshModel: true}, - filterParam: {refreshModel: true, replace: true} + filterParam: {refreshModel: true} }; // redirect to posts screen if: diff --git a/ghost/admin/app/templates/members.hbs b/ghost/admin/app/templates/members.hbs index 7abc669bea..3f71474d92 100644 --- a/ghost/admin/app/templates/members.hbs +++ b/ghost/admin/app/templates/members.hbs @@ -88,30 +88,34 @@ {{#unless this.members.loading}}
{{#if (feature "membersFiltering")}} -
- - - - - - - - - - - - - - - - - - -
{{this.listHeader}}Open rateLocationCreatedOpen rateLocationCreatedOpen rateLocationCreated
-
+
+ + + + + + + + {{#each this.filterColumnLabels as |filterColumn|}} + + {{/each}} + {{!-- + + + + + --}} + + + + + +
{{this.listHeader}}Open rateLocationCreated{{filterColumn}}Open rateLocationCreatedOpen rateLocationCreated
+
{{else}}