Added donation events to activity feeds (#17632)

fixes TryGhost/Product#3698
fixes TryGhost/Product#3699
This commit is contained in:
Simon Backx 2023-08-10 09:16:47 +02:00 committed by GitHub
parent 374bfc405c
commit 874552bdbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 103 additions and 10 deletions

View File

@ -93,8 +93,18 @@
{{#let (parse-member-event event eventsFetcher.hasMultipleNewsletters) as |parsedEvent|}}
<div class="gh-dashboard-list-item member-details">
<div class="gh-dashboard-list-item-sub">
<GhMemberAvatar @member={{parsedEvent.member}} @containerClass="w8 h8 mr3 flex-shrink-0" />
<LinkTo class="gh-dashboard-list-text" @route="member" @model="{{parsedEvent.memberId}}" data-test-dashboard-member-activity-item>{{parsedEvent.subject}}</LinkTo>
{{#if parsedEvent.member}}
<GhMemberAvatar @member={{parsedEvent.member}} @containerClass="w8 h8 mr3 flex-shrink-0" />
<LinkTo class="gh-dashboard-list-text" @route="member" @model="{{parsedEvent.memberId}}" data-test-dashboard-member-activity-item>{{parsedEvent.subject}}</LinkTo>
{{else}}
{{#if parsedEvent.email}}
<GhMemberAvatar @name={{parsedEvent.subject}} @containerClass="w8 h8 mr3 flex-shrink-0" />
<a class="gh-dashboard-list-text" href="mailto:{{parsedEvent.email}}" target="_blank" rel="noopener noreferrer" title="{{parsedEvent.email}}">{{parsedEvent.subject}}</a>
{{else}}
<GhMemberAvatar @name={{parsedEvent.subject}} @containerClass="w8 h8 mr3 flex-shrink-0" />
<span class="gh-dashboard-list-text">{{parsedEvent.subject}}</span>
{{/if}}
{{/if}}
</div>
<div class="gh-dashboard-list-item-sub">
{{svg-jar parsedEvent.icon }}

View File

@ -13,9 +13,9 @@ const stringToHslColor = function (str, saturation, lightness) {
export default class GhMemberAvatarComponent extends Component {
get memberName() {
let {member} = this.args;
let {member, name} = this.args;
return member?.name || member?.email || 'NM';
return member?.name || member?.email || name || 'NM';
}
get avatarImage() {

View File

@ -45,8 +45,18 @@
{{#let (parse-member-event event) as |parsedEvent|}}
<div class="gh-dashboard-list-item">
<div class="gh-dashboard-list-item-sub">
<GhMemberAvatar @member={{parsedEvent.member}} @containerClass="w6 h6 mr3 flex-shrink-0" />
<LinkTo class="gh-dashboard-list-text" @route="member" @model="{{parsedEvent.memberId}}" @query={{hash postAnalytics=@post.id}}>{{parsedEvent.subject}}</LinkTo>
{{#if parsedEvent.member}}
<GhMemberAvatar @member={{parsedEvent.member}} @containerClass="w6 h6 mr3 flex-shrink-0" />
<LinkTo class="gh-dashboard-list-text" @route="member" @model="{{parsedEvent.memberId}}" @query={{hash postAnalytics=@post.id}}>{{parsedEvent.subject}}</LinkTo>
{{else}}
{{#if parsedEvent.email}}
<GhMemberAvatar @name={{parsedEvent.subject}} @containerClass="w6 h6 mr3 flex-shrink-0" />
<a class="gh-dashboard-list-text" href="mailto:{{parsedEvent.email}}" target="_blank" rel="noopener noreferrer" title="{{parsedEvent.email}}">{{parsedEvent.subject}}</a>
{{else}}
<GhMemberAvatar @name={{parsedEvent.subject}} @containerClass="w6 h6 mr3 flex-shrink-0" />
<span class="gh-dashboard-list-text">{{parsedEvent.subject}}</span>
{{/if}}
{{/if}}
</div>
<div class="gh-dashboard-list-item-sub">
{{svg-jar parsedEvent.icon }}

View File

@ -10,7 +10,7 @@ export default class ParseMemberEventHelper extends Helper {
@service membersUtils;
compute([event, hasMultipleNewsletters]) {
const subject = event.data.member.name || event.data.member.email;
const subject = event.data.member ? (event.data.member.name || event.data.member.email) : (event.data.name || event.data.email || '');
const icon = this.getIcon(event);
const action = this.getAction(event, hasMultipleNewsletters);
const info = this.getInfo(event);
@ -110,6 +110,10 @@ export default class ParseMemberEventHelper extends Helper {
}
}
if (event.type === 'donation_event') {
icon = 'subscriptions';
}
return 'event-' + icon;
}
@ -203,6 +207,10 @@ export default class ParseMemberEventHelper extends Helper {
}
return 'less like this';
}
if (event.type === 'donation_event') {
return `Made a one-time payment`;
}
}
/**
@ -222,7 +230,7 @@ export default class ParseMemberEventHelper extends Helper {
* Clickable object, shown between action and info, or in a separate column in some views
*/
getObject(event) {
if (event.type === 'signup_event' || event.type === 'subscription_event') {
if (event.type === 'signup_event' || event.type === 'subscription_event' || event.type === 'donation_event') {
if (event.data.attribution?.title) {
return event.data.attribution.title;
}
@ -278,6 +286,12 @@ export default class ParseMemberEventHelper extends Helper {
return 'Free';
}
if (event.type === 'donation_event') {
const symbol = getSymbol(event.data.currency);
const formattedAmount = symbol + getNonDecimal(event.data.amount, event.data.currency);
return formattedAmount;
}
return;
}
@ -304,7 +318,7 @@ export default class ParseMemberEventHelper extends Helper {
}
}
if (['signup_event', 'subscription_event'].includes(event.type)) {
if (['signup_event', 'subscription_event', 'donation_event'].includes(event.type)) {
if (event.data.attribution && event.data.attribution.url) {
return event.data.attribution.url;
}

View File

@ -182,6 +182,7 @@ function createApiInstance(config) {
}
},
models: {
DonationPaymentEvent: models.DonationPaymentEvent,
EmailRecipient: models.EmailRecipient,
StripeCustomer: models.MemberStripeCustomer,
StripeCustomerSubscription: models.StripeCustomerSubscription,

View File

@ -37,6 +37,7 @@ module.exports = function MembersAPI({
getSubject
},
models: {
DonationPaymentEvent,
EmailRecipient,
StripeCustomer,
StripeCustomerSubscription,
@ -107,6 +108,7 @@ module.exports = function MembersAPI({
});
const eventRepository = new EventRepository({
DonationPaymentEvent,
EmailRecipient,
MemberSubscribeEvent,
MemberPaidSubscriptionEvent,

View File

@ -17,6 +17,7 @@ function replaceCustomFilterTransformer(filter) {
module.exports = class EventRepository {
constructor({
DonationPaymentEvent,
EmailRecipient,
MemberSubscribeEvent,
MemberPaymentEvent,
@ -32,6 +33,7 @@ module.exports = class EventRepository {
labsService,
memberAttributionService
}) {
this._DonationPaymentEvent = DonationPaymentEvent;
this._MemberSubscribeEvent = MemberSubscribeEvent;
this._MemberPaidSubscriptionEvent = MemberPaidSubscriptionEvent;
this._MemberPaymentEvent = MemberPaymentEvent;
@ -65,7 +67,8 @@ module.exports = class EventRepository {
{type: 'click_event', action: 'getClickEvents'},
{type: 'aggregated_click_event', action: 'getAggregatedClickEvents'},
{type: 'signup_event', action: 'getSignupEvents'},
{type: 'subscription_event', action: 'getSubscriptionEvents'}
{type: 'subscription_event', action: 'getSubscriptionEvents'},
{type: 'donation_event', action: 'getDonationEvents'}
];
// Some events are not filterable by post_id
@ -352,6 +355,59 @@ module.exports = class EventRepository {
};
}
async getDonationEvents(options = {}, filter) {
options = {
...options,
withRelated: [
'member',
'postAttribution',
'userAttribution',
'tagAttribution'
],
filter: 'member_id:-null+custom:true',
mongoTransformer: chainTransformers(
// First set the filter manually
replaceCustomFilterTransformer(filter),
// Map the used keys in that filter
...mapKeys({
'data.created_at': 'created_at',
'data.member_id': 'member_id'
}),
(f) => {
// Special one: when data.post_id is used, replace it with two filters: attribution_id:x+attribution_type:post
return expandFilters(f, [{
key: 'data.post_id',
replacement: 'attribution_id',
expansion: {attribution_type: 'post'}
}]);
}
)
};
const {data: models, meta} = await this._DonationPaymentEvent.findPage(options);
const data = models.map((model) => {
const json = model.toJSON(options);
delete json.postAttribution?.mobiledoc;
delete json.postAttribution?.lexical;
delete json.postAttribution?.plaintext;
return {
type: 'donation_event',
data: {
...json,
attribution: this._memberAttributionService.getEventAttribution(model)
}
};
});
return {
data,
meta
};
}
async getCommentEvents(options = {}, filter) {
options = {
...options,