Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added new members filters and refactored filters #15667

Merged
merged 2 commits into from
Oct 21, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions ghost/admin/app/components/gh-resource-select.hbs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
<GhTokenInput
<PowerSelect
@searchEnabled={{true}}
@options={{this.options}}
@selected={{this.selectedOptions}}
@disabled={{or @disabled this.fetchOptionsTask.isRunning}}
@optionsComponent={{component "power-select/options"}}
@allowCreation={{false}}
@renderInPlace={{this.renderInPlace}}
@selected={{this.selectedOption}}
@onChange={{this.onChange}}
@class="select-members"
@triggerClass="gh-resource-select-trigger"
@dropdownClass="gh-resource-select-dropdown"
@placeholder={{this.placeholderText}}
as |resource|
@renderInPlace={{this.renderInPlace}}
@disabled={{or @disabled this.fetchOptionsTask.isRunning}}
@optionsComponent={{component "power-select/options"}}
@search={{this.searchAndSuggest}}
@searchField={{this.searchField}}
@searchMessage={{@searchMessage}}
@searchPlaceholder={{this.searchPlaceholderText}}
as |resource|
>
{{resource.name}}
</GhTokenInput>
{{resource.title}}
</PowerSelect>
71 changes: 61 additions & 10 deletions ghost/admin/app/components/gh-resource-select.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import Component from '@glimmer/component';
import {action} from '@ember/object';
import {A} from '@ember/array';
import {action, get} from '@ember/object';
import {
defaultMatcher,
filterOptions
} from 'ember-power-select/utils/group-utils';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
Expand All @@ -13,6 +18,45 @@ export default class GhResourceSelect extends Component {
return this.args.renderInPlace === undefined ? false : this.args.renderInPlace;
}

get searchField() {
return this.args.searchField === undefined ? 'name' : this.args.searchField;
}

@action
searchAndSuggest(term, select) {
return this.searchAndSuggestTask.perform(term, select);
}

@task
*searchAndSuggestTask(term) {
let newOptions = this.flatOptions.toArray();

if (term.length === 0) {
return newOptions;
}

// todo: we can do actual filtering on posts here (allow searching when we have lots and lots of posts)
yield undefined;

newOptions = this._filter(A(newOptions), term);

return newOptions;
}

get matcher() {
return this.args.matcher || defaultMatcher;
}

_filter(options, searchText) {
let matcher;
if (this.searchField) {
matcher = (option, text) => this.matcher(get(option, this.searchField), text);
} else {
matcher = (option, text) => this.matcher(option, text);
}
return filterOptions(options || [], searchText, matcher);
}

constructor() {
super(...arguments);
this.fetchOptionsTask.perform();
Expand All @@ -38,9 +82,12 @@ export default class GhResourceSelect extends Component {
return options;
}

get selectedOptions() {
const resources = this.args.resources || [];
return this.flatOptions.filter(option => resources.find(resource => resource.id === option.id));
get selectedOption() {
if (this.args.resource.title) {
return this.args.resource;
}
const resource = this.args.resource ?? {};
return this.flatOptions.find(option => resource.id === option.id);
}

@action
Expand All @@ -55,16 +102,20 @@ export default class GhResourceSelect extends Component {
return 'Select a page/post';
}

get searchPlaceholderText() {
if (this.args.type === 'email') {
return 'Search emails';
}
return 'Search posts/pages';
}

@task
*fetchOptionsTask() {
const options = yield [];

if (this.args.type === 'email') {
const posts = yield this.store.query('post', {filter: '(status:published,status:sent)+newsletter_id:-null', limit: 'all'});
options.push({
groupName: 'Emails',
options: posts.map(mapResource)
});
options.push(...posts.map(mapResource));
this._options = options;
return;
}
Expand All @@ -74,8 +125,8 @@ export default class GhResourceSelect extends Component {

function mapResource(resource) {
return {
name: resource.title,
id: resource.id
id: resource.id,
title: resource.title
};
}

Expand Down
75 changes: 8 additions & 67 deletions ghost/admin/app/components/members/filter-value.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -47,47 +47,15 @@
<GhResourceSelect
@onChange={{fn this.setResourceFilterValue @filter}}
@type={{this.resourceFilterType}}
@resources={{this.resourceFilterValue}}
@resource={{this.resourceFilterValue}}
/>
</div>

{{else if (eq @filter.type 'subscribed')}}
{{else if (eq @filter.valueType 'options')}}
<span class="gh-select">
<OneWaySelect
@value={{@filter.value}}
@options={{this.availableFilterOptions.subscribed}}
@optionValuePath="name"
@optionLabelPath="label"
@optionTargetPath="name"
@update={{fn @setFilterValue @filter}}
data-test-select="members-filter-value"
/>
{{svg-jar "arrow-down-small"}}
</span>

{{else if (eq @filter.type 'last_seen_at')}}
<GhDatePicker
@value={{@filter.value}}
@maxDate={{now}}
@maxDateError="Must be in the past"
@onChange={{fn @setFilterValue @filter}}
data-test-input="members-filter-value"
/>

{{else if (eq @filter.type 'created_at')}}
<GhDatePicker
@value={{@filter.value}}
@maxDate={{now}}
@maxDateError="Must be in the past"
@onChange={{fn @setFilterValue @filter}}
data-test-input="members-filter-value"
/>

{{else if (eq @filter.type 'status')}}
<span class="gh-select">
<OneWaySelect
@value={{@filter.value}}
@options={{this.availableFilterOptions.status}}
@options={{@filter.options}}
@optionValuePath="name"
@optionLabelPath="label"
@optionTargetPath="name"
Expand Down Expand Up @@ -132,46 +100,19 @@
{{on "input" (fn this.setInputFilterValue @filter)}}
{{on "blur" (fn this.updateInputFilterValue @filter)}}
{{on "keypress" (fn this.updateInputFilterValueOnEnter @filter)}}
data-test-input="members-filter-value"
data-test-input="members-filter-value"
/>
</div>

{{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 @setFilterValue @filter}}
data-test-select="members-filter-value"
/>
{{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 @setFilterValue @filter}}
data-test-select="members-filter-value"
/>
{{svg-jar "arrow-down-small"}}
</span>

{{else if (eq @filter.type 'subscriptions.start_date')}}
{{else if (or (eq @filter.type 'last_seen_at') (eq @filter.type 'created_at'))}}
<GhDatePicker
@value={{@filter.value}}
@maxDate={{now}}
@maxDateError="Must be in the past"
@onChange={{fn @setFilterValue @filter}}
data-test-input="members-filter-value"
/>

{{else if (eq @filter.type 'subscriptions.current_period_end')}}
{{else if (eq @filter.valueType 'date')}}
<GhDatePicker
@value={{@filter.value}}
@onChange={{fn @setFilterValue @filter}}
Expand Down
51 changes: 10 additions & 41 deletions ghost/admin/app/components/members/filter-value.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,11 @@ import Component from '@glimmer/component';
import {action} from '@ember/object';
import {tracked} from '@glimmer/tracking';

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 MembersFilterValue extends Component {
@tracked filterValue;

constructor(...args) {
super(...args);
this.availableFilterOptions = FILTER_OPTIONS;
this.filterValue = this.args.filter.value;
}

Expand Down Expand Up @@ -80,35 +54,30 @@ export default class MembersFilterValue extends Component {
}

get isResourceFilter() {
return ['signup', 'conversion', 'emails.post_id', 'opened_emails.post_id', 'clicked_links.post_id'].includes(this.args.filter?.type);
return !!this.args.filter?.isResourceFilter;
}

get resourceFilterType() {
if (!this.isResourceFilter) {
return '';
}

if (['emails.post_id', 'opened_emails.post_id', 'clicked_links.post_id'].includes(this.args.filter?.type)) {
return 'email';
}

return '';
return this.args.filter?.properties?.resource ?? '';
}

get resourceFilterValue() {
if (!this.isResourceFilter) {
return [];
return {};
}
const resources = this.args.filter?.value || [];
return resources.map((resource) => {
return {
id: resource
};
});
const resource = this.args.filter?.resource || undefined;
const resourceId = this.args.filter?.value || undefined;
return resource ?? {
id: resourceId
};
}

@action
setResourceFilterValue(filter, resources) {
this.args.setFilterValue(filter, resources.map(resource => resource.id));
setResourceFilterValue(filter, resource) {
this.args.setResourceValue(filter, resource);
}
}
3 changes: 2 additions & 1 deletion ghost/admin/app/components/members/filter.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
@index={{index}}
@filter={{filter}}
@setFilterValue={{this.setFilterValue}}
@setResourceValue={{this.setResourceValue}}
@onLabelEdit={{@onLabelEdit}}
/>
<button
Expand Down Expand Up @@ -84,7 +85,7 @@
</button>
<button
class="gh-btn gh-btn-primary"
data-test-button="members-apply-filter" type="button" {{on "click" this.applyFilter}} {{on "keyup" this.handleSubmitKeyup}}
data-test-button="members-apply-filter" type="button" {{on "click" (fn this.applyFiltersPressed dd)}} {{on "keyup" this.handleSubmitKeyup}}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you didn't make changes to the filter, the dropdown didn't close. That is why I created a new action here that also sends a close command to the dropdown.

>
<span>Apply filters</span>
</button>
Expand Down
Loading