Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 9 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,13 @@ module.exports = {
'require-jsdoc': 'warn',
'no-shadow': 'warn',
'no-unused-expressions': 'warn'
}
},
overrides: [
{
files: ['*.js'],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off'
}
}
]
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hawk.api",
"version": "1.1.14",
"version": "1.1.15",
"main": "index.ts",
"license": "UNLICENSED",
"scripts": {
Expand Down
125 changes: 63 additions & 62 deletions src/models/eventsFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,23 @@ class EventsFactory extends Factory {
* @param {Number} skip - certain number of documents to skip
* @param {'BY_DATE' | 'BY_COUNT'} sort - events sort order
* @param {EventsFilters} filters - marks by which events should be filtered
* @param {String} search - Search query
*
* @return {RecentEventSchema[]}
*/
async findRecent(
limit = 10,
skip = 0,
sort = 'BY_DATE',
filters = {}
filters = {},
search = ''
) {
if (typeof search !== 'string') {
throw new Error('Search parameter must be a string');
}

const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

limit = this.validateLimit(limit);

switch (sort) {
Expand All @@ -184,71 +192,64 @@ class EventsFactory extends Factory {
},
];

/**
* If some events should be omitted, use alternative pipeline
*/
if (Object.values(filters).length > 0) {
pipeline.push(
/**
* Lookup events object for each daily event
*/
{
$lookup: {
from: 'events:' + this.projectId,
localField: 'groupHash',
foreignField: 'groupHash',
as: 'event',
const searchFilter = search.trim().length > 0
? {
$or: [
{
'event.payload.title': {
$regex: escapedSearch,
$options: 'i',
},
},
},
{
$unwind: '$event',
},
/**
* Match filters
*/
{
$match: {
...Object.fromEntries(
Object
.entries(filters)
.map(([mark, exists]) => [`event.marks.${mark}`, { $exists: exists } ])
),
{
'event.payload.backtrace.file': {
$regex: escapedSearch,
$options: 'i',
},
},
],
}
: {};

const matchFilter = filters
? Object.fromEntries(
Object
.entries(filters)
.map(([mark, exists]) => [`event.marks.${mark}`, { $exists: exists } ])
)
: {};

pipeline.push(
{
$lookup: {
from: 'events:' + this.projectId,
localField: 'groupHash',
foreignField: 'groupHash',
as: 'event',
},
{ $skip: skip },
{ $limit: limit },
{
$group: {
_id: null,
dailyInfo: { $push: '$$ROOT' },
events: { $push: '$event' },
},
},
{
$unwind: '$event',
},
{
$match: {
...matchFilter,
...searchFilter,
},
{
$unset: 'dailyInfo.event',
}
);
} else {
pipeline.push(
{ $skip: skip },
{ $limit: limit },
{
$group: {
_id: null,
groupHash: { $addToSet: '$groupHash' },
dailyInfo: { $push: '$$ROOT' },
},
},
{ $skip: skip },
{ $limit: limit },
{
$group: {
_id: null,
dailyInfo: { $push: '$$ROOT' },
events: { $push: '$event' },
},
{
$lookup: {
from: 'events:' + this.projectId,
localField: 'groupHash',
foreignField: 'groupHash',
as: 'events',
},
}
);
}
},
{
$unset: 'dailyInfo.event',
}
);

const cursor = this.getCollection(this.TYPES.DAILY_EVENTS).aggregate(pipeline);

Expand Down Expand Up @@ -316,7 +317,7 @@ class EventsFactory extends Factory {
});

/**
* Group events using 'groupByTimestamp:NNNNNNNN' key
* Group events using 'groupingTimestamp:NNNNNNNN' key
* @type {ProjectChartItem[]}
*/
const groupedData = groupBy('groupingTimestamp')(dailyEvents);
Expand Down
12 changes: 10 additions & 2 deletions src/resolvers/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const ProjectModel = require('../models/project').default;
const EVENTS_GROUP_HASH_INDEX_NAME = 'groupHashUnique';
const REPETITIONS_GROUP_HASH_INDEX_NAME = 'groupHash_hashed';
const REPETITIONS_USER_ID_INDEX_NAME = 'userId';
const MAX_SEARCH_QUERY_LENGTH = 50;

/**
* See all types and fields here {@see ../typeDefs/project.graphql}
Expand Down Expand Up @@ -304,13 +305,20 @@ module.exports = {
* @param {Number} skip - certain number of documents to skip
* @param {'BY_DATE' | 'BY_COUNT'} sort - events sort order
* @param {EventsFilters} filters - marks by which events should be filtered
* @param {String} search - search query
*
* @return {Promise<RecentEventSchema[]>}
*/
async recentEvents(project, { limit, skip, sort, filters }) {
async recentEvents(project, { limit, skip, sort, filters, search }) {
if (search) {
if (search.length > MAX_SEARCH_QUERY_LENGTH) {
search = search.slice(0, MAX_SEARCH_QUERY_LENGTH);
}
}

const factory = new EventsFactory(project._id);

return factory.findRecent(limit, skip, sort, filters);
return factory.findRecent(limit, skip, sort, filters, search);
},

/**
Expand Down
3 changes: 3 additions & 0 deletions src/typeDefs/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ type Project {

"Event marks by which events should be sorted"
filters: EventsFiltersInput

"Search query"
search: String
): RecentEvents
"""
Return events that occurred after a certain timestamp
Expand Down
Loading