Added dynamic value selection UI for filter dropdown

refs https://github.com/TryGhost/Team/issues/943

The filter UI behind labs in Admin allows filtering members list across several filters. Since each filter type can have its own specific set of values to choose from, this change adds custom UI based on filter type to select filter value.
This commit is contained in:
Rishabh 2021-08-12 13:33:32 +05:30
parent e224c96bba
commit 0aa7aca560
6 changed files with 185 additions and 21 deletions

View File

@ -44,10 +44,9 @@
/>
{{svg-jar "arrow-down-small"}}
</span>
<GhTextInput
@name="filter-value-1"
@value={{filter.value}}
@input={{fn this.setFilterValue filter.id}}
<GhMembersFilterValueLabs
@filter={{filter}}
@setFilterValue={{this.setFilterValue}}
/>
<button
type="button"

View File

@ -9,7 +9,7 @@ const FILTER_PROPERTIES = [
{label: 'Name', name: 'name', group: 'Basic'},
{label: 'Email', name: 'email', group: 'Basic'},
// {label: 'Location', name: 'location', group: 'Basic'},
{label: 'Newsletter subscription status', name: 'subscribed', group: 'Basic'},
{label: 'Newsletter subscription', name: 'subscribed', group: 'Basic'},
{label: 'Label', name: 'label', group: 'Basic'},
// Member subscription
@ -71,9 +71,15 @@ export default class GhMembersFilterLabsComponent extends Component {
generateNqlFilter(filters) {
let query = '';
filters.forEach((filter) => {
const relationStr = filter.relation === 'is-not' ? '-' : '';
const filterValue = filter.value.includes(' ') ? `'${filter.value}'` : filter.value;
query += `${filter.type}:${relationStr}${filterValue}+`;
if (filter.type === 'label') {
const relationStr = filter.relation === 'is-not' ? '-' : '';
const filterValue = '[' + filter.value.join(',') + ']';
query += `${filter.type}:${relationStr}${filterValue}+`;
} else {
const relationStr = filter.relation === 'is-not' ? '-' : '';
const filterValue = filter.value.includes(' ') ? `'${filter.value}'` : filter.value;
query += `${filter.type}:${relationStr}${filterValue}+`;
}
});
return query.slice(0, -1);
}
@ -88,6 +94,7 @@ export default class GhMembersFilterLabsComponent extends Component {
setFilterType(filterId, newType) {
const filterToEdit = this.filters.findBy('id', filterId);
filterToEdit.set('type', newType);
filterToEdit.set('value', '');
}
@action
@ -97,9 +104,13 @@ export default class GhMembersFilterLabsComponent extends Component {
}
@action
setFilterValue(filterId, event) {
setFilterValue(filterType, filterId, filterValue) {
const filterToEdit = this.filters.findBy('id', filterId);
filterToEdit.set('value', event.target.value);
if (filterType === 'label') {
filterToEdit.set('value', filterValue);
} else {
filterToEdit.set('value', filterValue);
}
}
@action

View File

@ -0,0 +1,85 @@
{{#if (eq @filter.type 'label')}}
<GhMemberLabelInput
@onChange={{fn this.setLabelsFilterValue @filter.type @filter.id}}
@triggerId="label-input"
data-test-input=""
/>
{{else if (eq @filter.type 'status')}}
<span class="gh-select">
<OneWaySelect
@value={{filter.value}}
@options={{this.availableFilterOptions.status}}
@optionValuePath="name"
@optionLabelPath="label"
@optionTargetPath="name"
@update={{fn this.setFilterValue @filter.type @filter.id}}
/>
{{svg-jar "arrow-down-small"}}
</span>
{{else if (eq @filter.type 'email_count')}}
<GhTextInput
@value={{@filter.value}}
@type="number"
@input={{fn this.setInputFilterValue @filter.type @filter.id}}
/>
{{else if (eq @filter.type 'email_opened_count')}}
<GhTextInput
@value={{@filter.value}}
@type="number"
@input={{fn this.setInputFilterValue @filter.type @filter.id}}
/>
{{else if (eq @filter.type 'email_open_rate')}}
<GhTextInput
@value={{@filter.value}}
@type="number"
@input={{fn this.setInputFilterValue @filter.type @filter.id}}
/>
{{else if (eq @filter.type 'subscriptions.plan_interval')}}
<span class="gh-select">
<OneWaySelect
@value={{filter.value}}
@options={{this.availableFilterOptions.subscriptionPriceInterval}}
@optionValuePath="name"
@optionLabelPath="label"
@optionTargetPath="name"
@update={{fn this.setFilterValue @filter.type @filter.id}}
/>
{{svg-jar "arrow-down-small"}}
</span>
{{else if (eq @filter.type 'subscriptions.status')}}
<span class="gh-select">
<OneWaySelect
@value={{filter.value}}
@options={{this.availableFilterOptions.subscriptionStripeStatus}}
@optionValuePath="name"
@optionLabelPath="label"
@optionTargetPath="name"
@update={{fn this.setFilterValue @filter.type @filter.id}}
/>
{{svg-jar "arrow-down-small"}}
</span>
{{else if (eq @filter.type 'subscribed')}}
<span class="gh-select">
<OneWaySelect
@value={{filter.value}}
@options={{this.availableFilterOptions.subscribed}}
@optionValuePath="name"
@optionLabelPath="label"
@optionTargetPath="name"
@update={{fn this.setFilterValue @filter.type @filter.id}}
/>
{{svg-jar "arrow-down-small"}}
</span>
{{else}}
<GhTextInput
@name={{@filter.id}}
@value={{@filter.value}}
@input={{fn this.setInputFilterValue @filter.type @filter.id}}
/>
{{/if}}

View File

@ -0,0 +1,49 @@
import Component from '@glimmer/component';
import {action} from '@ember/object';
const FILTER_OPTIONS = {
subscriptionPriceInterval: [
{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'}
],
subscriptionStripeStatus: [
{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'}
]
};
export default class GhMembersFilterValueLabs extends Component {
constructor(...args) {
super(...args);
this.availableFilterOptions = FILTER_OPTIONS;
}
@action
setInputFilterValue(filterType, filterId, event) {
this.args.setFilterValue(filterType, filterId, event.target.value);
}
@action
setLabelsFilterValue(filterType, filterId, labels) {
this.args.setFilterValue(filterType, filterId, labels.map(label => label.slug));
}
@action
setFilterValue(filterType, filterId, value) {
this.args.setFilterValue(filterType, filterId, value);
}
}

View File

@ -2,9 +2,8 @@
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
<span class="midlightgrey">{{labels}}</span>
</LinkTo>
{{/if}}
{{#if (eq @filterColumn 'status')}}
{{else if (eq @filterColumn 'status')}}
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
{{#if (not (is-empty @member.status))}}
<span class="gh-members-list-open-rate-mobile">{{capitalize @member.status}}</span>
@ -12,9 +11,8 @@
<span class="midlightgrey">-</span>
{{/if}}
</LinkTo>
{{/if}}
{{#if (eq @filterColumn 'email_count')}}
{{else if (eq @filterColumn 'email_count')}}
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
{{#if (not (is-empty @member.emailCount))}}
<span class="gh-members-list-open-rate-mobile">{{@member.emailCount}}</span>
@ -22,9 +20,8 @@
<span class="midlightgrey">-</span>
{{/if}}
</LinkTo>
{{/if}}
{{#if (eq @filterColumn 'email_opened_count')}}
{{else if (eq @filterColumn 'email_opened_count')}}
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
{{#if (not (is-empty @member.emailOpenedCount))}}
<span class="gh-members-list-open-rate-mobile">{{@member.emailOpenedCount}}</span>
@ -32,9 +29,8 @@
<span class="midlightgrey">-</span>
{{/if}}
</LinkTo>
{{/if}}
{{#if (eq @filterColumn 'subscribed')}}
{{else if (eq @filterColumn 'subscribed')}}
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
{{#if (not (is-empty @member.subscribed))}}
<span class="gh-members-list-open-rate-mobile">{{@member.subscribed}}</span>
@ -42,4 +38,20 @@
<span class="midlightgrey">-</span>
{{/if}}
</LinkTo>
{{else if (eq @filterColumn 'subscriptions.status')}}
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
{{#if (not (is-empty @member.subscriptions?.[0]?.status))}}
<span class="gh-members-list-open-rate-mobile">{{@member.subscriptions?.[0]?.status}}</span>
{{else}}
<span class="midlightgrey">-</span>
{{/if}}
</LinkTo>
{{else if (eq @filterColumn 'subscriptions.plan_interval')}}
<LinkTo @route="member" @model={{@member}} title="Member details" class="gh-list-data middarkgrey f8">
{{#if (not (is-empty @member.subscriptions?.[0]?.plan?.interval))}}
<span class="gh-members-list-open-rate-mobile">{{@member.subscriptions?.[0]?.plan?.interval}}</span>
{{else}}
<span class="midlightgrey">-</span>
{{/if}}
</LinkTo>
{{/if}}

View File

@ -35,8 +35,8 @@
grid-column-gap: 8px;
}
.gh-filter-builder .gh-input,
.gh-filter-builder .gh-select,
.gh-filter-builder .gh-input,
.gh-filter-builder .gh-select,
.gh-filter-builder select {
height: 33px;
font-size: 1.35rem;
@ -108,4 +108,12 @@
align-items: center;
justify-content: flex-end;
margin-top: 20px;
}
}
.gh-filter-block .ember-power-select-multiple-trigger {
height: 33px !important;
}
.gh-filter-block .label-token {
margin: 2px !important;
}