Merge branch 'issue9-outagebar'

This commit is contained in:
Daniel Thee Roperto
2016-09-07 10:25:17 +10:00
13 changed files with 271 additions and 13 deletions

View File

@@ -16,6 +16,8 @@ This is a Moodle plugin which makes the student experience of planned outages ni
The main idea is that instead of an outage being a very booleon on/off situation, this plugin creates the concept of graduated outages where at predefined times before an outage and after, different levels of warning and access can be provided to students and testers letting them know what is about to happen and why.
![Screenshot as of 2016-09-06](docs/2016-09-06_screenshot.png?raw=true)
Why it is an auth plugin?
-------------------------

View File

@@ -49,4 +49,11 @@ class auth_plugin_outage extends auth_plugin_base
public function user_login($username, $password) {
return false;
}
/**
* Login page hook.
*/
public function loginpage_hook() {
\auth_outage\outagelib::inject();
}
}

View File

@@ -27,8 +27,7 @@ namespace auth_outage\models;
use auth_outage\outagelib;
class outage
{
class outage {
/**
* @var int Outage ID (auto generated by the DB).
*/
@@ -85,9 +84,30 @@ class outage
if (is_object($data) || is_array($data)) {
outagelib::data2object($data, $this);
// FIXME types are wrong. Is this behaving as expected?
$fields = ['createdby', 'id', 'lastmodified', 'modifiedby', 'starttime', 'stoptime', 'warningduration'];
foreach ($fields as $f) {
$this->$f = ($this->$f === null) ? null : (int)$this->$f;
}
return;
}
throw new \InvalidArgumentException('$data must be null (default), an array or an object.');
}
public function is_ongoing($time = null) {
if ($time === null) {
$time = time();
}
if (!is_int($time) || ($time <= 0)) {
throw new \InvalidArgumentException('$time must be an positive int.');
}
if (is_null($this->starttime) || is_null($this->stoptime)) {
return false;
}
return (($this->starttime <= $time) && ($time < $this->stoptime));
}
}

View File

@@ -135,4 +135,39 @@ final class outagedb {
$DB->delete_records('auth_outage', ['id' => $id]);
}
}
/**
* Gets the most important active outage, considering importance as:
* - Ongoing outages more important than outages in warning period.
* - Outages that start earlier are more important.
* - Outages that stop later are more important.
* @param int|null $time Timestamp considered to check for outages, null for current date/time.
* @return outage|null The outage or null if no active outages were found.
*/
public static function getactive($time = null) {
global $DB;
if ($time === null) {
$time = time();
}
if (!is_int($time)) {
throw new \InvalidArgumentException('$time must be null or an int.');
}
// TODO Is there a way to use Moodle API instead of writing SQL (conditions not equals)?
// TODO Query not fully using indexes (starttime + 90)
// Gets any active outage (already started or during warning period).
// Gets only one record if available, the one that starts(ed) first and that stops last.
$data = $DB->get_record_sql('
SELECT *
FROM {auth_outage}
WHERE (starttime - warningduration <= :datetime1 AND stoptime >= :datetime2)
ORDER BY starttime ASC, stoptime DESC, title ASC
LIMIT 1
',
['datetime1' => $time, 'datetime2' => $time]
);
return ($data === false) ? null : new \auth_outage\models\outage($data);
}
}

View File

@@ -28,8 +28,9 @@ if (!defined('MOODLE_INTERNAL')) {
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class outagelib
{
class outagelib {
private static $initialized = false;
/**
* Initializes admin pages for outage.
*
@@ -39,9 +40,41 @@ class outagelib
global $PAGE;
admin_externalpage_setup('auth_outage_manage');
$PAGE->set_url(new \moodle_url('/auth/outage/list.php'));
return self::get_renderer();
}
/**
* Returns the outage renderer.
* @return \renderer_base
*/
public static function get_renderer() {
global $PAGE;
return $PAGE->get_renderer('auth_outage');
}
/**
* Will check for ongoing or warning outages and will attach the message bar as required.
*/
public static function inject() {
global $CFG;
global $PAGE;
// Many hooks can call it, execute only once.
if (self::$initialized) {
return;
}
self::$initialized = true;
if (($active = outagedb::getactive()) == null) {
return;
}
// FIXME Code below is raising error at http://moodle.test/my/ for example.
// $PAGE->add_body_class('auth_outage_active');
$CFG->additionalhtmltopofbody = self::get_renderer()->renderbar($active)
. $CFG->additionalhtmltopofbody;
}
/**
* Loads data from an object or array into another object. It ensures no new fields are created in the $obj.
*

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

View File

@@ -32,6 +32,8 @@ $string['defaultwarningtimedescription'] = 'Default warning time (in minutes) fo
$string['description'] = 'Public description';
$string['menudefaults'] = 'Default Settings';
$string['menumanage'] = 'Manage';
$string['messageoutageongoing'] = 'Our system will be under maintenance until {$a->stop}.';
$string['messageoutagewarning'] = 'There is an scheduled downtime from {$a->start} until {$a->stop}.';
$string['modify'] = 'Modify';
$string['modifyoutage'] = 'Modify Outage';
$string['outagecreate'] = 'Create Outage';

10
lib.php
View File

@@ -23,3 +23,13 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
// FIXME hook not installing in courses/index.php page.
function auth_outage_extend_navigation_user() {
\auth_outage\outagelib::inject();
}
function auth_outage_extend_navigation_frontpage() {
\auth_outage\outagelib::inject();
}

View File

@@ -29,8 +29,7 @@ if (!defined('MOODLE_INTERNAL')) {
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class auth_outage_renderer extends plugin_renderer_base
{
class auth_outage_renderer extends plugin_renderer_base {
public function rendersubtitle($subtitlekey) {
if (!is_string($subtitlekey)) {
throw new \InvalidArgumentException('$subtitle is not a string.');
@@ -125,4 +124,31 @@ class auth_outage_renderer extends plugin_renderer_base
)
);
}
public function renderbar($outage) {
global $PAGE;
$PAGE->requires->css(new moodle_url('/auth/outage/res/outage.css'));
$start = userdate($outage->starttime, get_string('strftimedatetimeshort'));
$stop = userdate($outage->stoptime, get_string('strftimedatetimeshort'));
$message = get_string(
$outage->is_ongoing() ? 'messageoutageongoing' : 'messageoutagewarning',
'auth_outage',
['start' => $start, 'stop' => $stop]
);
return
html_writer::div(
html_writer::div(
html_writer::div($outage->title, 'auth_outage_warningbar_box_title')
. html_writer::div($message, 'auth_outage_warningbar_box_message'),
'auth_outage_warningbar_box'
),
'auth_outage_warningbar'
)
.
html_writer::div('&nbsp;', 'auth_outage_warningbar_spacer');
}
}

35
res/outage.css Normal file
View File

@@ -0,0 +1,35 @@
.auth_outage_warningbar {
position: fixed;
top: 0px;
left: 0px;
height: 90px;
z-index: 9999;
width: 100%;
text-align: center;
background-color: white;
}
.auth_outage_warningbar_box {
border-top: 2px dashed #a00000;
border-bottom: 2px dashed #a00000;
background-color: #ffcccc;
color: #a00000;
}
.auth_outage_warningbar_box_title {
font-size: 200%;
font-weight: bold;
margin: 10px 0;
}
.auth_outage_warningbar_box_message {
margin-bottom: 5px;
}
.navbar.navbar-fixed-top {
top: 90px;
}
.auth_outage_warningbar_spacer {
height: 80px;
}

View File

@@ -25,7 +25,6 @@
defined('MOODLE_INTERNAL') || die;
// FIXME If plugin not installed, it is still generating the category Outage under Auth Plugins.
if ($hassiteconfig) {
// Configure default settings page.
$settings->visiblename = get_string('menudefaults', 'auth_outage');

View File

@@ -28,8 +28,7 @@ use auth_outage\models\outage;
defined('MOODLE_INTERNAL') || die();
class outage_test extends basic_testcase
{
class outage_test extends basic_testcase {
public function test_constructor() {
$outage = new outage();
// Very important, this should never change.
@@ -39,4 +38,38 @@ class outage_test extends basic_testcase
self::assertNull($v);
}
}
public function test_isongoing() {
$now = time();
// In the past.
$outage = new outage([
'starttime' => $now + (-3 * 60 * 60),
'stoptime' => $now + (-2 * 60 * 60),
'warningduration' => 2 * 60 * 60,
'title' => '',
'description' => ''
]);
self::assertFalse($outage->is_ongoing($now));
// In the present (ongoing).
$outage = new outage([
'starttime' => $now + (-1 * 60 * 60),
'stoptime' => $now + (1 * 60 * 60),
'warningduration' => 2 * 60 * 60,
'title' => '',
'description' => ''
]);
self::assertTrue($outage->is_ongoing($now));
// In the future.
$outage = new outage([
'starttime' => $now + (1 * 60 * 60),
'stoptime' => $now + (2 * 60 * 60),
'warningduration' => 2 * 60 * 60,
'title' => '',
'description' => ''
]);
self::assertFalse($outage->is_ongoing($now));
}
}

View File

@@ -29,8 +29,7 @@ use auth_outage\outagedb;
defined('MOODLE_INTERNAL') || die();
class outagedb_test extends advanced_testcase
{
class outagedb_test extends advanced_testcase {
/**
* Make sure we can save and update.
*/
@@ -95,7 +94,6 @@ class outagedb_test extends advanced_testcase
* Perform some tests on the data itself, checking values after inserted and updated.
*/
public function test_basiccrud() {
return;
$this->resetAfterTest(true);
// Create some outages.
@@ -134,6 +132,64 @@ class outagedb_test extends advanced_testcase
}
}
public function test_getactive() {
$this->resetAfterTest(true);
// Have a consistent time for now (no seconds variation), helps debugging.
$now = time();
// Should never fail.
self::assertEquals([], outagedb::getall(), 'Ensure there are no other outages that can affect the test.');
self::assertNull(outagedb::getactive($now), 'There should be no active outage at this point.');
// An outage that starts in the future and is not in warning period.
self::saveoutage($now, 2, 3, 1);
self::assertNull(outagedb::getactive($now), 'No active outages yet.');
// An outage that is already in the past.
self::saveoutage($now, -3, -2, 1);
self::assertNull(outagedb::getactive($now), 'No active outages yet.');
// An outage in warning period.
$activeid = self::saveoutage($now, 1, 2, 2);
self::assertSame($activeid, outagedb::getactive($now)->id, 'Wrong active outage picked.');
// Another outage in warning period, but ignored as it starts after the previous one.
self::saveoutage($now, 2, 3, 3);
self::assertSame($activeid, outagedb::getactive($now)->id, 'Wrong active outage picked.');
// An ongoing outage.
$activeid = self::saveoutage($now, -2, 2, 1);
self::assertSame($activeid, outagedb::getactive($now)->id, 'Wrong active outage picked.');
// Another ongoing outage but ignored because it started after the previous one.
self::saveoutage($now, -1, 2, 1);
self::assertSame($activeid, outagedb::getactive($now)->id, 'Wrong active outage picked.');
// Another ongoing outage starting at the same time, but ignored as it stops before the previous one.
self::saveoutage($now, -2, 1, 1);
self::assertSame($activeid, outagedb::getactive($now)->id, 'Wrong active outage picked.');
}
/**
* Helper function to create an outage then save it to the database.
*
* @param $now int Timestamp for now, such as time().
* @param $start int In how many hours this outage starts. Can be negative.
* @param $stop int In how many hours this outage finishes. Can be negative.
* @param $warning int Warning duration in hours.
* @return int Id the of created outage.
*/
private static function saveoutage($now, $start, $stop, $warning) {
return outagedb::save(new outage([
'starttime' => $now + ($start * 60 * 60),
'stoptime' => $now + ($stop * 60 * 60),
'warningduration' => ($warning * 60 * 60),
'title' => 'Test Outage',
'description' => 'Test Outage Description.'
]));
}
/**
* Helper function to create an outage for tests.
*