mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
✨ Added Members Filtering feature
no-issue This adds the ability to apply complex filters to members, as well as to perform bulk actions on the filtered set, including unsubscribing, adding & removing labels.
This commit is contained in:
parent
a5f158c061
commit
592e3df794
9 changed files with 212 additions and 361 deletions
|
@ -72,21 +72,13 @@
|
|||
|
||||
<GhFormGroup @classNames="gh-member-labels">
|
||||
<label for="label-input">Labels</label>
|
||||
{{#if (feature "membersFiltering")}}
|
||||
<GhMemberLabelInputLabs
|
||||
@onChange={{action "setLabels"}}
|
||||
@onLabelEdit={{@onLabelEdit}}
|
||||
@labels={{this.member.labels}}
|
||||
@triggerId="label-input"
|
||||
data-test-input=""
|
||||
/>
|
||||
{{else}}
|
||||
<GhMemberLabelInput
|
||||
@onChange={{action "setLabels"}}
|
||||
@labels={{this.member.labels}}
|
||||
@triggerId="label-input"
|
||||
data-test-input="" />
|
||||
{{/if}}
|
||||
<GhMemberLabelInputLabs
|
||||
@onChange={{action "setLabels"}}
|
||||
@onLabelEdit={{@onLabelEdit}}
|
||||
@labels={{this.member.labels}}
|
||||
@triggerId="label-input"
|
||||
data-test-input=""
|
||||
/>
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @errors={{this.member.errors}} @hasValidated={{this.member.hasValidated}} @property="note" @classNames="mb0 gh-member-note">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<tr>
|
||||
<tr data-test-list='members-list-item' data-test-member={{@member.id}}>
|
||||
{{#if @member.is_loading}}
|
||||
<div class="gh-list-data gh-members-list-basic gh-list-loadingcell">
|
||||
<div class="gh-list-loading-title"></div>
|
||||
|
|
|
@ -1,188 +1,124 @@
|
|||
{{#if (feature "membersFiltering")}}
|
||||
<tr>
|
||||
{{#if @member.is_loading}}
|
||||
<div class="gh-list-data gh-members-list-basic gh-list-loadingcell">
|
||||
<div class="gh-list-loading-title"></div>
|
||||
<div class="gh-list-loading-detail"></div>
|
||||
</div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
{{else}}
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data">
|
||||
<div class="flex items-center">
|
||||
<GhMemberAvatar @member={{@member}} @containerClass="w9 h9 mr3 flex-shrink-0" />
|
||||
<div class="w-80">
|
||||
<h3 class="ma0 pa0 gh-members-list-name {{if (not @member.name) "gh-members-name-noname"}}">{{or @member.name @member.email}}</h3>
|
||||
{{#if @member.name}}
|
||||
<p class="ma0 pa0 middarkgrey f8 gh-members-list-email">{{@member.email}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
<tr>
|
||||
{{#if @member.is_loading}}
|
||||
<div class="gh-list-data gh-members-list-basic gh-list-loadingcell">
|
||||
<div class="gh-list-loading-title"></div>
|
||||
<div class="gh-list-loading-detail"></div>
|
||||
</div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
<div class="gh-list-data"></div>
|
||||
{{else}}
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data">
|
||||
<div class="flex items-center">
|
||||
<GhMemberAvatar @member={{@member}} @containerClass="w9 h9 mr3 flex-shrink-0" />
|
||||
<div class="w-80">
|
||||
<h3 class="ma0 pa0 gh-members-list-name {{if (not @member.name) "gh-members-name-noname"}}">{{or @member.name @member.email}}</h3>
|
||||
{{#if @member.name}}
|
||||
<p class="ma0 pa0 middarkgrey f8 gh-members-list-email">{{@member.email}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</LinkTo>
|
||||
</div>
|
||||
</LinkTo>
|
||||
|
||||
{{#if (feature "emailAnalytics")}}
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8 {{if (not @member.name) "gh-members-list-open-rate-noname"}}">
|
||||
{{#if (not (is-empty @member.emailOpenRate))}}
|
||||
<span class="gh-members-list-open-rate-mobile">{{@member.emailOpenRate}}%</span>
|
||||
{{else}}
|
||||
<span class="midlightgrey">N/A</span>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8 {{if (not @member.name) "gh-members-geolocation-noname"}}">
|
||||
{{#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}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if (feature "emailAnalytics")}}
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8 {{if (not @member.name) "gh-members-list-open-rate-noname"}}">
|
||||
{{#if (not (is-empty @member.emailOpenRate))}}
|
||||
<span class="gh-members-list-open-rate-mobile">{{@member.emailOpenRate}}%</span>
|
||||
{{else}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#if @member.createdAtUTC}}
|
||||
<div>{{moment-format @member.createdAtUTC "D MMM YYYY"}}</div>
|
||||
<div class="midlightgrey gh-members-list-subscribed-moment">{{moment-from-now @member.createdAtUTC}}</div>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
|
||||
{{#if (feature "emailAnalytics")}}
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#if (not (is-empty @member.emailOpenRate))}}
|
||||
<span class="gh-members-list-open-rate-mobile">{{@member.emailOpenRate}}%</span>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#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}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#if @member.createdAtUTC}}
|
||||
<div>{{moment-format @member.createdAtUTC "D MMM YYYY"}}</div>
|
||||
<div class="midlightgrey gh-members-list-subscribed-moment">{{moment-from-now @member.createdAtUTC}}</div>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
|
||||
{{#if (feature "emailAnalytics")}}
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#if (not (is-empty @member.emailOpenRate))}}
|
||||
<span class="gh-members-list-open-rate-mobile">{{@member.emailOpenRate}}%</span>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#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}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#if @member.createdAtUTC}}
|
||||
<div>{{moment-format @member.createdAtUTC "D MMM YYYY"}}</div>
|
||||
<div class="midlightgrey gh-members-list-subscribed-moment">{{moment-from-now @member.createdAtUTC}}</div>
|
||||
<span class="midlightgrey">N/A</span>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</tr>
|
||||
{{else}}
|
||||
<li class="gh-list-row gh-members-list-item {{if @member.is_loading "loading"}}" data-test-member={{@member.id}} ...attributes>
|
||||
{{#if @member.is_loading}}
|
||||
<div class="gh-list-data gh-members-list-basic gh-list-loadingcell">
|
||||
<div class="gh-list-loading-title"></div>
|
||||
<div class="gh-list-loading-detail"></div>
|
||||
</div>
|
||||
<div class="gh-list-data gh-members-list-open-rate gh-list-cellwidth-10"></div>
|
||||
<div class="gh-list-data gh-members-list-geolocation gh-list-cellwidth-10"></div>
|
||||
<div class="gh-list-data gh-members-list-subscribed-at gh-list-cellwidth-10"></div>
|
||||
<div class="gh-list-data gh-members-list-chevron gh-list-cellwidth-chevron"></div>
|
||||
{{else}}
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data gh-members-list-basic">
|
||||
<div class="flex items-center">
|
||||
<GhMemberAvatar @member={{@member}} @containerClass="w9 h9 mr3 flex-shrink-0" />
|
||||
<div class="w-80">
|
||||
<h3 class="ma0 pa0 gh-members-list-name {{if (not @member.name) "gh-members-name-noname"}}">{{or @member.name @member.email}}</h3>
|
||||
{{#if @member.name}}
|
||||
<p class="ma0 pa0 middarkgrey f8 gh-members-list-email">{{@member.email}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</LinkTo>
|
||||
|
||||
{{#if (feature "emailAnalytics")}}
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data gh-members-list-open-rate middarkgrey f8 {{if (not @member.name) "gh-members-list-open-rate-noname"}}">
|
||||
{{#if (not (is-empty @member.emailOpenRate))}}
|
||||
<span class="gh-members-list-open-rate-mobile">{{@member.emailOpenRate}}%</span>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data gh-members-list-geolocation middarkgrey f8 {{if (not @member.name) "gh-members-geolocation-noname"}}">
|
||||
{{#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}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8 {{if (not @member.name) "gh-members-geolocation-noname"}}">
|
||||
{{#if (and @member.geolocation @member.geolocation.country)}}
|
||||
{{#if (and (eq @member.geolocation.country_code "US") @member.geolocation.region)}}
|
||||
{{@member.geolocation.region}}, US
|
||||
{{else}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{#if @member.geolocation.country}}
|
||||
{{@member.geolocation.country}}
|
||||
{{else}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data gh-members-list-subscribed-at middarkgrey f8 {{if (not @member.name) "gh-members-subscribed-noname"}}">
|
||||
{{#if @member.createdAtUTC}}
|
||||
<div>{{moment-format @member.createdAtUTC "D MMM YYYY"}}</div>
|
||||
<div class="midlightgrey gh-members-list-subscribed-moment">{{moment-from-now @member.createdAtUTC}}</div>
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#if @member.createdAtUTC}}
|
||||
<div>{{moment-format @member.createdAtUTC "D MMM YYYY"}}</div>
|
||||
<div class="midlightgrey gh-members-list-subscribed-moment">{{moment-from-now @member.createdAtUTC}}</div>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
|
||||
{{#if (feature "emailAnalytics")}}
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#if (not (is-empty @member.emailOpenRate))}}
|
||||
<span class="gh-members-list-open-rate-mobile">{{@member.emailOpenRate}}%</span>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data gh-list-cellwidth-chevron gh-members-list-chevron">
|
||||
<div class="flex items-center justify-end w-100 h-100">
|
||||
<span class="nr2">{{svg-jar "arrow-right" class="w6 h6 fill-midgrey pa1"}}</span>
|
||||
</div>
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#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}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#if @member.createdAtUTC}}
|
||||
<div>{{moment-format @member.createdAtUTC "D MMM YYYY"}}</div>
|
||||
<div class="midlightgrey gh-members-list-subscribed-moment">{{moment-from-now @member.createdAtUTC}}</div>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
|
||||
{{#if (feature "emailAnalytics")}}
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#if (not (is-empty @member.emailOpenRate))}}
|
||||
<span class="gh-members-list-open-rate-mobile">{{@member.emailOpenRate}}%</span>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#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}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span class="midlightgrey">Unknown</span>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
|
||||
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
|
||||
{{#if @member.createdAtUTC}}
|
||||
<div>{{moment-format @member.createdAtUTC "D MMM YYYY"}}</div>
|
||||
<div class="midlightgrey gh-members-list-subscribed-moment">{{moment-from-now @member.createdAtUTC}}</div>
|
||||
{{/if}}
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</tr>
|
|
@ -197,8 +197,7 @@ export default class MembersController extends Controller {
|
|||
filters.push('status:free');
|
||||
}
|
||||
}
|
||||
|
||||
if (filterParam && this.feature.get('membersFiltering')) {
|
||||
if (filterParam) {
|
||||
filters.push(filterParam);
|
||||
}
|
||||
|
||||
|
|
|
@ -35,16 +35,10 @@ export default class MembersRoute extends AuthenticatedRoute {
|
|||
}
|
||||
|
||||
buildRouteInfoMetadata() {
|
||||
if (this.feature.membersFiltering) {
|
||||
return {
|
||||
titleToken: 'Members',
|
||||
mainClasses: ['gh-main-fullwidth']
|
||||
return {
|
||||
titleToken: 'Members',
|
||||
mainClasses: ['gh-main-fullwidth']
|
||||
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
titleToken: 'Members'
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,6 @@ export default Service.extend({
|
|||
launchComplete: feature('launchComplete', {user: true}),
|
||||
matchHelper: feature('matchHelper'),
|
||||
multipleProducts: feature('multipleProducts'),
|
||||
membersFiltering: feature('membersFiltering', {developer: true}),
|
||||
offers: feature('offers', {developer: true}),
|
||||
oauthLogin: feature('oauthLogin', {developer: true}),
|
||||
emailOnlyPosts: feature('emailOnlyPosts', {developer: true}),
|
||||
|
|
|
@ -3,21 +3,6 @@
|
|||
<h2 class="gh-canvas-title" data-test-screen-title>Members</h2>
|
||||
<section class="view-actions">
|
||||
<div class="view-actions-bottom-row">
|
||||
{{#unless (feature "membersFiltering")}}
|
||||
<GhMembersFilter
|
||||
@selectedLabel={{this.selectedLabel}}
|
||||
@availableLabels={{this.availableLabels}}
|
||||
@onLabelChange={{this.changeLabel}}
|
||||
@onLabelAdd={{this.addLabel}}
|
||||
@onLabelEdit={{this.editLabel}}
|
||||
@selectedPaidParam={{this.selectedPaidParam}}
|
||||
@availablePaidParams={{this.paidParams}}
|
||||
@onPaidParamChange={{this.changePaidParam}}
|
||||
@availableOrders={{this.availableOrders}}
|
||||
@selectedOrder={{this.selectedOrder}}
|
||||
@onOrderChange={{this.changeOrder}}
|
||||
/>
|
||||
{{/unless}}
|
||||
<div class="relative gh-members-header-search">
|
||||
{{svg-jar "search" class="gh-input-search-icon"}}
|
||||
<GhTextInput
|
||||
|
@ -29,7 +14,6 @@
|
|||
</div>
|
||||
|
||||
<div class="view-actions-top-row">
|
||||
{{#if (feature "membersFiltering")}}
|
||||
<GhMembersFilterLabs
|
||||
@isFiltered={{this.isFiltered}}
|
||||
@onApplyFilter={{this.applyFilter}}
|
||||
|
@ -39,7 +23,6 @@
|
|||
@onResetFilter={{this.resetFilter}}
|
||||
@onLabelEdit={{this.editLabel}}
|
||||
/>
|
||||
{{/if}}
|
||||
<span class="dropdown">
|
||||
<GhDropdownButton
|
||||
@dropdownName="members-actions-menu"
|
||||
|
@ -79,23 +62,21 @@
|
|||
</li>
|
||||
{{#if (and this.members.length this.isFiltered)}}
|
||||
<li class="divider"></li>
|
||||
{{#if (feature "membersFiltering")}}
|
||||
<li>
|
||||
<button class="mr2" {{on "click" this.toggleAddMembersLabelModal}} data-test-button="add-label-selected">
|
||||
<span>Add label for selected members ({{this.members.length}})</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="mr2" {{on "click" this.toggleRemoveMembersLabelModal}} data-test-button="remove-label-selected">
|
||||
<span>Remove label from selected members ({{this.members.length}})</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="mr2" {{on "click" this.toggleUnsubscribeMembersModal}} data-test-button="remove-label-selected">
|
||||
<span>Unsubscribe selected members ({{this.members.length}})</span>
|
||||
</button>
|
||||
</li>
|
||||
{{/if}}
|
||||
<li>
|
||||
<button class="mr2" {{on "click" this.toggleAddMembersLabelModal}} data-test-button="add-label-selected">
|
||||
<span>Add label for selected members ({{this.members.length}})</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="mr2" {{on "click" this.toggleRemoveMembersLabelModal}} data-test-button="remove-label-selected">
|
||||
<span>Remove label from selected members ({{this.members.length}})</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="mr2" {{on "click" this.toggleUnsubscribeMembersModal}} data-test-button="remove-label-selected">
|
||||
<span>Unsubscribe selected members ({{this.members.length}})</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
<button class="mr2" {{on "click" this.toggleDeleteMembersModal}} data-test-button="delete-selected">
|
||||
|
@ -112,83 +93,44 @@
|
|||
|
||||
{{#unless this.members.loading}}
|
||||
<section class="view-container">
|
||||
{{#if (feature "membersFiltering")}}
|
||||
{{#if this.members}}
|
||||
<div class="gh-list-scrolling">
|
||||
<table class="gh-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{this.listHeader}}</th>
|
||||
<th>Open rate</th>
|
||||
<th>Location</th>
|
||||
<th>Created</th>
|
||||
{{#each this.filterColumnLabels as |filterColumn|}}
|
||||
<th>{{filterColumn}}</th>
|
||||
{{/each}}
|
||||
</tr>
|
||||
</thead>
|
||||
<VerticalCollection @tagName="tbody" @items={{this.members}} @key="id" @containerSelector=".gh-list-scrolling" @estimateHeight={{69}} @staticHeight={{true}} @bufferSize={{20}} as |member|>
|
||||
<GhMembersListItemLabs
|
||||
@member={{member}}
|
||||
@filterColumns={{this.filterColumns}}
|
||||
data-test-member={{member.id}}
|
||||
/>
|
||||
</VerticalCollection>
|
||||
</table>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="no-posts-box">
|
||||
<div class="no-posts">
|
||||
{{svg-jar "members-placeholder" class="gh-members-placeholder"}}
|
||||
{{#if this.showingAll}}
|
||||
<h3>No members yet</h3>
|
||||
<GhMembersNoMembers @afterCreate={{this.refreshData}} />
|
||||
{{else}}
|
||||
<h3>No members match the current filter</h3>
|
||||
<LinkTo @route="members" @query={{hash filter=null}} class="gh-btn gh-btn-lg">
|
||||
<span>Show all members</span>
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.members}}
|
||||
<div class="gh-list-scrolling">
|
||||
<table class="gh-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{this.listHeader}}</th>
|
||||
<th>Open rate</th>
|
||||
<th>Location</th>
|
||||
<th>Created</th>
|
||||
{{#each this.filterColumnLabels as |filterColumn|}}
|
||||
<th>{{filterColumn}}</th>
|
||||
{{/each}}
|
||||
</tr>
|
||||
</thead>
|
||||
<VerticalCollection @tagName="tbody" @items={{this.members}} @key="id" @containerSelector=".gh-list-scrolling" @estimateHeight={{69}} @staticHeight={{true}} @bufferSize={{20}} as |member|>
|
||||
<GhMembersListItemLabs
|
||||
@member={{member}}
|
||||
@filterColumns={{this.filterColumns}}
|
||||
data-test-member={{member.id}}
|
||||
/>
|
||||
</VerticalCollection>
|
||||
</table>
|
||||
</div>
|
||||
{{else}}
|
||||
<section class="content-list">
|
||||
<ol class="members-list gh-list {{unless this.members "no-posts"}}">
|
||||
{{#if this.members}}
|
||||
<li class="gh-list-row header relative">
|
||||
<div class="gh-list-header" data-test-list-header>{{this.listHeader}}</div>
|
||||
{{#if (feature "emailAnalytics")}}
|
||||
<div class="gh-list-header gh-members-list-open-rate nowrap">Open rate</div>
|
||||
{{/if}}
|
||||
<div class="gh-list-header gh-members-list-geolocation nowrap">Location</div>
|
||||
<div class="gh-list-header gh-members-list-subscribed-at nowrap">Created</div>
|
||||
<div class="gh-list-header gh-members-list-chevron gh-list-cellwidth-chevron"></div>
|
||||
</li>
|
||||
<VerticalCollection @items={{this.members}} @key="id" @containerSelector=".gh-main" @estimateHeight={{69}} @staticHeight={{true}} @bufferSize={{20}} as |member|>
|
||||
<GhMembersListItem
|
||||
@member={{member}}
|
||||
data-test-member={{member.id}}
|
||||
/>
|
||||
</VerticalCollection>
|
||||
{{else}}
|
||||
<li class="no-posts-box" data-test-no-members>
|
||||
<div class="no-posts">
|
||||
{{svg-jar "members-placeholder" class="gh-members-placeholder"}}
|
||||
{{#if this.showingAll}}
|
||||
<h3>No members yet</h3>
|
||||
<GhMembersNoMembers @afterCreate={{this.refreshData}} />
|
||||
{{else}}
|
||||
<h3>No members match the current filter</h3>
|
||||
<LinkTo @route="members" @query={{hash label=null paid=null search=null}} class="gh-btn gh-btn-lg">
|
||||
<span>Show all members</span>
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ol>
|
||||
</section>
|
||||
<div class="no-posts-box">
|
||||
<div class="no-posts">
|
||||
{{svg-jar "members-placeholder" class="gh-members-placeholder"}}
|
||||
{{#if this.showingAll}}
|
||||
<h3>No members yet</h3>
|
||||
<GhMembersNoMembers @afterCreate={{this.refreshData}} />
|
||||
{{else}}
|
||||
<h3>No members match the current filter</h3>
|
||||
<LinkTo @route="members" @query={{hash filter=null}} class="gh-btn gh-btn-lg">
|
||||
<span>Show all members</span>
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
||||
{{else}}
|
||||
|
|
|
@ -261,19 +261,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-expandable-block">
|
||||
<div class="gh-expandable-header">
|
||||
<div>
|
||||
<h4 class="gh-expandable-title">Members Filtering</h4>
|
||||
<p class="gh-expandable-description">
|
||||
Allow using filtering features on Members
|
||||
</p>
|
||||
</div>
|
||||
<div class="for-switch">
|
||||
<GhFeatureFlag @flag="membersFiltering" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-expandable-block">
|
||||
<div class="gh-expandable-header">
|
||||
<div>
|
||||
|
|
|
@ -58,10 +58,10 @@ describe('Acceptance: Members', function () {
|
|||
expect(document.title, 'page title').to.equal('Members - Test Blog');
|
||||
|
||||
// it lists all members
|
||||
expect(findAll('.members-list .gh-members-list-item').length, 'members list count')
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, 'members list count')
|
||||
.to.equal(2);
|
||||
|
||||
let member = find('.members-list .gh-members-list-item');
|
||||
let member = find('[data-test-list="members-list-item"]');
|
||||
expect(member.querySelector('.gh-members-list-name').textContent, 'member list item title')
|
||||
.to.equal(member1.name);
|
||||
|
||||
|
@ -110,7 +110,7 @@ describe('Acceptance: Members', function () {
|
|||
expect(document.title, 'page title').to.equal('Members - Test Blog');
|
||||
|
||||
// it lists all members
|
||||
expect(findAll('.members-list .gh-members-list-item').length, 'members list count')
|
||||
expect(findAll('[data-test-list="members-list-item"]').length, 'members list count')
|
||||
.to.equal(1);
|
||||
|
||||
// start new member
|
||||
|
@ -145,51 +145,53 @@ describe('Acceptance: Members', function () {
|
|||
expect(find('[data-test-input="member-email"]').value, 'email has been preserved')
|
||||
.to.equal('example@domain.com');
|
||||
});
|
||||
/**
|
||||
* Commented temporarily for unblocking Filtering GA
|
||||
*/
|
||||
// it('can bulk delete members', async function () {
|
||||
// // members to be kept
|
||||
// this.server.createList('member', 6);
|
||||
|
||||
it('can bulk delete members', async function () {
|
||||
// members to be kept
|
||||
this.server.createList('member', 6);
|
||||
// // imported members to be deleted
|
||||
// const label = this.server.create('label');
|
||||
// this.server.createList('member', 5, {labels: [label]});
|
||||
|
||||
// imported members to be deleted
|
||||
const label = this.server.create('label');
|
||||
this.server.createList('member', 5, {labels: [label]});
|
||||
// await visit('/members');
|
||||
|
||||
await visit('/members');
|
||||
// expect(findAll('[data-test-member]').length).to.equal(11);
|
||||
|
||||
expect(findAll('[data-test-member]').length).to.equal(11);
|
||||
// await click('[data-test-button="members-actions"]');
|
||||
|
||||
await click('[data-test-button="members-actions"]');
|
||||
// expect(find('[data-test-button="delete-selected"]')).to.not.exist;
|
||||
|
||||
expect(find('[data-test-button="delete-selected"]')).to.not.exist;
|
||||
// // a filter is needed for the delete-selected button to show
|
||||
// await click('[data-test-button="members-actions"]');
|
||||
// await click(`[data-test-label-filter="${label.name}"]`);
|
||||
|
||||
// a filter is needed for the delete-selected button to show
|
||||
await click('[data-test-button="labels-filter"]');
|
||||
await click(`[data-test-label-filter="${label.name}"]`);
|
||||
// expect(findAll('[data-test-member]').length).to.equal(5);
|
||||
// expect(currentURL()).to.equal('/members?label=label-0');
|
||||
|
||||
expect(findAll('[data-test-member]').length).to.equal(5);
|
||||
expect(currentURL()).to.equal('/members?label=label-0');
|
||||
// await click('[data-test-button="members-actions"]');
|
||||
|
||||
await click('[data-test-button="members-actions"]');
|
||||
// expect(find('[data-test-button="delete-selected"]')).to.exist;
|
||||
|
||||
expect(find('[data-test-button="delete-selected"]')).to.exist;
|
||||
// await click('[data-test-button="delete-selected"]');
|
||||
|
||||
await click('[data-test-button="delete-selected"]');
|
||||
// expect(find('[data-test-modal="delete-members"]')).to.exist;
|
||||
// expect(find('[data-test-text="delete-count"]')).to.have.text('5 members');
|
||||
|
||||
expect(find('[data-test-modal="delete-members"]')).to.exist;
|
||||
expect(find('[data-test-text="delete-count"]')).to.have.text('5 members');
|
||||
// await click('[data-test-button="confirm"]');
|
||||
|
||||
await click('[data-test-button="confirm"]');
|
||||
// expect(find('[data-test-text="deleted-count"]')).to.have.text('5 members');
|
||||
// expect(find('[data-test-button="confirm"]')).to.not.exist;
|
||||
// // members filter is reset
|
||||
// // TODO: fix query params reset for empty strings
|
||||
// expect(currentURL()).to.equal('/members?search=');
|
||||
// expect(findAll('[data-test-member]').length).to.equal(6);
|
||||
|
||||
expect(find('[data-test-text="deleted-count"]')).to.have.text('5 members');
|
||||
expect(find('[data-test-button="confirm"]')).to.not.exist;
|
||||
// members filter is reset
|
||||
// TODO: fix query params reset for empty strings
|
||||
expect(currentURL()).to.equal('/members?search=');
|
||||
expect(findAll('[data-test-member]').length).to.equal(6);
|
||||
// await click('[data-test-button="close-modal"]');
|
||||
|
||||
await click('[data-test-button="close-modal"]');
|
||||
|
||||
expect(find('[data-test-modal="delete-members"]')).to.not.exist;
|
||||
});
|
||||
// expect(find('[data-test-modal="delete-members"]')).to.not.exist;
|
||||
// });
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue