// This function runs the sprint burndown history logger for $bible. // If no $bible is passed, it will do all Bibles. // If $mail is true, it will mail the burndown chart to the subscribed users. // If $mail is false, it decides on its own whether to mail the chart to the users. void sprint_burndown (string bible, bool email) { int localseconds = filter_date_local_seconds (filter_date_seconds_since_epoch ()); int year = filter_date_numerical_year (localseconds); int month = filter_date_numerical_month (localseconds); int monthday = filter_date_numerical_month_day (localseconds); // 1 to 31. int weekday = filter_date_numerical_week_day (localseconds); // 0 (for Sunday) through 6 (for Saturday). int hour = filter_date_numerical_hour (localseconds); bool sprintstart = false; bool sprintfinish = false; // Every Friday at 2 PM (14:00h) it sends email about the sprint progress. if ((weekday == 5) && (hour == 14)) email = true; // On the first business day of the month, at 10 AM, send email about the start of the sprint. if (filter_date_is_first_business_day_of_month (monthday, weekday) && (hour == 10)) { email = true; sprintstart = true; } // On the last business day of the month, at 2 PM (14:00h), send email about the end of the sprint. if ((monthday == filter_date_get_last_business_day_of_month (year, month)) && (hour == 14)) { email = true; sprintfinish = true; } // Determine what to do, or to quit. if (!email && !sprintstart && !sprintfinish) { if (hour != 1) return; } Database_Logs::log ("Updating Sprint information", Filter_Roles::manager ()); Webserver_Request request; Database_Mail database_mail = Database_Mail (&request); Database_Sprint database_sprint = Database_Sprint (); // Determine year / month / day of the current sprint. // If this script runs from midnight till early morning, // it applies to the day before. // If the script runs during the day, it applies to today. if (hour <= 6) { localseconds -= (3600 * 6); } year = filter_date_numerical_year (localseconds); month = filter_date_numerical_month (localseconds); monthday = filter_date_numerical_month_day (localseconds); // 1 to 31. vector <string> bibles = {bible}; if (bible == "") { bibles = request.database_bibles()->getBibles (); } for (auto bible : bibles) { // Get the total number of tasks for this sprint, // and the average percentage of completion of them, // and store this information in the sprint history table. vector <int> ids = database_sprint.getTasks (bible, year, month); vector <int> percentages; for (auto id : ids) { percentages.push_back (database_sprint.getComplete (id)); } int tasks = ids.size (); int complete = 0; if (tasks != 0) { for (auto percentage : percentages) complete += percentage; complete = round ((float) complete / (float) tasks); } database_sprint.logHistory (bible, year, month, monthday, tasks, complete); // Send email if requested. if (email) { if (tasks) { // Only mail if the current sprint contains tasks. string scategories = Database_Config_Bible::getSprintTaskCompletionCategories (bible); vector <string> categories = filter_string_explode (scategories, '\n'); int category_count = categories.size(); int category_percentage = round (100 / category_count); vector <string> users = request.database_users ()->getUsers (); for (auto user : users) { if (request.database_config_user()->getUserSprintProgressNotification (user)) { string subject = translate("Team's progress in Sprint"); if (sprintstart) subject = translate("Sprint has started"); if (sprintfinish) subject = translate("Sprint has finished"); subject += " | " + bible; vector <string> body; body.push_back ("<h3>" + translate("Sprint Planning and Team's Progress") + " | " + bible + "</h3>"); body.push_back ("<table class='burndown'>"); vector <int> tasks = database_sprint.getTasks (bible, year, month); for (auto id : tasks) { body.push_back ("<tr>"); string title = database_sprint.getTitle (id); body.push_back ("<td>" + title + "</td>"); int complete = database_sprint.getComplete (id); string text; for (int i = 0; i < round (complete / category_percentage); i++) text.append ("▓"); for (int i = 0; i < category_count - round (complete / category_percentage); i++) text.append ("▁"); body.push_back ("<td>" + text + "</td>"); body.push_back ("</tr>"); } body.push_back ("</table>"); body.push_back ("<h3>" + translate("Sprint Burndown Chart - Remaining Tasks") + "</h3>"); string burndownchart = sprint_create_burndown_chart (bible, year, month); body.push_back ("<p>" + burndownchart + "</p>"); if (!body.empty ()) { string mailbody = filter_string_implode (body, "\n"); if (!client_logic_client_enabled ()) database_mail.send (user, subject, mailbody); } } } } else { // Since there are no tasks, no mail will be sent: Make a logbook entry. Database_Logs::log ("No tasks in this Sprint: No email was sent"); } } } }
string sprint_index (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; Database_Sprint database_sprint; string page; Assets_Header header = Assets_Header (translate("Sprint"), request); header.addBreadCrumb (menu_logic_tools_menu (), menu_logic_tools_text ()); page = header.run (); Assets_View view; if (request->query.count ("previoussprint")) { int month = request->database_config_user()->getSprintMonth (); int year = request->database_config_user()->getSprintYear (); filter_date_get_previous_month (month, year); request->database_config_user()->setSprintMonth (month); request->database_config_user()->setSprintYear (year); } if (request->query.count ("currentprint")) { request->database_config_user()->setSprintMonth (filter_date_numerical_month (filter_date_seconds_since_epoch ())); request->database_config_user()->setSprintYear (filter_date_numerical_year (filter_date_seconds_since_epoch ())); } if (request->query.count ("nextsprint")) { int month = request->database_config_user()->getSprintMonth (); int year = request->database_config_user()->getSprintYear (); filter_date_get_next_month (month, year); request->database_config_user()->setSprintMonth (month); request->database_config_user()->setSprintYear (year); } string bible = access_bible_clamp (webserver_request, request->database_config_user()->getBible ()); int month = request->database_config_user()->getSprintMonth (); int year = request->database_config_user()->getSprintYear (); if (request->post.count ("id")) { string id = request->post ["id"]; string checked = request->post ["checked"]; if (id.length () >= 9) { // Remove "task". id.erase (0, 4); // Convert the fragment to an integer. int identifier = convert_to_int (id); // Find the fragment "box". size_t pos = id.find ("box"); if (pos != string::npos) { // Remove the fragment "box". id.erase (0, pos + 3); // Convert the box to an integer. int box = convert_to_int (id); string categorytext = Database_Config_Bible::getSprintTaskCompletionCategories (bible); vector <string> categories = filter_string_explode (categorytext, '\n'); int category_count = categories.size (); float category_percentage = 100 / category_count; int percentage; bool on = (checked == "true"); if (on) percentage = round ((box + 1) * category_percentage); else percentage = round (box * category_percentage); database_sprint.updateComplete (identifier, percentage); } } return ""; } if (request->post.count ("add")) { string title = request->post ["add"]; database_sprint.storeTask (bible, year, month, title); view.set_variable ("success", translate("New task added")); // Focus the entry for adding tasks only in case a new task was added. view.set_variable ("autofocus", "autofocus"); } if (request->query.count ("mail")) { string mail = request->query ["mail"]; sprint_burndown (bible, true); view.set_variable ("success", translate("The information was mailed to the subscribers")); } if (request->query.count ("bible")) { bible = request->query ["bible"]; if (bible == "") { Dialog_List dialog_list = Dialog_List ("index", translate("Select which Bible to display the Sprint for"), "", ""); vector <string> bibles = access_bible_bibles (request); for (auto & bible : bibles) { dialog_list.add_row (bible, "bible", bible); } page += dialog_list.run(); return page; } else { request->database_config_user()->setBible (bible); } } bible = access_bible_clamp (webserver_request, request->database_config_user()->getBible ()); int id = convert_to_int (request->query ["id"]); if (request->query.count ("remove")) { database_sprint.deleteTask (id); view.set_variable ("success", translate("The task was removed")); } if (request->query.count ("moveback")) { filter_date_get_previous_month (month, year); database_sprint.updateMonthYear (id, month, year); view.set_variable ("success", translate("The task was moved to the previous sprint")); request->database_config_user()->setSprintMonth (month); request->database_config_user()->setSprintYear (year); } if (request->query.count ("moveforward")) { filter_date_get_next_month (month, year); database_sprint.updateMonthYear (id, month, year); view.set_variable ("success", translate("The task was moved to the next sprint")); request->database_config_user()->setSprintMonth (month); request->database_config_user()->setSprintYear (year); } if (request->post.count ("categories")) { string categories = request->post ["categories"]; vector <string> categories2; categories = filter_string_trim (categories); vector <string> vcategories = filter_string_explode (categories, '\n'); for (auto category : vcategories) { category = filter_string_trim (category); if (category != "") categories2.push_back (category); } categories = filter_string_implode (categories2, "\n"); Database_Config_Bible::setSprintTaskCompletionCategories (bible, categories); } view.set_variable ("bible", bible); view.set_variable ("sprint", locale_logic_month (month) + " " + convert_to_string (year)); string categorytext = Database_Config_Bible::getSprintTaskCompletionCategories (bible); view.set_variable ("categorytext", categorytext); vector <string> vcategories = filter_string_explode (categorytext, '\n'); string categories; for (auto category : vcategories) { categories.append ("<td>" + category + "</td>\n"); } view.set_variable ("categories", categories); string tasks; vector <int> vtasks = database_sprint.getTasks (bible, year, month); for (auto & id : vtasks) { string title = filter_string_sanitize_html (database_sprint.getTitle (id)); int percentage = database_sprint.getComplete (id); tasks.append ("<tr id=\"a" + convert_to_string (id) + "\">\n"); tasks.append ("<td><a href=\"?id=" + convert_to_string (id) + "&remove=\">" + emoji_wastebasket () + "</a></td>\n"); tasks.append ("<td></td>\n"); tasks.append ("<td><a href=\"?id=" + convert_to_string (id) + "&moveback=\"> « </a></td>\n"); tasks.append ("<td>" + title + "</td>\n"); int category_count = vcategories.size(); float category_percentage = 100 / category_count; for (size_t i2 = 0; i2 < vcategories.size (); i2++) { int high = round ((i2 + 1) * category_percentage); tasks.append ("<td>\n"); tasks.append ("<input type=\"checkbox\" id=\"task"); tasks.append (convert_to_string (id)); tasks.append ("box"); tasks.append (convert_to_string (i2)); tasks.append ("\""); if (percentage >= high) tasks.append (" checked"); else tasks.append (""); tasks.append (">"); tasks.append ("</td>\n"); } tasks.append ("<td><a href=\"?id=" + convert_to_string (id) + "&moveforward=\"> » </a></td>\n"); tasks.append ("</tr>\n"); } view.set_variable ("tasks", tasks); view.set_variable ("chart", sprint_create_burndown_chart (bible, year, month)); page += view.render ("sprint", "index"); page += Assets_Page::footer (); return page; }