Skip to content

Commit

Permalink
Merge pull request #90 from nossas/hotfix/query-email-stats
Browse files Browse the repository at this point in the history
fix(notifications): count messages and events with precision
  • Loading branch information
igr-santos authored Jul 4, 2024
2 parents 7fdaf99 + d0432b5 commit e424189
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 54 deletions.
110 changes: 61 additions & 49 deletions packages/notifications/src/graphql-api/resolvers/email_stats.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
import logger, { apmAgent } from "../../logger";
import { client } from "../../core/elasticsearchdb";

interface EmailEvent {
email: string;
event: string;
timestamp: number;
sg_event_id: string;
sg_message_id: string;
useragent: string;
ip: string;
url: string;
}

interface EmailStatsResponse {
events: EmailEvent[];
total: number;
stats: {
open: number;
delivered: number;
bounced: number;
processed: number;
click: number;
total: number;
processed?: number;
delivered?: number;
open?: number;
unsubscribe?: number;
bounce?: number;
deferred?: number;
dropped?: number;
spamreport?: number;
click?: number;
};
}

Expand All @@ -29,6 +22,11 @@ interface EmailStatsArgs {
category?: string;
}

interface Bucket {
key: 'processed' | 'delivered' | 'open' | 'unsubscribe' | 'bounce' | 'deferred' | 'dropped' | 'spamreport' | 'click';
doc_count: number;
}


export default async (_: void, args: EmailStatsArgs): Promise<EmailStatsResponse> => {
const { widget_id, category } = args;
Expand All @@ -46,51 +44,65 @@ export default async (_: void, args: EmailStatsArgs): Promise<EmailStatsResponse
terms: {
"category.keyword": categories
}
},
{
bool: {
should: [
{ match: { event: "delivered" } },
{ match: { event: "bounced" } },
{ match: { event: "processed" } },
{ match: { event: "click" } },
{ match: { event: "open" } }
]
}
}
]
}
};

const aggs = {
messages_count: {
scripted_metric: {
params: {
fieldName: "sg_message_id"
},
init_script: "state.list = []",
map_script: `
if(params['_source'][params.fieldName] != null)
state.list.add(params['_source'][params.fieldName]);
`,
combine_script: "return state.list;",
reduce_script: `
Map uniqueValueMap = new HashMap();
int count = 0;
for(shardList in states) {
if(shardList != null) {
for(key in shardList) {
if(!uniqueValueMap.containsKey(key)) {
count +=1;
uniqueValueMap.put(key, key);
}
}
}
}
return count;
`
}
},
"events_count": {
"terms": {
"field": "event"
}
}
}

try {
const { body } = await client.search({
index: "events-sendgrid-*",
body: { query: query }
body: { query, aggs }
});

const events = body.hits.hits.map((hit: any) => ({
email: hit._source.email,
event: hit._source.event,
timestamp: hit._source.timestamp,
sg_event_id: hit._source.sg_event_id,
sg_message_id: hit._source.sg_message_id,
useragent: hit._source.useragent,
ip: hit._source.ip,
url: hit._source.url,
}));
const stats = {};
body.aggregations.events_count.buckets.forEach((item: Bucket) => {
stats[item.key] = item.doc_count
})

const stats = {
open: events.filter(event => event.event === "open").length,
delivered: events.filter(event => event.event === "delivered").length,
bounced: events.filter(event => event.event === "bounced").length,
processed: events.filter(event => event.event === "processed").length,
click: events.filter(event => event.event === "click").length,
total: events.length
return {
total: body.aggregations.messages_count.value,
stats
};

return { events, stats };
} catch (error) {
logger.error(`Failed to fetch email stats`);
console.log("error", error);
logger.error(`Failed to fetch email stats`);78152
throw new Error(`Failed to fetch email stats`);
}
};
13 changes: 8 additions & 5 deletions packages/notifications/src/graphql-api/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,19 @@ type EmailEvent {
}

type EmailStats {
open: Int
delivered: Int
bounced: Int
processed: Int
delivered: Int
open: Int
unsubscribe: Int
bounce: Int
deferred: Int
dropped: Int
spamreport: Int
click: Int
total: Int
}

type EmailStatsResponse {
events: [EmailEvent]
total: Int!
stats: EmailStats
}

Expand Down

0 comments on commit e424189

Please sign in to comment.