diff --git a/package.json b/package.json index 69375d18..e55a38aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hawk.api", - "version": "1.0.6", + "version": "1.0.7", "main": "index.ts", "license": "UNLICENSED", "scripts": { diff --git a/src/models/eventsFactory.js b/src/models/eventsFactory.js index 1a380b9f..9c9a6d0c 100644 --- a/src/models/eventsFactory.js +++ b/src/models/eventsFactory.js @@ -1,4 +1,4 @@ -import { getMidnightWithTimezoneOffset, getUTCMidnight } from '../utils/dates'; +import { addTimezoneOffset, getMidnightWithTimezoneOffset, setUTCMidnight } from '../utils/dates'; import { groupBy } from '../utils/grouper'; const Factory = require('./modelFactory'); @@ -258,14 +258,14 @@ class EventsFactory extends Factory { } /** - * Fetch timestamps and total count of errors (or target error) for each day since + * Get daily events for last few days grouped by user timezone days * - * @param {number} days - how many days we need to fetch for displaying in a chart - * @param {number} timezoneOffset - user's local timezone offset in minutes - * @param {string} groupHash - event's group hash for showing only target event - * @return {ProjectChartItem[]} + * @param {number} days - days to get + * @param {number} [timezoneOffset] - user timezone offset in minutes + * @param {string} [groupHash] - event group hash + * @return {Promise} */ - async findChartData(days, timezoneOffset = 0, groupHash = '') { + async getGroupedDailyEvents(days, timezoneOffset = 0, groupHash) { const today = new Date(); const since = today.setDate(today.getDate() - days) / 1000; @@ -305,17 +305,99 @@ class EventsFactory extends Factory { * Group events using 'groupByTimestamp:NNNNNNNN' key * @type {ProjectChartItem[]} */ - const groupedData = groupBy('groupingTimestamp')(dailyEvents); + return groupBy('groupingTimestamp')(dailyEvents); + } + + /** + * Fetch timestamps and count of affected users for each day since + * + * @param {number} days - how many days we need to fetch for displaying in a chart + * @param {number} timezoneOffset - user's local timezone offset in minutes + * @param {string} groupHash - event's group hash for showing only target event + * @return {AffectedUsersChartItem[]} + */ + async findAffectedUsersChart(days, timezoneOffset = 0, groupHash = '') { + const groupedData = await this.getGroupedDailyEvents(days, timezoneOffset, groupHash); + + /** + * Now fill all requested days + */ + let result = []; + + for (let i = 0; i < days; i++) { + const day = new Date(); + + /** + * Subtract timezone offset to get user`s local day + * + * @example 22:00 UTC 25.12 === 01:00 GMT+03 26.12, so local date is 26 + */ + addTimezoneOffset(day, -timezoneOffset); + + /** + * Set midnight for user`s local date + */ + setUTCMidnight(day); + + /** + * Get date for the chart + */ + day.setDate(day.getDate() - i); + + const dayMidnight = day / 1000; + const groupedEvents = groupedData[`groupingTimestamp:${dayMidnight}`]; + const affectedUsers = groupedEvents ? groupedEvents.reduce((set, value) => new Set([...set, ...value.affectedUsers]), new Set()) : new Set(); + + result.push({ + timestamp: dayMidnight, + count: affectedUsers.size, + }); + } + + /** + * Order by time ascendance + */ + result = result.sort((a, b) => a.timestamp - b.timestamp); + + return result; + } + /** + * Fetch timestamps and total count of errors (or target error) for each day since + * + * @param {number} days - how many days we need to fetch for displaying in a chart + * @param {number} timezoneOffset - user's local timezone offset in minutes + * @param {string} groupHash - event's group hash for showing only target event + * @return {ProjectChartItem[]} + */ + async findChartData(days, timezoneOffset = 0, groupHash = '') { + const groupedData = await this.getGroupedDailyEvents(days, timezoneOffset, groupHash); /** * Now fill all requested days */ let result = []; for (let i = 0; i < days; i++) { - const now = new Date(); - const day = new Date(now.setDate(now.getDate() - i)); - const dayMidnight = getUTCMidnight(day) / 1000; + const day = new Date(); + + /** + * Subtract timezone offset to get user`s local day + * + * @example 22:00 UTC 25.12 === 01:00 GMT+03 26.12, so local date is 26 + */ + addTimezoneOffset(day, -timezoneOffset); + + /** + * Set midnight for user`s local date + */ + setUTCMidnight(day); + + /** + * Get date for the chart + */ + day.setDate(day.getDate() - i); + + const dayMidnight = day / 1000; const groupedEvents = groupedData[`groupingTimestamp:${dayMidnight}`]; result.push({ diff --git a/src/resolvers/event.js b/src/resolvers/event.js index 6faaeb16..1fe00690 100644 --- a/src/resolvers/event.js +++ b/src/resolvers/event.js @@ -89,6 +89,21 @@ module.exports = { return factories.usersFactory.dataLoaders.userById.load(assignee); }, + /** + * Return chart data for target event affected users in last few days + * + * @param {string} projectId - event's project + * @param {string} groupHash - event's groupHash + * @param {number} days - how many days we need to fetch for displaying in a charts + * @param {number} timezoneOffset - user's local timezone offset in minutes + * @returns {Promise} + */ + async usersAffectedChart({ projectId, groupHash }, { days, timezoneOffset }) { + const factory = new EventsFactory(new ObjectID(projectId)); + + return factory.findAffectedUsersChart(days, timezoneOffset, groupHash); + }, + /** * Return chart data for target event occured in last few days * diff --git a/src/typeDefs/event.ts b/src/typeDefs/event.ts index 8baad046..4dfedd7a 100644 --- a/src/typeDefs/event.ts +++ b/src/typeDefs/event.ts @@ -348,6 +348,21 @@ type Event { """ usersAffected: Int + """ + Return affected users chart data for the last few days + """ + usersAffectedChart( + """ + How many days we need to fetch for displaying in a chart + """ + days: Int! = 0 + + """ + User's local timezone offset in minutes + """ + timezoneOffset: Int! = 0 + ): [ChartDataItem!]! @requireAuth + """ Return graph of the error rate for the last few days """ @@ -392,6 +407,12 @@ type DailyEventInfo { Last event occurrence timestamp """ lastRepetitionTime: Float! + + + """ + Array of user's ids affected this day + """ + affectedUsers: [String!] } type Subscription { diff --git a/src/utils/dates.ts b/src/utils/dates.ts index c2ee05cf..cde5ed39 100644 --- a/src/utils/dates.ts +++ b/src/utils/dates.ts @@ -6,7 +6,28 @@ import { ObjectId } from 'mongodb'; * @param {Date} date - date object */ export function getUTCMidnight(date: Date): number { - return date.setUTCHours(0, 0, 0, 0); + const copy = new Date(date); + + return copy.setUTCHours(0, 0, 0, 0); +} + +/** + * Sets UTC midnight for a given date + * + * @param {Date} date — date to set midnight + */ +export function setUTCMidnight(date: Date): void { + date.setHours(0, 0, 0, 0); +} + +/** + * Adds passed offset in minutes to a given date + * + * @param {Date} date — date to change + * @param {number} offset — offset in minutes + */ +export function addTimezoneOffset(date: Date, offset: number): void { + date.setTime(date.getTime() + offset * 60 * 1000); } /**