2019-01-24 22:34:32 +03:00
|
|
|
import Controller from '@ember/controller';
|
2021-04-08 18:06:00 +03:00
|
|
|
import config from 'ghost-admin/config/environment';
|
|
|
|
import fetch from 'fetch';
|
2019-10-04 12:33:10 +03:00
|
|
|
import ghostPaths from 'ghost-admin/utils/ghost-paths';
|
2020-06-01 17:48:46 +03:00
|
|
|
import moment from 'moment';
|
2020-05-20 16:55:41 +03:00
|
|
|
import {A} from '@ember/array';
|
|
|
|
import {action} from '@ember/object';
|
2021-08-10 15:11:22 +03:00
|
|
|
import {capitalize} from '@ember/string';
|
2020-08-10 13:32:45 +03:00
|
|
|
import {ghPluralize} from 'ghost-admin/helpers/gh-pluralize';
|
2021-04-08 14:06:27 +03:00
|
|
|
import {resetQueryParams} from 'ghost-admin/helpers/reset-query-params';
|
2019-01-31 17:13:35 +03:00
|
|
|
import {inject as service} from '@ember/service';
|
2020-05-20 16:55:41 +03:00
|
|
|
import {task} from 'ember-concurrency-decorators';
|
2020-05-28 12:15:17 +03:00
|
|
|
import {timeout} from 'ember-concurrency';
|
2020-05-20 16:55:41 +03:00
|
|
|
import {tracked} from '@glimmer/tracking';
|
2019-01-24 22:34:32 +03:00
|
|
|
|
2020-06-12 14:12:27 +03:00
|
|
|
const PAID_PARAMS = [{
|
|
|
|
name: 'All members',
|
|
|
|
value: null
|
|
|
|
}, {
|
|
|
|
name: 'Free members',
|
|
|
|
value: 'false'
|
|
|
|
}, {
|
|
|
|
name: 'Paid members',
|
|
|
|
value: 'true'
|
|
|
|
}];
|
|
|
|
|
2020-05-20 16:55:41 +03:00
|
|
|
export default class MembersController extends Controller {
|
2020-06-19 20:14:14 +03:00
|
|
|
@service ajax;
|
2020-06-18 13:05:03 +03:00
|
|
|
@service config;
|
2020-06-01 17:48:46 +03:00
|
|
|
@service ellaSparse;
|
2020-05-22 19:58:01 +03:00
|
|
|
@service feature;
|
2020-06-19 20:14:14 +03:00
|
|
|
@service ghostPaths;
|
2020-06-01 17:48:46 +03:00
|
|
|
@service membersStats;
|
2021-04-08 14:06:27 +03:00
|
|
|
@service router;
|
2020-05-20 16:55:41 +03:00
|
|
|
@service store;
|
2019-01-31 17:13:35 +03:00
|
|
|
|
2020-06-12 14:12:27 +03:00
|
|
|
queryParams = [
|
|
|
|
'label',
|
|
|
|
{paidParam: 'paid'},
|
2020-12-08 22:23:57 +03:00
|
|
|
{searchParam: 'search'},
|
2021-08-04 09:50:34 +03:00
|
|
|
{orderParam: 'order'},
|
|
|
|
{filterParam: 'filter'}
|
2020-06-12 14:12:27 +03:00
|
|
|
];
|
2020-02-14 12:34:01 +03:00
|
|
|
|
2020-06-01 17:48:46 +03:00
|
|
|
@tracked members = A([]);
|
2020-05-20 16:55:41 +03:00
|
|
|
@tracked searchText = '';
|
2020-05-28 12:15:17 +03:00
|
|
|
@tracked searchParam = '';
|
2021-08-04 13:41:50 +03:00
|
|
|
@tracked filterParam = null;
|
2021-08-13 20:52:49 +03:00
|
|
|
@tracked softFilterParam = null;
|
2020-06-12 14:12:27 +03:00
|
|
|
@tracked paidParam = null;
|
2020-05-20 16:55:41 +03:00
|
|
|
@tracked label = null;
|
2020-12-08 22:23:57 +03:00
|
|
|
@tracked orderParam = null;
|
2020-05-20 16:55:41 +03:00
|
|
|
@tracked modalLabel = null;
|
|
|
|
@tracked showLabelModal = false;
|
2020-06-19 16:14:39 +03:00
|
|
|
@tracked showDeleteMembersModal = false;
|
2021-08-13 14:41:34 +03:00
|
|
|
@tracked showUnsubscribeMembersModal = false;
|
|
|
|
@tracked showAddMembersLabelModal = false;
|
|
|
|
@tracked showRemoveMembersLabelModal = false;
|
2021-08-10 15:11:22 +03:00
|
|
|
@tracked filters = A([]);
|
2021-08-13 20:52:49 +03:00
|
|
|
@tracked softFilters = A([]);
|
2020-02-14 12:34:01 +03:00
|
|
|
|
2020-05-20 16:55:41 +03:00
|
|
|
@tracked _availableLabels = A([]);
|
2019-12-03 08:04:04 +03:00
|
|
|
|
2020-06-12 14:12:27 +03:00
|
|
|
paidParams = PAID_PARAMS;
|
|
|
|
|
2020-05-20 16:55:41 +03:00
|
|
|
constructor() {
|
|
|
|
super(...arguments);
|
2020-02-14 12:34:01 +03:00
|
|
|
this._availableLabels = this.store.peekAll('label');
|
2021-04-08 18:06:00 +03:00
|
|
|
|
|
|
|
if (this.isTesting === undefined) {
|
|
|
|
this.isTesting = config.environment === 'test';
|
|
|
|
}
|
2020-05-20 16:55:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Computed properties -----------------------------------------------------
|
2019-01-31 17:13:35 +03:00
|
|
|
|
2020-05-20 16:55:41 +03:00
|
|
|
get listHeader() {
|
2020-05-20 18:39:06 +03:00
|
|
|
let {searchText, selectedLabel, members} = this;
|
2020-05-22 14:47:03 +03:00
|
|
|
|
|
|
|
if (members.loading) {
|
|
|
|
return 'Loading...';
|
|
|
|
}
|
|
|
|
|
2020-02-14 12:34:01 +03:00
|
|
|
if (searchText) {
|
|
|
|
return 'Search result';
|
|
|
|
}
|
2020-05-22 14:47:03 +03:00
|
|
|
|
2020-08-10 13:16:03 +03:00
|
|
|
let count = ghPluralize(members.length, 'member');
|
2020-05-22 14:47:03 +03:00
|
|
|
|
|
|
|
if (selectedLabel && selectedLabel.slug) {
|
|
|
|
if (members.length > 1) {
|
|
|
|
return `${count} match current filter`;
|
|
|
|
} else {
|
|
|
|
return `${count} matches current filter`;
|
2020-02-14 12:34:01 +03:00
|
|
|
}
|
|
|
|
}
|
2020-05-22 14:47:03 +03:00
|
|
|
|
|
|
|
return count;
|
2020-05-20 16:55:41 +03:00
|
|
|
}
|
2020-02-14 12:34:01 +03:00
|
|
|
|
2020-05-20 16:55:41 +03:00
|
|
|
get showingAll() {
|
2021-09-06 10:13:17 +03:00
|
|
|
return !this.searchParam && !this.paidParam && !this.label && !this.filterParam && !this.softFilterParam;
|
2020-05-20 16:55:41 +03:00
|
|
|
}
|
2020-02-14 12:34:01 +03:00
|
|
|
|
2020-12-08 22:23:57 +03:00
|
|
|
get availableOrders() {
|
|
|
|
// don't return anything if email analytics is disabled because
|
|
|
|
// we don't want to show an order dropdown with only a single option
|
|
|
|
|
|
|
|
if (this.feature.get('emailAnalytics')) {
|
|
|
|
return [{
|
|
|
|
name: 'Newest',
|
|
|
|
value: null
|
|
|
|
}, {
|
|
|
|
name: 'Open rate',
|
|
|
|
value: 'email_open_rate'
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
get selectedOrder() {
|
|
|
|
return this.availableOrders.find(order => order.value === this.orderParam);
|
|
|
|
}
|
|
|
|
|
2020-05-20 16:55:41 +03:00
|
|
|
get availableLabels() {
|
2020-02-14 12:34:01 +03:00
|
|
|
let labels = this._availableLabels
|
2020-05-20 16:55:41 +03:00
|
|
|
.filter(label => !label.isNew)
|
|
|
|
.filter(label => label.id !== null)
|
2020-02-14 12:34:01 +03:00
|
|
|
.sort((labelA, labelB) => labelA.name.localeCompare(labelB.name, undefined, {ignorePunctuation: true}));
|
|
|
|
let options = labels.toArray();
|
|
|
|
|
|
|
|
options.unshiftObject({name: 'All labels', slug: null});
|
|
|
|
|
|
|
|
return options;
|
2020-05-20 16:55:41 +03:00
|
|
|
}
|
2020-02-14 12:34:01 +03:00
|
|
|
|
2020-05-20 16:55:41 +03:00
|
|
|
get selectedLabel() {
|
|
|
|
let {label, availableLabels} = this;
|
|
|
|
return availableLabels.findBy('slug', label);
|
|
|
|
}
|
2020-02-14 12:34:01 +03:00
|
|
|
|
2020-05-20 16:55:41 +03:00
|
|
|
get labelModalData() {
|
|
|
|
let label = this.modalLabel;
|
|
|
|
let labels = this.availableLabels;
|
2020-02-14 12:34:01 +03:00
|
|
|
|
|
|
|
return {
|
|
|
|
label,
|
|
|
|
labels
|
|
|
|
};
|
2020-05-20 16:55:41 +03:00
|
|
|
}
|
2020-02-14 12:34:01 +03:00
|
|
|
|
2020-06-12 14:12:27 +03:00
|
|
|
get selectedPaidParam() {
|
|
|
|
return this.paidParams.findBy('value', this.paidParam) || {value: '!unknown'};
|
|
|
|
}
|
|
|
|
|
2021-04-08 14:06:27 +03:00
|
|
|
get isFiltered() {
|
2021-08-04 09:50:34 +03:00
|
|
|
return !!(this.label || this.paidParam || this.searchParam || this.filterParam);
|
2021-04-08 14:06:27 +03:00
|
|
|
}
|
|
|
|
|
2021-08-10 15:11:22 +03:00
|
|
|
get filterColumns() {
|
|
|
|
const defaultColumns = ['name', 'email'];
|
2021-08-13 20:52:49 +03:00
|
|
|
const availableFilters = this.filters.length ? this.filters : this.softFilters;
|
|
|
|
return availableFilters.map((filter) => {
|
2021-08-10 15:11:22 +03:00
|
|
|
return filter.type;
|
|
|
|
}).filter((f, idx, arr) => {
|
|
|
|
return arr.indexOf(f) === idx;
|
|
|
|
}).filter(d => !defaultColumns.includes(d));
|
|
|
|
}
|
|
|
|
|
|
|
|
get filterColumnLabels() {
|
2021-08-13 16:36:39 +03:00
|
|
|
const filterColumnLabelMap = {
|
2021-08-13 17:23:52 +03:00
|
|
|
'subscriptions.plan_interval': 'Billing period',
|
2021-08-13 18:28:30 +03:00
|
|
|
subscribed: 'Subscribed to email',
|
2021-08-13 16:36:39 +03:00
|
|
|
'subscriptions.status': 'Subscription Status'
|
|
|
|
};
|
2021-08-10 15:11:22 +03:00
|
|
|
return this.filterColumns.map((d) => {
|
2021-08-13 16:36:39 +03:00
|
|
|
return filterColumnLabelMap[d] ? filterColumnLabelMap[d] : capitalize(d.replace(/_/g, ' '));
|
2021-08-10 15:11:22 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-04-08 18:00:32 +03:00
|
|
|
getApiQueryObject({params, extraFilters = []} = {}) {
|
2021-08-04 09:50:34 +03:00
|
|
|
let {label, paidParam, searchParam, filterParam} = params ? params : this;
|
2021-04-08 17:12:00 +03:00
|
|
|
|
|
|
|
let filters = [];
|
|
|
|
|
2021-08-04 09:57:51 +03:00
|
|
|
filters = filters.concat(extraFilters);
|
2021-04-08 17:12:00 +03:00
|
|
|
|
|
|
|
if (label) {
|
|
|
|
filters.push(`label:'${label}'`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (paidParam !== null) {
|
|
|
|
if (paidParam === 'true') {
|
|
|
|
filters.push('status:-free');
|
|
|
|
} else {
|
|
|
|
filters.push('status:free');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-04 13:53:00 +03:00
|
|
|
if (filterParam && this.feature.get('membersFiltering')) {
|
2021-08-04 09:50:34 +03:00
|
|
|
filters.push(filterParam);
|
|
|
|
}
|
|
|
|
|
2021-04-08 17:12:00 +03:00
|
|
|
let searchQuery = searchParam ? {search: searchParam} : {};
|
|
|
|
|
|
|
|
return Object.assign({}, {filter: filters.join('+')}, searchQuery);
|
|
|
|
}
|
|
|
|
|
2020-05-20 16:55:41 +03:00
|
|
|
// Actions -----------------------------------------------------------------
|
|
|
|
|
2020-06-01 17:48:46 +03:00
|
|
|
@action
|
|
|
|
refreshData() {
|
|
|
|
this.fetchMembersTask.perform();
|
|
|
|
this.fetchLabelsTask.perform();
|
|
|
|
this.membersStats.invalidate();
|
2021-04-06 11:42:41 +03:00
|
|
|
this.membersStats.fetchCounts();
|
2020-06-01 17:48:46 +03:00
|
|
|
}
|
|
|
|
|
2020-12-08 22:23:57 +03:00
|
|
|
@action
|
|
|
|
changeOrder(order) {
|
|
|
|
this.orderParam = order.value;
|
|
|
|
}
|
|
|
|
|
2021-08-05 16:52:16 +03:00
|
|
|
@action
|
2021-08-10 15:11:22 +03:00
|
|
|
applyFilter(filterStr, filters) {
|
2021-08-13 20:52:49 +03:00
|
|
|
this.softFilters = A([]);
|
2021-08-05 16:52:16 +03:00
|
|
|
this.filterParam = filterStr || null;
|
2021-09-06 09:44:08 +03:00
|
|
|
this.filters = filters;
|
2021-08-13 15:30:17 +03:00
|
|
|
}
|
|
|
|
|
2021-08-13 20:52:49 +03:00
|
|
|
@action
|
|
|
|
applySoftFilter(filterStr, filters) {
|
|
|
|
this.softFilters = filters;
|
|
|
|
this.softFilterParam = filterStr || null;
|
2021-09-06 09:44:08 +03:00
|
|
|
let {label, paidParam, searchParam, orderParam} = this;
|
|
|
|
this.fetchMembersTask.perform({label, paidParam, searchParam, orderParam, filterParam: filterStr});
|
2021-08-13 20:52:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
resetSoftFilter() {
|
2021-09-06 09:44:08 +03:00
|
|
|
if (this.softFilters.length > 0 || !!this.softFilterParam) {
|
|
|
|
this.softFilters = A([]);
|
|
|
|
this.softFilterParam = null;
|
|
|
|
this.fetchMembersTask.perform();
|
|
|
|
}
|
2021-08-13 20:52:49 +03:00
|
|
|
}
|
|
|
|
|
2021-08-13 15:30:17 +03:00
|
|
|
@action
|
|
|
|
resetFilter() {
|
2021-08-13 20:52:49 +03:00
|
|
|
this.softFilters = A([]);
|
|
|
|
this.softFilterParam = null;
|
2021-08-13 15:30:17 +03:00
|
|
|
this.filters = A([]);
|
|
|
|
this.filterParam = null;
|
2021-09-06 09:44:08 +03:00
|
|
|
this.fetchMembersTask.perform();
|
2021-08-05 16:52:16 +03:00
|
|
|
}
|
|
|
|
|
2020-05-28 12:15:17 +03:00
|
|
|
@action
|
|
|
|
search(e) {
|
|
|
|
this.searchTask.perform(e.target.value);
|
|
|
|
}
|
|
|
|
|
2020-05-20 16:55:41 +03:00
|
|
|
@action
|
|
|
|
exportData() {
|
2020-06-16 09:13:06 +03:00
|
|
|
let exportUrl = ghostPaths().url.api('members/upload');
|
2021-04-08 17:12:00 +03:00
|
|
|
let downloadParams = new URLSearchParams(this.getApiQueryObject());
|
2020-09-23 16:29:47 +03:00
|
|
|
downloadParams.set('limit', 'all');
|
2021-04-08 17:12:00 +03:00
|
|
|
|
2020-05-20 16:55:41 +03:00
|
|
|
let iframe = document.getElementById('iframeDownload');
|
|
|
|
|
|
|
|
if (!iframe) {
|
|
|
|
iframe = document.createElement('iframe');
|
|
|
|
iframe.id = 'iframeDownload';
|
|
|
|
iframe.style.display = 'none';
|
|
|
|
document.body.append(iframe);
|
|
|
|
}
|
2020-09-23 16:29:47 +03:00
|
|
|
iframe.setAttribute('src', `${exportUrl}?${downloadParams.toString()}`);
|
2020-05-20 16:55:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
changeLabel(label, e) {
|
|
|
|
if (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
2019-10-04 12:33:10 +03:00
|
|
|
}
|
2020-05-20 16:55:41 +03:00
|
|
|
this.label = label.slug;
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
addLabel(e) {
|
|
|
|
if (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
}
|
|
|
|
const newLabel = this.store.createRecord('label');
|
|
|
|
this.modalLabel = newLabel;
|
|
|
|
this.showLabelModal = !this.showLabelModal;
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
editLabel(label, e) {
|
|
|
|
if (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
}
|
|
|
|
let modalLabel = this.availableLabels.findBy('slug', label);
|
|
|
|
this.modalLabel = modalLabel;
|
|
|
|
this.showLabelModal = !this.showLabelModal;
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
toggleLabelModal() {
|
|
|
|
this.showLabelModal = !this.showLabelModal;
|
|
|
|
}
|
|
|
|
|
2020-06-12 14:12:27 +03:00
|
|
|
@action
|
|
|
|
changePaidParam(paid) {
|
|
|
|
this.paidParam = paid.value;
|
|
|
|
}
|
|
|
|
|
2020-06-18 13:05:03 +03:00
|
|
|
@action
|
2020-06-19 16:14:39 +03:00
|
|
|
toggleDeleteMembersModal() {
|
|
|
|
this.showDeleteMembersModal = !this.showDeleteMembersModal;
|
|
|
|
}
|
|
|
|
|
2021-08-13 14:41:34 +03:00
|
|
|
@action
|
|
|
|
toggleUnsubscribeMembersModal() {
|
|
|
|
this.showUnsubscribeMembersModal = !this.showUnsubscribeMembersModal;
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
toggleAddMembersLabelModal() {
|
|
|
|
this.showAddMembersLabelModal = !this.showAddMembersLabelModal;
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
toggleRemoveMembersLabelModal() {
|
|
|
|
this.showRemoveMembersLabelModal = !this.showRemoveMembersLabelModal;
|
|
|
|
}
|
|
|
|
|
2020-06-19 16:14:39 +03:00
|
|
|
@action
|
|
|
|
deleteMembers() {
|
|
|
|
return this.deleteMembersTask.perform();
|
2020-06-18 13:05:03 +03:00
|
|
|
}
|
|
|
|
|
2021-08-13 14:41:34 +03:00
|
|
|
@action
|
|
|
|
unsubscribeMembers() {
|
|
|
|
return this.unsubscribeMembersTask.perform();
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
2021-08-13 19:20:46 +03:00
|
|
|
addLabelToMembers(selectedLabel) {
|
|
|
|
return this.addLabelToMembersTask.perform(selectedLabel);
|
2021-08-13 14:41:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
2021-08-13 19:20:46 +03:00
|
|
|
removeLabelFromMembers(selectedLabel) {
|
|
|
|
return this.removeLabelFromMembersTask.perform(selectedLabel);
|
2021-08-13 14:41:34 +03:00
|
|
|
}
|
|
|
|
|
2020-05-20 16:55:41 +03:00
|
|
|
// Tasks -------------------------------------------------------------------
|
2019-11-28 14:30:21 +03:00
|
|
|
|
2020-05-28 12:15:17 +03:00
|
|
|
@task({restartable: true})
|
|
|
|
*searchTask(query) {
|
|
|
|
yield timeout(250); // debounce
|
|
|
|
this.searchParam = query;
|
|
|
|
}
|
|
|
|
|
2020-05-20 17:12:48 +03:00
|
|
|
@task
|
|
|
|
*fetchLabelsTask() {
|
2020-06-09 08:42:55 +03:00
|
|
|
yield this.store.query('label', {limit: 'all'});
|
2020-05-20 17:12:48 +03:00
|
|
|
}
|
2020-05-28 12:15:17 +03:00
|
|
|
|
2020-06-05 10:49:56 +03:00
|
|
|
@task({restartable: true})
|
2020-06-01 17:48:46 +03:00
|
|
|
*fetchMembersTask(params) {
|
|
|
|
// params is undefined when called as a "refresh" of the model
|
2021-08-04 09:50:34 +03:00
|
|
|
let {label, paidParam, searchParam, orderParam, filterParam} = typeof params === 'undefined' ? this : params;
|
2020-06-01 17:48:46 +03:00
|
|
|
|
|
|
|
if (!searchParam) {
|
|
|
|
this.resetSearch();
|
|
|
|
}
|
|
|
|
|
|
|
|
// use a fixed created_at date so that subsequent pages have a consistent index
|
|
|
|
let startDate = new Date();
|
|
|
|
|
|
|
|
// bypass the stale data shortcut if params change
|
2020-06-12 14:12:27 +03:00
|
|
|
let forceReload = !params
|
|
|
|
|| label !== this._lastLabel
|
|
|
|
|| paidParam !== this._lastPaidParam
|
2020-12-08 22:23:57 +03:00
|
|
|
|| searchParam !== this._lastSearchParam
|
2021-08-04 10:00:05 +03:00
|
|
|
|| orderParam !== this._lastOrderParam
|
2021-08-04 09:50:34 +03:00
|
|
|
|| filterParam !== this._lastFilterParam;
|
2020-06-01 17:48:46 +03:00
|
|
|
this._lastLabel = label;
|
2020-06-12 14:12:27 +03:00
|
|
|
this._lastPaidParam = paidParam;
|
2020-06-01 17:48:46 +03:00
|
|
|
this._lastSearchParam = searchParam;
|
2020-12-08 22:23:57 +03:00
|
|
|
this._lastOrderParam = orderParam;
|
2021-08-04 09:50:34 +03:00
|
|
|
this._lastFilterParam = filterParam;
|
2020-06-01 17:48:46 +03:00
|
|
|
|
|
|
|
// unless we have a forced reload, do not re-fetch the members list unless it's more than a minute old
|
|
|
|
// keeps navigation between list->details->list snappy
|
|
|
|
if (!forceReload && this._startDate && !(this._startDate - startDate > 1 * 60 * 1000)) {
|
|
|
|
return this.members;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._startDate = startDate;
|
|
|
|
|
|
|
|
this.members = yield this.ellaSparse.array((range = {}, query = {}) => {
|
2021-04-08 17:12:00 +03:00
|
|
|
const searchQuery = this.getApiQueryObject({
|
2021-04-08 18:00:32 +03:00
|
|
|
params,
|
2021-04-08 17:12:00 +03:00
|
|
|
extraFilters: [`created_at:<='${moment.utc(this._startDate).format('YYYY-MM-DD HH:mm:ss')}'`]
|
|
|
|
});
|
2020-12-08 22:23:57 +03:00
|
|
|
const order = orderParam ? `${orderParam} desc` : `created_at desc`;
|
2020-06-01 17:48:46 +03:00
|
|
|
|
|
|
|
query = Object.assign({
|
2020-12-08 22:23:57 +03:00
|
|
|
order,
|
2020-06-01 17:48:46 +03:00
|
|
|
limit: range.length,
|
2021-04-08 17:12:00 +03:00
|
|
|
page: range.page
|
2021-01-28 19:32:21 +03:00
|
|
|
}, searchQuery, query);
|
2020-06-01 17:48:46 +03:00
|
|
|
|
|
|
|
return this.store.query('member', query).then((result) => {
|
|
|
|
return {
|
|
|
|
data: result,
|
|
|
|
total: result.meta.pagination.total
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}, {
|
|
|
|
limit: 50
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-06-19 16:14:39 +03:00
|
|
|
@task({drop: true})
|
|
|
|
*deleteMembersTask() {
|
2021-04-08 17:12:00 +03:00
|
|
|
const query = new URLSearchParams(this.getApiQueryObject());
|
2020-06-26 00:44:24 +03:00
|
|
|
|
2021-04-08 18:06:00 +03:00
|
|
|
// Trigger download before deleting. Uses the CSV export endpoint but
|
|
|
|
// needs to fetch the file and trigger a download directly rather than
|
|
|
|
// via an iframe. The iframe approach can't tell us when a download has
|
|
|
|
// started/finished meaning we could end up deleting the data before exporting it
|
|
|
|
const exportUrl = ghostPaths().url.api('members/upload');
|
|
|
|
const exportParams = new URLSearchParams(this.getApiQueryObject());
|
|
|
|
exportParams.set('limit', 'all');
|
|
|
|
|
|
|
|
yield fetch(exportUrl, {method: 'GET'})
|
|
|
|
.then(res => res.blob())
|
|
|
|
.then((blob) => {
|
|
|
|
const blobUrl = window.URL.createObjectURL(blob);
|
|
|
|
const a = document.createElement('a');
|
|
|
|
a.href = blobUrl;
|
|
|
|
a.download = `members.${moment().format('YYYY-MM-DD')}.csv`;
|
|
|
|
document.body.appendChild(a);
|
|
|
|
if (!this.isTesting) {
|
|
|
|
a.click();
|
|
|
|
}
|
|
|
|
a.remove();
|
|
|
|
URL.revokeObjectURL(blobUrl);
|
|
|
|
});
|
|
|
|
|
|
|
|
// backup downloaded, continue with deletion
|
|
|
|
|
|
|
|
const deleteUrl = `${this.ghostPaths.url.api('members')}?${query}`;
|
2020-06-19 20:14:14 +03:00
|
|
|
|
|
|
|
// response contains details of which members failed to be deleted
|
2021-04-08 18:06:00 +03:00
|
|
|
const response = yield this.ajax.del(deleteUrl);
|
2020-06-19 16:14:39 +03:00
|
|
|
|
|
|
|
// reset and reload
|
2020-06-19 20:14:14 +03:00
|
|
|
this.store.unloadAll('member');
|
2021-04-08 14:06:27 +03:00
|
|
|
this.router.transitionTo('members.index', {queryParams: Object.assign(resetQueryParams('members.index'))});
|
|
|
|
this.membersStats.invalidate();
|
|
|
|
this.membersStats.fetchCounts();
|
2020-06-19 16:14:39 +03:00
|
|
|
|
2021-04-08 14:06:27 +03:00
|
|
|
return response.meta;
|
2020-06-19 16:14:39 +03:00
|
|
|
}
|
|
|
|
|
2021-08-13 14:41:34 +03:00
|
|
|
@task({drop: true})
|
|
|
|
*unsubscribeMembersTask() {
|
2021-08-13 19:20:46 +03:00
|
|
|
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: {}
|
|
|
|
}
|
|
|
|
});
|
2021-08-13 14:41:34 +03:00
|
|
|
|
|
|
|
// reset and reload
|
|
|
|
this.store.unloadAll('member');
|
2021-09-08 09:36:03 +03:00
|
|
|
this.reset();
|
|
|
|
|
2021-08-13 14:41:34 +03:00
|
|
|
this.membersStats.invalidate();
|
|
|
|
this.membersStats.fetchCounts();
|
|
|
|
|
2021-09-08 09:36:03 +03:00
|
|
|
return response?.bulk?.meta;
|
2021-08-13 14:41:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@task({drop: true})
|
2021-08-13 19:20:46 +03:00
|
|
|
*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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2021-08-13 14:41:34 +03:00
|
|
|
|
|
|
|
// reset and reload
|
|
|
|
this.store.unloadAll('member');
|
2021-09-08 09:36:03 +03:00
|
|
|
this.reset();
|
2021-08-13 14:41:34 +03:00
|
|
|
this.membersStats.invalidate();
|
|
|
|
this.membersStats.fetchCounts();
|
|
|
|
|
2021-09-08 09:36:03 +03:00
|
|
|
return response?.bulk?.meta;
|
2021-08-13 14:41:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@task({drop: true})
|
2021-08-13 19:20:46 +03:00
|
|
|
*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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2021-08-13 14:41:34 +03:00
|
|
|
|
|
|
|
// reset and reload
|
|
|
|
this.store.unloadAll('member');
|
2021-09-08 09:36:03 +03:00
|
|
|
this.reset();
|
2021-08-13 14:41:34 +03:00
|
|
|
this.membersStats.invalidate();
|
|
|
|
this.membersStats.fetchCounts();
|
|
|
|
|
2021-09-08 09:36:03 +03:00
|
|
|
return response?.bulk?.meta;
|
2021-08-13 14:41:34 +03:00
|
|
|
}
|
2020-05-28 12:15:17 +03:00
|
|
|
// Internal ----------------------------------------------------------------
|
|
|
|
|
|
|
|
resetSearch() {
|
|
|
|
this.searchText = '';
|
|
|
|
}
|
2020-06-18 13:23:15 +03:00
|
|
|
|
2021-08-13 16:30:24 +03:00
|
|
|
reset() {
|
|
|
|
this.filterParam = null;
|
2021-09-08 09:36:03 +03:00
|
|
|
this.softFilterParam = null;
|
2021-08-13 16:30:24 +03:00
|
|
|
}
|
|
|
|
|
2021-04-08 14:06:27 +03:00
|
|
|
reload(params) {
|
2020-06-19 16:14:39 +03:00
|
|
|
this.membersStats.invalidate();
|
2021-04-06 11:42:41 +03:00
|
|
|
this.membersStats.fetchCounts();
|
2021-04-08 14:06:27 +03:00
|
|
|
this.fetchMembersTask.perform(params);
|
2020-06-19 16:14:39 +03:00
|
|
|
}
|
2020-05-20 16:55:41 +03:00
|
|
|
}
|