Quickstart: Monthly stats
The below quickstart sample creates a script that calculates the stats of your tasks completed or added in the last month.
When you run the script, it will add
Set it up
- Open the web app or desktop app.
- Click the MilkScript button at the top right, then click New...
- Copy the code below and paste it into the script editor.
const now = new Date();
const thisMonth = new Date(now.getFullYear(), now.getMonth());
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1);
const penultimateMonth = new Date(now.getFullYear(), now.getMonth() - 2);
const completedLastMonth = searchTasks('completed', lastMonth, thisMonth);
const completedPenultimateMonth =
searchTasks('completed', penultimateMonth, lastMonth);
const addedLastMonth = searchTasks('added', lastMonth, thisMonth);
const addedPenultimateMonth =
searchTasks('added', penultimateMonth, lastMonth);
const taskName =
`Monthly Stats: ${formatMonth(lastMonth)} ${lastMonth.getFullYear()}`;
const addedStats = formatStats(
'Tasks added', addedLastMonth, addedPenultimateMonth);
const completedStats = formatStats(
'Tasks completed', completedLastMonth, completedPenultimateMonth);
rtm.addTask(taskName)
.addNote(addedStats)
.addNote(completedStats);
/**
* Formats the given date as a month:
*
* 2022-01-25T06:46:08.623Z ===> January
*
* 2021-12-01T08:00:00.123Z ===> December
*
* @return {string}
* @param {!Date} date
*/
function formatMonth(date) {
return date.toLocaleString('default', { month: 'long' });
}
/**
* Formats the percentage difference between the given values:
*
* newValue = 10, oldValue = 5 ===> (↑100.00%)
*
* newValue = 7, oldValue = 33 ===> (↓78.79%)
*
* @return {string}
* @param {number} newValue
* @param {number} oldValue
*/
function formatRate(newValue, oldValue) {
if (newValue === oldValue || newValue === 0 || oldValue === 0) {
return '';
}
const rate = -(100 - newValue / oldValue * 100);
return ` (${rate < 0 ? '↓' : '↑'}${Math.abs(rate).toFixed(2)}%)`;
}
/**
* Formats the estimate section of the stats:
*
* name = "Total estimate", estimate = rtm.newEstimate(2, 35) ===>
*
* TOTAL ESTIMATE
* 2 hours 35 minutes
*
* @return {!Array<string>}
* @param {!rtm.Estimate} estimate
*/
function formatEstimate(name, estimate) {
if (estimate.getMinutes() === 0) {
return [];
}
const hours = Math.trunc(estimate.getMinutes() / 60);
const minutes = estimate.getMinutes() % 60;
return [
'',
name.toUpperCase(),
[
hours === 1 ? '1 hour' : `${hours} hours`,
minutes === 1 ? '1 minute' : `${minutes} minutes`
].join(' ')
];
}
/**
* Groups the given items and formats them as a list:
*
* name = "By list", items = ["Inbox", "Inbox", "Work", "Inbox"] ===>
*
* BY LIST
* • Inbox: 3
* • Work: 1
*
* @return {!Array<string>}
* @param {string} name
* @param {!Array<string>} items
*/
function formatCounts(name, items) {
if (items.length === 0) {
return '';
}
const counts = items.reduce((result, item) =>
result.set(item, (result.get(item) ?? 0) + 1), new Map());
return [
'',
name.toUpperCase(),
...Array.from(counts)
.sort(([item1, count1], [item2, count2]) =>
count2 - count1 || item2.localeCompare(item1))
.map(([item, count]) => ` • ${item}: ${count}`)
];
}
/**
* Formats the total section of the stats:
*
* name = "Tasks completed", count = 12, previousCount = 50 ===>
*
* TASKS COMPLETED
* 12 (↓76.00%)
*
* @return {!Array<string>}
* @param {string} name
* @param {number} count
* @param {number} previousCount
*/
function formatTotal(name, count, previousCount) {
return [
name.toUpperCase(),
`${count}${formatRate(count, previousCount)}`
];
}
/**
* @return {string}
* @param {string} statsName
* @param {!Array<!rtm.Task>} tasks — Last month's tasks.
* @param {!Array<!rtm.Task>} previousTasks — Penultimate month's tasks.
*/
function formatStats(statsName, tasks, previousTasks) {
const zeroEstimate = rtm.newEstimate(0);
const totalEstimate = tasks.reduce((result, task) =>
result.plus(task.getEstimate() ?? zeroEstimate), zeroEstimate);
const lists = tasks.map(task => task.getList().getName());
const tags = tasks
.flatMap(task => task.getTags())
.map(tag => tag.getName());
const locations = tasks.map(task =>
task.getLocation()?.getName() ?? 'No Location');
return [
...formatTotal(statsName, tasks.length, previousTasks.length),
...formatEstimate('Total estimate', totalEstimate),
...formatCounts('By list', lists),
...formatCounts('By tag', tags),
...formatCounts('By location', locations)
].join('\n');
}
/**
* Searches tasks within the given date range.
*
* @param {string} queryPrefix — Either 'completed' or 'added'.
* @param {!Date} begin
* @param {!Date} end
*/
function searchTasks(queryPrefix, begin, end) {
const formatDate =
date => `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
const query = [
`${queryPrefix}After:${formatDate(begin)}`,
`${queryPrefix}Before:${formatDate(end)}`
].join(' AND ');
return rtm.getTasks(query);
}
- At the top left, click Untitled script.
- Enter a name for your script (e.g. Create Monthly Stats), then close the script editor.
Try it out
- Click the MilkScript button at the top right.
- Click the recently created script, then click Yes, run script.
- When the script execution completes, check your default list for a new
Monthly Stats: November 2024 task with the stats in the notes.