CmdTimesheet: Rewrote the command

- Updated the 'timesheet' command with a more compact report that accepts a
  filter, and has a default filter showing the last four weeks of completed and
  started tasks.
This commit is contained in:
Paul Beckingham 2017-02-09 08:19:30 -05:00
parent 4c20ff04c2
commit 440cfb009e
10 changed files with 182 additions and 190 deletions

View file

@ -251,6 +251,7 @@ std::string Config::_defaults =
"#default.due=eom # Default due date for 'add' command\n"
"#default.scheduled=eom # Default scheduled date for 'add' command\n"
"default.command=next # When no arguments are specified\n"
"default.timesheet.filter=( +PENDING and start.after:now-4wks ) or ( +COMPLETED and end.after:now-4wks )\n"
"\n"
"_forcecolor=0 # Forces color to be on, even for non TTY output\n"
"complete.all.tags=0 # Include old tag names in '_ags' command\n"
@ -383,7 +384,9 @@ std::string Config::_defaults =
"report.blocking.labels=ID,UUID,A,Deps,Project,Tags,R,W,Sch,Due,Until,Description,Urg\n"
"report.blocking.columns=id,uuid.short,start.active,depends,project,tags,recur,wait,scheduled.remaining,due.relative,until.remaining,description.count,urgency\n"
"report.blocking.sort=urgency-,due+,entry+\n"
"report.blocking.filter= status:pending +BLOCKING\n"
"report.blocking.filter=status:pending +BLOCKING\n"
"\n"
"report.timesheet.filter=(+PENDING and start.after:now-4wks) or (+COMPLETED and end.after:now-4wks)\n"
"\n";
extern Context context;

View file

@ -103,7 +103,7 @@ int CmdInfo::execute (std::string& output)
view.width (context.getWidth ());
if (context.config.getBoolean ("obfuscate"))
view.obfuscate ();
if (context.config.getBoolean ("color"))
if (context.color ())
view.forceColor ();
view.add (STRING_COLUMN_LABEL_NAME);
view.add (STRING_COLUMN_LABEL_VALUE);
@ -113,11 +113,11 @@ int CmdInfo::execute (std::string& output)
{
Color alternate (context.config.get ("color.alternate"));
view.colorOdd (alternate);
view.intraColorOdd (alternate);
Color label (context.config.get ("color.label"));
view.colorHeader (label);
view.colorHeader (Color ("underline " + context.config.get ("color.label")));
}
else
view.underlineHeaders ();
Datetime now;
@ -444,8 +444,7 @@ int CmdInfo::execute (std::string& output)
urgencyDetails.colorOdd (alternate);
urgencyDetails.intraColorOdd (alternate);
Color label (context.config.get ("color.label"));
urgencyDetails.colorHeader (label);
urgencyDetails.colorHeader (Color ("underline " + context.config.get ("color.label")));
}
if (context.config.getBoolean ("obfuscate"))
@ -548,9 +547,10 @@ int CmdInfo::execute (std::string& output)
journal.colorOdd (alternate);
journal.intraColorOdd (alternate);
Color label (context.config.get ("color.label"));
journal.colorHeader (label);
journal.colorHeader (Color ("underline " + context.config.get ("color.label")));
}
else
journal.underlineHeaders ();
if (context.config.getBoolean ("obfuscate"))
journal.obfuscate ();

View file

@ -147,6 +147,7 @@ int CmdShow::execute (std::string& output)
" default.scheduled"
" defaultheight"
" defaultwidth"
" default.timesheet.filter"
" dependency.confirmation"
" dependency.indicator"
" dependency.reminder"

View file

@ -26,6 +26,7 @@
#include <cmake.h>
#include <CmdTimesheet.h>
#include <algorithm>
#include <sstream>
#include <stdlib.h>
#include <Context.h>
@ -33,6 +34,7 @@
#include <Table.h>
#include <Datetime.h>
#include <main.h>
#include <util.h>
#include <i18n.h>
#include <format.h>
@ -42,16 +44,16 @@ extern Context context;
CmdTimesheet::CmdTimesheet ()
{
_keyword = "timesheet";
_usage = "task timesheet [weeks]";
_usage = "task [filter] timesheet";
_description = STRING_CMD_TIMESHEET_USAGE;
_read_only = true;
_displays_id = false;
_needs_gc = true;
_uses_context = false;
_accepts_filter = false;
_accepts_filter = true;
_accepts_modifications = false;
_accepts_miscellaneous = true;
_category = Command::Category::graphs;
_accepts_miscellaneous = false;
_category = Command::Category::report;
}
////////////////////////////////////////////////////////////////////////////////
@ -59,165 +61,145 @@ int CmdTimesheet::execute (std::string& output)
{
int rc = 0;
// Scan the pending tasks.
handleRecurrence ();
std::vector <Task> all = context.tdb2.all_tasks ();
// What day of the week does the user consider the first?
int weekStart = Datetime::dayOfWeek (context.config.get ("weekstart"));
if (weekStart != 0 && weekStart != 1)
throw std::string (STRING_DATE_BAD_WEEKSTART);
// Determine the date of the first day of the most recent report.
Datetime today;
Datetime start;
start -= (((today.dayOfWeek () - weekStart) + 7) % 7) * 86400;
// Roll back to midnight.
start = Datetime (start.year (), start.month (), start.day ());
Datetime end = start + (7 * 86400);
// Determine how many reports to run.
int quantity = 1;
std::vector <std::string> words = context.cli2.getWords ();
if (words.size () == 1)
quantity = strtol (words[0].c_str (), NULL, 10);;
std::stringstream out;
for (int week = 0; week < quantity; ++week)
// Detect a filter.
bool hasFilter {false};
for (auto& a : context.cli2._args)
{
Datetime endString (end);
endString -= 86400;
if (a.hasTag ("FILTER"))
{
hasFilter = true;
break;
}
}
std::string title = start.toString (context.config.get ("dateformat"))
+ " - "
+ endString.toString (context.config.get ("dateformat"));
if (! hasFilter)
{
auto defaultFilter = context.config.get ("report.timesheet.filter");
if (defaultFilter == "")
defaultFilter = "(+PENDING and start.after:now-4wks) or (+COMPLETED and end.after:now-4wks)";
context.cli2.addFilter (defaultFilter);
}
Color bold;
if (context.color ())
bold = Color ("bold");
// Apply filter to get a set of tasks.
handleRecurrence ();
Filter filter;
std::vector <Task> filtered;
filter.subset (filtered);
out << '\n'
<< bold.colorize (title)
// Subset the tasks to only those that are either completed or started.
// The _key attribute is represents either the 'start' or 'end' date.
int num_completed = 0;
int num_started = 0;
std::vector <Task> shown;
for (auto& task : filtered)
{
if (task.getStatus () == Task::completed)
{
task.set ("_key", task.get ("end"));
++num_completed;
}
if (task.getStatus () == Task::pending && task.has ("start"))
{
task.set ("_key", task.get ("start"));
++num_started;
}
shown.push_back (task);
}
// Sort tasks by _key.
std::sort (shown.begin (),
shown.end (),
[](const Task& a, const Task& b) { return a.get ("_key") < b.get ("_key"); });
// Render the completed table.
Table table;
table.width (context.getWidth ());
if (context.config.getBoolean ("obfuscate"))
table.obfuscate ();
table.add ("Wk");
table.add ("Date");
table.add ("Day");
table.add ("Action");
table.add ("Project");
table.add ("Due");
table.add ("Task");
if (context.color ())
{
table.forceColor ();
table.colorHeader (Color ("underline " + context.config.get ("color.label")));
table.colorOdd (Color (context.config.get ("color.alternate")));
}
else
table.underlineHeaders ();
auto dateformat = context.config.get ("dateformat");
int previous_week = -1;
std::string previous_date = "";
std::string previous_day = "";
int weekCounter = 0;
Color week_color;
for (auto& task : shown)
{
Datetime key (task.get_date ("_key"));
std::string label = task.has ("end") ? "Completed"
: task.has ("start") ? "Started"
: "";
auto week = key.week ();
auto date = key.toString (dateformat);
auto due = task.has ("due") ? Datetime (task.get ("due")).toString (dateformat) : "";
auto day = Datetime::dayNameShort (key.dayOfWeek ());
Color task_color;
autoColorize (task, task_color);
// Add a blank line between weeks.
if (week != previous_week && previous_week != -1)
{
auto row = table.addRowEven ();
table.set (row, 0, " ");
}
// Keep track of unique week numbers.
if (week != previous_week)
++weekCounter;
// User-defined oddness.
int row;
if (weekCounter % 2)
row = table.addRowOdd ();
else
row = table.addRowEven ();
// If the data doesn't change, it doesn't get shown.
table.set (row, 0, (week != previous_week ? format ("W{1}", week) : ""));
table.set (row, 1, (date != previous_date ? date : ""));
table.set (row, 2, (day != previous_day ? day : ""));
table.set (row, 3, label);
table.set (row, 4, task.get ("project"));
table.set (row, 5, due);
table.set (row, 6, task.get ("description"), task_color);
previous_week = week;
previous_date = date;
previous_day = day;
}
// Render the table.
std::stringstream out;
if (table.rows ())
out << optionalBlankLine ()
<< table.render ()
<< '\n';
// Render the completed table.
Table completed;
completed.width (context.getWidth ());
completed.add (" ");
completed.add (STRING_COLUMN_LABEL_PROJECT);
completed.add (STRING_COLUMN_LABEL_DUE, false);
completed.add (STRING_COLUMN_LABEL_DESC);
Color label;
if (context.color ())
{
label = Color (context.config.get ("color.label"));
completed.colorHeader (label);
}
for (auto& task : all)
{
// If task completed within range.
if (task.getStatus () == Task::completed)
{
Datetime compDate (task.get_date ("end"));
if (compDate >= start && compDate < end)
{
Color c;
autoColorize (task, c);
int row = completed.addRow ();
std::string format = context.config.get ("dateformat.report");
if (format == "")
format = context.config.get ("dateformat");
completed.set (row, 1, task.get ("project"), c);
if(task.has ("due"))
{
Datetime dt (task.get_date ("due"));
completed.set (row, 2, dt.toString (format));
}
std::string description = task.get ("description");
int indent = context.config.getInteger ("indent.annotation");
for (auto& ann : task.getAnnotations ())
description += '\n'
+ std::string (indent, ' ')
+ Datetime (ann.first.substr (11)).toString (context.config.get ("dateformat"))
+ ' '
+ ann.second;
completed.set (row, 3, description, c);
}
}
}
out << " " << format (STRING_CMD_TIMESHEET_DONE, completed.rows ()) << '\n';
if (completed.rows ())
out << completed.render ()
<< '\n';
// Now render the started table.
Table started;
started.width (context.getWidth ());
started.add (" ");
started.add (STRING_COLUMN_LABEL_PROJECT);
started.add (STRING_COLUMN_LABEL_DUE, false);
started.add (STRING_COLUMN_LABEL_DESC);
started.colorHeader (label);
for (auto& task : all)
{
// If task started within range, but not completed withing range.
if (task.getStatus () == Task::pending &&
task.has ("start"))
{
Datetime startDate (task.get_date ("start"));
if (startDate >= start && startDate < end)
{
Color c;
autoColorize (task, c);
int row = started.addRow ();
std::string format = context.config.get ("dateformat.report");
if (format == "")
format = context.config.get ("dateformat");
started.set (row, 1, task.get ("project"), c);
if (task.has ("due"))
{
Datetime dt (task.get_date ("due"));
started.set (row, 2, dt.toString (format));
}
std::string description = task.get ("description");
int indent = context.config.getInteger ("indent.annotation");
for (auto& ann : task.getAnnotations ())
description += '\n'
+ std::string (indent, ' ')
+ Datetime (ann.first.substr (11)).toString (context.config.get ("dateformat"))
+ ' '
+ ann.second;
started.set (row, 3, description, c);
}
}
}
out << " " << format (STRING_CMD_TIMESHEET_STARTED, started.rows ()) << '\n';
if (started.rows ())
out << started.render ()
<< "\n\n";
// Prior week.
start -= 7 * 86400;
end -= 7 * 86400;
}
if (context.verbose ("affected"))
out << format ("{1} completed, {2} started.", num_completed, num_started)
<< '\n';
output = out.str ();
return rc;

View file

@ -537,7 +537,7 @@
#define STRING_CMD_CUSTOM_COUNT "1 task"
#define STRING_CMD_CUSTOM_COUNTN "{1} tasks"
#define STRING_CMD_CUSTOM_TRUNCATED "truncated to {1} lines"
#define STRING_CMD_TIMESHEET_USAGE "Weekly summary of completed and started tasks"
#define STRING_CMD_TIMESHEET_USAGE "Summary of completed and started tasks"
#define STRING_CMD_TIMESHEET_STARTED "Started ({1} tasks)"
#define STRING_CMD_TIMESHEET_DONE "Completed ({1} tasks)"
#define STRING_CMD_BURN_USAGE_M "Shows a graphical burndown chart, by month"

View file

@ -539,7 +539,7 @@
#define STRING_CMD_CUSTOM_COUNT "1 task"
#define STRING_CMD_CUSTOM_COUNTN "{1} tasks"
#define STRING_CMD_CUSTOM_TRUNCATED "truncated to {1} lines"
#define STRING_CMD_TIMESHEET_USAGE "Weekly summary of completed and started tasks"
#define STRING_CMD_TIMESHEET_USAGE "Summary of completed and started tasks"
#define STRING_CMD_TIMESHEET_STARTED "Started ({1} tasks)"
#define STRING_CMD_TIMESHEET_DONE "Completed ({1} tasks)"
#define STRING_CMD_BURN_USAGE_M "Shows a graphical burndown chart, by month"