-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #88 from mlomb/calls-support
Merge calls support
- Loading branch information
Showing
46 changed files
with
1,085 additions
and
199 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { BlockDescription, BlockFn } from "@pipeline/aggregate/Blocks"; | ||
import { WeekdayHourEntry } from "@pipeline/aggregate/Common"; | ||
|
||
import { iterateHoursInCall } from "./CallsUtils"; | ||
|
||
export interface CallsActivity { | ||
/** Each entry contains the total amount of seconds spent in calls for that hour of the week */ | ||
weekdayHourActivity: WeekdayHourEntry[]; | ||
} | ||
|
||
const fn: BlockFn<CallsActivity> = (database, filters, common) => { | ||
const weekdayHourDurations: number[] = new Array(7 * 24).fill(0); | ||
|
||
for (const call of database.calls) { | ||
// Note: time filtered below, we have to filter each hour individually | ||
if (!filters.hasChannel(call.channelIndex)) continue; | ||
if (!filters.hasAuthor(call.authorIndex)) continue; | ||
|
||
iterateHoursInCall(call, (dayIndex, hourInDay, secondsInCall) => { | ||
if (filters.inTime(dayIndex)) { | ||
weekdayHourDurations[common.dayOfWeek[dayIndex] * 24 + hourInDay] += secondsInCall; | ||
} | ||
}); | ||
} | ||
|
||
const weekdayHourActivity: WeekdayHourEntry[] = weekdayHourDurations.map((count, i) => { | ||
const weekday = Math.floor(i / 24); | ||
const hour = i % 24; | ||
return { | ||
value: count, | ||
hour: `${hour}hs`, | ||
weekday: (["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const)[weekday], | ||
}; | ||
}); | ||
|
||
return { | ||
weekdayHourActivity, | ||
}; | ||
}; | ||
|
||
export default { | ||
key: "calls/activty", | ||
triggers: ["time", "authors", "channels"], | ||
fn, | ||
} as BlockDescription<"calls/activty", CallsActivity>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { BlockDescription, BlockFn } from "@pipeline/aggregate/Blocks"; | ||
|
||
import { iterateHoursInCall } from "./CallsUtils"; | ||
|
||
export type CallsInDate = { | ||
ts: number; // timestamp | ||
|
||
n: number; // number of calls | ||
t: number; // total time in calls (seconds) | ||
}; | ||
|
||
/** | ||
* Number of calls per different time cycles. | ||
* It ignores the time filter completely, all cycles are included. | ||
*/ | ||
export interface CallsPerPeriod { | ||
perDay: CallsInDate[]; | ||
perWeek: CallsInDate[]; | ||
perMonth: CallsInDate[]; | ||
} | ||
|
||
const fn: BlockFn<CallsPerPeriod> = (database, filters, common) => { | ||
const res: CallsPerPeriod = { | ||
perDay: [], | ||
perWeek: [], | ||
perMonth: [], | ||
}; | ||
|
||
const { keyToTimestamp } = common; | ||
const { dateToWeekIndex, dateToMonthIndex } = common.timeKeys; | ||
|
||
// fill empty | ||
for (const ts of keyToTimestamp.date) { | ||
res.perDay.push({ | ||
ts, | ||
n: 0, | ||
t: 0, | ||
}); | ||
} | ||
for (const ts of keyToTimestamp.week) { | ||
res.perWeek.push({ | ||
ts, | ||
n: 0, | ||
t: 0, | ||
}); | ||
} | ||
for (const ts of keyToTimestamp.month) { | ||
res.perMonth.push({ | ||
ts, | ||
n: 0, | ||
t: 0, | ||
}); | ||
} | ||
|
||
for (const call of database.calls) { | ||
// check filters | ||
// if (!filters.inTime(call.start.dayIndex)) continue; // don't filter by time, UI scrolls the time natively | ||
if (!filters.hasChannel(call.channelIndex)) continue; | ||
if (!filters.hasAuthor(call.authorIndex)) continue; | ||
|
||
iterateHoursInCall(call, (dayIndex, _, secondsInCall) => { | ||
res.perDay[dayIndex].t += secondsInCall; | ||
res.perWeek[dateToWeekIndex[dayIndex]].t += secondsInCall; | ||
res.perMonth[dateToMonthIndex[dayIndex]].t += secondsInCall; | ||
}); | ||
|
||
res.perDay[call.start.dayIndex].n += 1; | ||
res.perWeek[dateToWeekIndex[call.start.dayIndex]].n += 1; | ||
res.perMonth[dateToMonthIndex[call.start.dayIndex]].n += 1; | ||
} | ||
|
||
return res; | ||
}; | ||
|
||
export default { | ||
key: "calls/per-period", | ||
triggers: ["authors", "channels"], | ||
fn, | ||
} as BlockDescription<"calls/per-period", CallsPerPeriod>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { Datetime, diffDatetime } from "@pipeline/Time"; | ||
import { BlockDescription, BlockFn } from "@pipeline/aggregate/Blocks"; | ||
import { VariableDistribution, computeVariableDistribution } from "@pipeline/aggregate/Common"; | ||
import { filterMessages } from "@pipeline/aggregate/Helpers"; | ||
import { MessageView } from "@pipeline/serialization/MessageView"; | ||
|
||
interface CallDuration { | ||
duration: number; | ||
start: Datetime; | ||
} | ||
|
||
export interface CallsStats { | ||
/** Total number of calls */ | ||
total: number; | ||
/** Total number of seconds spent in calls */ | ||
secondsInCall: number; | ||
|
||
longestCall?: CallDuration; | ||
|
||
/** Call duration distribution in seconds */ | ||
durationDistribution: VariableDistribution; | ||
/** Time between calls distribution in seconds */ | ||
timesBetweenDistribution: VariableDistribution; | ||
|
||
/** Number of calls made by each author */ | ||
authorsCount: number[]; | ||
} | ||
|
||
const fn: BlockFn<CallsStats> = (database, filters, common, args) => { | ||
const { dateKeys } = common.timeKeys; | ||
|
||
let total = 0; | ||
let secondsInCall = 0; | ||
let longestCall: CallDuration | undefined = undefined; | ||
let lastCall: Datetime | undefined = undefined; | ||
|
||
const authorsCount = new Array(database.authors.length).fill(0); | ||
|
||
const durations = new Uint32Array(database.calls.length).fill(0xfffffff0); | ||
const timesBetween = new Uint32Array(database.calls.length).fill(0xfffffff0); | ||
|
||
for (const call of database.calls) { | ||
if (!filters.inTime(call.start.dayIndex)) continue; | ||
if (!filters.hasChannel(call.channelIndex)) continue; | ||
if (!filters.hasAuthor(call.authorIndex)) continue; | ||
|
||
const startDatetime = { | ||
key: dateKeys[call.start.dayIndex], | ||
secondOfDay: call.start.secondOfDay, | ||
}; | ||
const endDatetime = { | ||
key: dateKeys[call.end.dayIndex], | ||
secondOfDay: call.end.secondOfDay, | ||
}; | ||
|
||
durations[total] = call.duration; | ||
authorsCount[call.authorIndex]++; | ||
|
||
if (longestCall === undefined || call.duration > longestCall.duration) { | ||
longestCall = { | ||
duration: call.duration, | ||
start: startDatetime, | ||
}; | ||
} | ||
|
||
if (lastCall !== undefined) { | ||
// compute time difference between calls | ||
const diff = diffDatetime(lastCall, startDatetime); | ||
if (diff < 0) throw new Error("Time difference between calls is negative, diff=" + diff); | ||
timesBetween[total - 1] = diff; | ||
} | ||
lastCall = endDatetime; | ||
|
||
secondsInCall += call.duration; | ||
total++; | ||
} | ||
|
||
return { | ||
total, | ||
secondsInCall, | ||
averageDuration: secondsInCall / total, | ||
longestCall, | ||
|
||
durationDistribution: computeVariableDistribution(durations, total), | ||
timesBetweenDistribution: computeVariableDistribution(timesBetween, total - 1), | ||
|
||
authorsCount, | ||
}; | ||
}; | ||
|
||
export default { | ||
key: "calls/stats", | ||
triggers: ["authors", "channels", "time"], | ||
fn, | ||
} as BlockDescription<"calls/stats", CallsStats>; |
Oops, something went wrong.