386 lines
12 KiB
JavaScript
386 lines
12 KiB
JavaScript
import Component from '@glimmer/component';
|
|
import nql from '@nexes/nql-lang';
|
|
import {A} from '@ember/array';
|
|
import {action} from '@ember/object';
|
|
import {inject as service} from '@ember/service';
|
|
import {tracked} from '@glimmer/tracking';
|
|
|
|
const FILTER_PROPERTIES = [
|
|
// Basic
|
|
// {label: 'Name', name: 'name', group: 'Basic'},
|
|
// {label: 'Email', name: 'email', group: 'Basic'},
|
|
// {label: 'Location', name: 'location', group: 'Basic'},
|
|
{label: 'Label', name: 'label', group: 'Basic'},
|
|
{label: 'Newsletter subscription', name: 'subscribed', group: 'Basic'},
|
|
|
|
// Member subscription
|
|
{label: 'Member status', name: 'status', group: 'Subscription'},
|
|
// {label: 'Tier', name: 'tier', group: 'Subscription'},
|
|
{label: 'Billing period', name: 'subscriptions.plan_interval', group: 'Subscription'},
|
|
{label: 'Stripe subscription status', name: 'subscriptions.status', group: 'Subscription'},
|
|
|
|
// Emails
|
|
{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'},
|
|
];
|
|
|
|
const FILTER_RELATIONS_OPTIONS = {
|
|
subscribed: [
|
|
{label: 'is', name: 'is'},
|
|
{label: 'is not', name: 'is-not'}
|
|
],
|
|
name: [
|
|
{label: 'is', name: 'is'},
|
|
{label: 'is not', name: 'is-not'}
|
|
],
|
|
email: [
|
|
{label: 'is', name: 'is'},
|
|
{label: 'is not', name: 'is-not'}
|
|
],
|
|
status: [
|
|
{label: 'is', name: 'is'},
|
|
{label: 'is not', name: 'is-not'}
|
|
],
|
|
'subscriptions.plan_interval': [
|
|
{label: 'is', name: 'is'},
|
|
{label: 'is not', name: 'is-not'}
|
|
],
|
|
'subscriptions.status': [
|
|
{label: 'is', name: 'is'},
|
|
{label: 'is not', name: 'is-not'}
|
|
],
|
|
label: [
|
|
{label: 'is', name: 'is'},
|
|
{label: 'is not', name: 'is-not'}
|
|
],
|
|
email_count: [
|
|
{label: 'is', name: 'is'},
|
|
{label: 'is greater than', name: 'is-greater'},
|
|
{label: 'is less than', name: 'is-less'}
|
|
],
|
|
email_opened_count: [
|
|
{label: 'is', name: 'is'},
|
|
{label: 'is greater than', name: 'is-greater'},
|
|
{label: 'is less than', name: 'is-less'}
|
|
],
|
|
email_open_rate: [
|
|
{label: 'is', name: 'is'},
|
|
{label: 'is greater than', name: 'is-greater'},
|
|
{label: 'is less than', name: 'is-less'}
|
|
]
|
|
};
|
|
|
|
const FILTER_VALUE_OPTIONS = {
|
|
'subscriptions.plan_interval': [
|
|
{label: 'Monthly', name: 'month'},
|
|
{label: 'Yearly', name: 'year'}
|
|
],
|
|
status: [
|
|
{label: 'Paid', name: 'paid'},
|
|
{label: 'Free', name: 'free'},
|
|
{label: 'Complimentary', name: 'comped'}
|
|
],
|
|
subscribed: [
|
|
{label: 'Subscribed', name: 'true'},
|
|
{label: 'Unsubscribed', name: 'false'}
|
|
],
|
|
'subscriptions.status': [
|
|
{label: 'Active', name: 'active'},
|
|
{label: 'Trialing', name: 'trialing'},
|
|
{label: 'Canceled', name: 'canceled'},
|
|
{label: 'Unpaid', name: 'unpaid'},
|
|
{label: 'Past Due', name: 'past_due'},
|
|
{label: 'Incomplete', name: 'incomplete'},
|
|
{label: 'Incomplete - Expired', name: 'incomplete_expired'}
|
|
]
|
|
};
|
|
|
|
class Filter {
|
|
@tracked type;
|
|
@tracked value;
|
|
@tracked relation;
|
|
@tracked relationOptions;
|
|
|
|
constructor(options) {
|
|
this.id = options.id;
|
|
this.type = options.type;
|
|
this.value = options.value;
|
|
this.relation = options.relation;
|
|
this.relationOptions = options.relationOptions;
|
|
}
|
|
}
|
|
|
|
export default class MembersFilter extends Component {
|
|
@service session;
|
|
|
|
@tracked filters = A([
|
|
new Filter({
|
|
id: `filter-0`,
|
|
type: 'label',
|
|
relation: 'is',
|
|
value: [],
|
|
relationOptions: FILTER_RELATIONS_OPTIONS.label
|
|
})
|
|
]);
|
|
|
|
availableFilterProperties = FILTER_PROPERTIES;
|
|
availableFilterRelationsOptions = FILTER_RELATIONS_OPTIONS;
|
|
availableFilterValueOptions = FILTER_VALUE_OPTIONS;
|
|
nextFilterId = 1;
|
|
|
|
get totalFilters() {
|
|
return this.filters?.length;
|
|
}
|
|
|
|
constructor(...args) {
|
|
super(...args);
|
|
|
|
if (this.args.defaultFilterParam) {
|
|
this.parseNqlFilter(this.args.defaultFilterParam);
|
|
}
|
|
}
|
|
|
|
@action
|
|
addFilter() {
|
|
this.filters.pushObject(new Filter({
|
|
id: `filter-${this.nextFilterId}`,
|
|
type: 'label',
|
|
relation: 'is',
|
|
value: [],
|
|
relationOptions: FILTER_RELATIONS_OPTIONS.label
|
|
}));
|
|
this.nextFilterId = this.nextFilterId + 1;
|
|
this.applySoftFilter();
|
|
}
|
|
|
|
@action
|
|
onDropdownClose() {
|
|
this.applyFilter();
|
|
}
|
|
|
|
generateNqlFilter(filters) {
|
|
let query = '';
|
|
filters.forEach((filter) => {
|
|
if (filter.type === 'label' && filter.value?.length) {
|
|
const relationStr = filter.relation === 'is-not' ? '-' : '';
|
|
const filterValue = '[' + filter.value.join(',') + ']';
|
|
query += `${filter.type}:${relationStr}${filterValue}+`;
|
|
} else {
|
|
const relationStr = this.getFilterRelationOperator(filter.relation);
|
|
const filterValue = (typeof filter.value === 'string' && filter.value.includes(' ')) ? `'${filter.value}'` : filter.value;
|
|
query += `${filter.type}:${relationStr}${filterValue}+`;
|
|
}
|
|
});
|
|
return query.slice(0, -1);
|
|
}
|
|
|
|
parseNqlFilterKey(nqlFilter) {
|
|
const keys = Object.keys(nqlFilter);
|
|
const key = keys[0];
|
|
const value = nqlFilter[key];
|
|
const filterId = this.nextFilterId;
|
|
|
|
if (typeof value === 'object') {
|
|
if (value.$in !== undefined && key === 'label') {
|
|
this.nextFilterId = this.nextFilterId + 1;
|
|
return new Filter({
|
|
id: `filter-${filterId}`,
|
|
type: key,
|
|
relation: 'is',
|
|
value: value.$in,
|
|
relationOptions: FILTER_RELATIONS_OPTIONS[key]
|
|
});
|
|
}
|
|
if (value.$nin !== undefined && key === 'label') {
|
|
this.nextFilterId = this.nextFilterId + 1;
|
|
return new Filter({
|
|
id: `filter-${filterId}`,
|
|
type: key,
|
|
relation: 'is-not',
|
|
value: value.$nin,
|
|
relationOptions: FILTER_RELATIONS_OPTIONS[key]
|
|
});
|
|
}
|
|
if (value.$ne !== undefined) {
|
|
this.nextFilterId = this.nextFilterId + 1;
|
|
return new Filter({
|
|
id: `filter-${filterId}`,
|
|
type: key,
|
|
relation: 'is-not',
|
|
value: value.$ne,
|
|
relationOptions: FILTER_RELATIONS_OPTIONS[key]
|
|
});
|
|
}
|
|
if (value.$gt !== undefined) {
|
|
this.nextFilterId = this.nextFilterId + 1;
|
|
return new Filter({
|
|
id: `filter-${filterId}`,
|
|
type: key,
|
|
relation: 'is-greater',
|
|
value: value.$gt ,
|
|
relationOptions: FILTER_RELATIONS_OPTIONS[key]
|
|
});
|
|
}
|
|
|
|
if (value.$lt !== undefined) {
|
|
this.nextFilterId = this.nextFilterId + 1;
|
|
return new Filter({
|
|
id: `filter-${filterId}`,
|
|
type: key,
|
|
relation: 'is-less',
|
|
value: value.$lt,
|
|
relationOptions: FILTER_RELATIONS_OPTIONS[key]
|
|
});
|
|
}
|
|
|
|
return null;
|
|
} else {
|
|
this.nextFilterId = this.nextFilterId + 1;
|
|
|
|
return new Filter({
|
|
id: `filter-${filterId}`,
|
|
type: key,
|
|
relation: 'is',
|
|
value: value,
|
|
relationOptions: FILTER_RELATIONS_OPTIONS[key]
|
|
});
|
|
}
|
|
}
|
|
|
|
parseNqlFilter(filterParam) {
|
|
const validKeys = Object.keys(FILTER_RELATIONS_OPTIONS);
|
|
const filters = nql.parse(filterParam);
|
|
const filterKeys = Object.keys(filters);
|
|
|
|
let filterData = [];
|
|
|
|
if (filterKeys?.length === 1 && validKeys.includes(filterKeys[0])) {
|
|
const filterObj = this.parseNqlFilterKey(filters);
|
|
filterData = [filterObj];
|
|
} else if (filters?.$and) {
|
|
const andFilters = filters?.$and || [];
|
|
filterData = andFilters.filter((nqlFilter) => {
|
|
const _filterKeys = Object.keys(nqlFilter);
|
|
if (_filterKeys?.length === 1 && validKeys.includes(_filterKeys[0])) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}).map((nqlFilter) => {
|
|
return this.parseNqlFilterKey(nqlFilter);
|
|
}).filter((nqlFilter) => {
|
|
return !!nqlFilter;
|
|
});
|
|
}
|
|
|
|
this.filters = A(filterData);
|
|
}
|
|
|
|
getFilterRelationOperator(relation) {
|
|
if (relation === 'is-not') {
|
|
return '-';
|
|
} else if (relation === 'is-greater') {
|
|
return '>';
|
|
} else if (relation === 'is-less') {
|
|
return '<';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
@action
|
|
deleteFilter(filterId, event) {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
const filterToDelete = this.filters.findBy('id', filterId);
|
|
if (this.filters.length === 1) {
|
|
this.resetFilter();
|
|
} else {
|
|
this.filters.removeObject(filterToDelete);
|
|
this.applySoftFilter();
|
|
}
|
|
}
|
|
|
|
@action
|
|
setFilterType(filterId, newType) {
|
|
let defaultValue = this.availableFilterValueOptions[newType]
|
|
? this.availableFilterValueOptions[newType][0].name
|
|
: '';
|
|
|
|
if (newType === 'label' && !defaultValue) {
|
|
defaultValue = [];
|
|
}
|
|
|
|
const filterToEdit = this.filters.findBy('id', filterId);
|
|
if (filterToEdit) {
|
|
filterToEdit.type = newType;
|
|
filterToEdit.relationOptions = this.availableFilterRelationsOptions[newType];
|
|
filterToEdit.value = defaultValue;
|
|
}
|
|
|
|
if (newType !== 'label' && defaultValue) {
|
|
this.applySoftFilter();
|
|
}
|
|
}
|
|
|
|
@action
|
|
setFilterRelation(filterId, newRelation) {
|
|
const filterToEdit = this.filters.findBy('id', filterId);
|
|
filterToEdit.relation = newRelation;
|
|
this.applySoftFilter();
|
|
}
|
|
|
|
@action
|
|
setFilterValue(filterType, filterId, filterValue) {
|
|
const filterToEdit = this.filters.findBy('id', filterId);
|
|
filterToEdit.value = filterValue;
|
|
this.applySoftFilter();
|
|
}
|
|
|
|
@action
|
|
applySoftFilter() {
|
|
const validFilters = this.filters.filter((fil) => {
|
|
if (fil.type === 'label') {
|
|
return fil.value?.length;
|
|
}
|
|
return fil.value;
|
|
});
|
|
const query = this.generateNqlFilter(validFilters);
|
|
this.args.onApplySoftFilter(query, validFilters);
|
|
}
|
|
|
|
@action
|
|
applyFilter() {
|
|
const validFilters = this.filters.filter((fil) => {
|
|
if (fil.type === 'label') {
|
|
return fil.value?.length;
|
|
}
|
|
return fil.value;
|
|
});
|
|
|
|
const query = this.generateNqlFilter(validFilters);
|
|
this.args.onApplyFilter(query, validFilters);
|
|
}
|
|
|
|
@action
|
|
resetFilter() {
|
|
this.nextFilterId = 1;
|
|
this.filters = A([
|
|
new Filter({
|
|
id: `filter-0`,
|
|
type: 'label',
|
|
relation: 'is',
|
|
value: [],
|
|
relationOptions: FILTER_RELATIONS_OPTIONS.label
|
|
})
|
|
]);
|
|
this.args.onResetFilter();
|
|
}
|
|
}
|