Wired bulk action operations on filtered members list

closes https://github.com/TryGhost/Team/issues/969

Wires the bulk action operation UI to Ghost API to perform operations on filtered member list - unsubscribe filtered members, add label to filtered members or remove label from filtered members
This commit is contained in:
Rishabh 2021-08-13 21:50:46 +05:30
parent 0afa2aa2c0
commit 6d9b8175a2
9 changed files with 58 additions and 49 deletions

View File

@ -1,9 +1,9 @@
<span class="gh-select">
<OneWaySelect
@options={{this.availableLabels}}
@optionValuePath="slug"
@optionValuePath="id"
@optionLabelPath="name"
@optionTargetPath="slug"
@optionTargetPath="id"
@update={{this.updateLabel}}
/>
{{svg-jar "arrow-down-small"}}

View File

@ -25,7 +25,8 @@ export default class GhMemberLabelInput extends Component {
// store and be updated when the above query returns
this.store.query('label', {limit: 'all'});
this._availableLabels = this.store.peekAll('label');
this.selectedLabel = this.args.label || null;
this.selectedLabel = this.args.label || this.availableLabels[0].get('id');
this.args.onChange(this.selectedLabel);
}
@action

View File

@ -24,7 +24,7 @@
<GhTaskButton
@buttonText="Add Label"
@successText="Added"
@task={{this.deleteMembersTask}}
@task={{this.addLabelTask}}
@class="gh-btn gh-btn-green gh-btn-icon"
data-test-button="confirm"
/>

View File

@ -14,7 +14,7 @@ export default ModalComponent.extend({
actions: {
confirm() {
this.deleteMember.perform();
this.addLabelTask.perform();
},
setLabel(label) {
@ -22,9 +22,9 @@ export default ModalComponent.extend({
}
},
deleteMember: task(function* () {
addLabelTask: task(function* () {
try {
yield this.confirm(this.shouldCancelSubscriptions);
yield this.confirm(this.selectedLabel);
this.membersStats.invalidate();
} finally {
this.send('closeModal');

View File

@ -24,7 +24,7 @@
<GhTaskButton
@buttonText="Remove Label"
@successText="Removed"
@task={{this.deleteMembersTask}}
@task={{this.removeLabelTask}}
@class="gh-btn gh-btn-red gh-btn-icon"
data-test-button="confirm"
/>

View File

@ -14,7 +14,7 @@ export default ModalComponent.extend({
actions: {
confirm() {
this.deleteMember.perform();
this.removeLabelTask.perform();
},
setLabel(label) {
@ -22,9 +22,9 @@ export default ModalComponent.extend({
}
},
deleteMember: task(function* () {
removeLabelTask: task(function* () {
try {
yield this.confirm(this.shouldCancelSubscriptions);
yield this.confirm(this.selectedLabel);
this.membersStats.invalidate();
} finally {
this.send('closeModal');

View File

@ -54,7 +54,7 @@
<GhTaskButton
@buttonText="Unsubscribe members"
@successText="Unsubscribed"
@task={{this.deleteMembersTask}}
@task={{this.unsubscribeMemberTask}}
@class="gh-btn gh-btn-red gh-btn-icon"
data-test-button="confirm"
/>

View File

@ -1,7 +1,5 @@
import ModalComponent from 'ghost-admin/components/modal-base';
import {alias} from '@ember/object/computed';
import {computed} from '@ember/object';
import {reads} from '@ember/object/computed';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
@ -15,35 +13,15 @@ export default ModalComponent.extend({
member: alias('model'),
cancelSubscriptions: reads('shouldCancelSubscriptions'),
hasActiveStripeSubscriptions: computed('member', function () {
let subscriptions = this.member.get('subscriptions');
if (!subscriptions || subscriptions.length === 0) {
return false;
}
let firstActiveStripeSubscription = subscriptions.find((subscription) => {
return ['active', 'trialing', 'unpaid', 'past_due'].includes(subscription.status);
});
return firstActiveStripeSubscription !== undefined;
}),
actions: {
confirm() {
this.deleteMember.perform();
},
toggleShouldCancelSubscriptions() {
this.set('shouldCancelSubscriptions', !this.shouldCancelSubscriptions);
this.unsubscribeMember.perform();
}
},
deleteMember: task(function* () {
unsubscribeMemberTask: task(function* () {
try {
yield this.confirm(this.shouldCancelSubscriptions);
yield this.confirm();
this.membersStats.invalidate();
} finally {
this.send('closeModal');

View File

@ -325,13 +325,13 @@ export default class MembersController extends Controller {
}
@action
addLabelsToMembers() {
return this.addLabelToMembersTask.perform();
addLabelToMembers(selectedLabel) {
return this.addLabelToMembersTask.perform(selectedLabel);
}
@action
removeLabelsFromMembers() {
return this.removeLabelFromMembersTask.perform();
removeLabelFromMembers(selectedLabel) {
return this.removeLabelFromMembersTask.perform(selectedLabel);
}
// Tasks -------------------------------------------------------------------
@ -449,7 +449,15 @@ export default class MembersController extends Controller {
@task({drop: true})
*unsubscribeMembersTask() {
yield Promise.resolve();
const query = new URLSearchParams(this.getApiQueryObject());
const unsubscribeUrl = `${this.ghostPaths.url.api('members/bulk')}?${query}`;
// response contains details of which members failed to be unsubscribe
const response = yield this.ajax.put(unsubscribeUrl, {
data: {
action: 'unsubscribe',
meta: {}
}
});
// reset and reload
this.store.unloadAll('member');
@ -457,12 +465,23 @@ export default class MembersController extends Controller {
this.membersStats.invalidate();
this.membersStats.fetchCounts();
return {};
return response.meta;
}
@task({drop: true})
*addLabelToMembersTask() {
yield Promise.resolve();
*addLabelToMembersTask(selectedLabel) {
const query = new URLSearchParams(this.getApiQueryObject());
const addLabelUrl = `${this.ghostPaths.url.api('members/bulk')}?${query}`;
const response = yield this.ajax.put(addLabelUrl, {
data: {
action: 'addLabel',
meta: {
label: {
id: selectedLabel
}
}
}
});
// reset and reload
this.store.unloadAll('member');
@ -470,12 +489,23 @@ export default class MembersController extends Controller {
this.membersStats.invalidate();
this.membersStats.fetchCounts();
return {};
return response.meta;
}
@task({drop: true})
*removeLabelFromMembersTask() {
yield Promise.resolve();
*removeLabelFromMembersTask(selectedLabel) {
const query = new URLSearchParams(this.getApiQueryObject());
const removeLabelUrl = `${this.ghostPaths.url.api('members/bulk')}?${query}`;
const response = yield this.ajax.put(removeLabelUrl, {
data: {
action: 'removeLabel',
meta: {
label: {
id: selectedLabel
}
}
}
});
// reset and reload
this.store.unloadAll('member');
@ -483,7 +513,7 @@ export default class MembersController extends Controller {
this.membersStats.invalidate();
this.membersStats.fetchCounts();
return {};
return response.meta;
}
// Internal ----------------------------------------------------------------