Commit 3721db31 authored by Leigh Stoller's avatar Leigh Stoller

Two sets of changes:

1. Implement most of issue #258: Add a start and end time for
   announcements. Also add a target uid for announcements.  Also add a
   purge option to fully remove announcements from the database, rather
   then retiring them. Add priorities for ordering announcements.
   Add update mode to modify an existing announcement.

   In other news, announcements now have a uuid and can be specified on
   the command line using their index or their uuid. For the web
   interface we now use uuids only instead of database indexes.

2. The web interface now polls for announcements so that users see new
   announcements without reloading. I noticed that a lot people stay
   parked on the status page and might not see them. Cleaned up DB
   queries and formatting to make is easier to use in an ajax call.
parent 517699a9
#!/usr/bin/perl -w
#
# Copyright (c)-2016 University of Utah and the Flux Group.
# Copyright (c) 2016-2017 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -33,7 +33,8 @@ sub usage()
{
print STDERR "Usage: announce -a <-p portal> [-c] [-s alert_style] [-b button_label] [-u action_url] [-m max_seen_count] <announcement_text>\n";
print STDERR " announce -l [active|retired|all]\n";
print STDERR " announce -r idx [-c]\n";
print STDERR " announce -r idx|uuid [-c]\n";
print STDERR " announce -R idx|uuid\n";
print STDERR " announce -i idx\n";
print STDERR " announce -h\n";
print STDERR " -h This message\n";
......@@ -46,14 +47,19 @@ sub usage()
print STDERR " -m The maximum number of times that this announcement will appear to a user. Every page view (even those in a single session) counts. A value of '0' indicates that the announcement should keep appearing indefinitely until dismissed by the user or an action is taken. Defaults to 20.\n";
print STDERR " -l List global announcements. Defaults to listing active announcements.\n";
print STDERR " -r Retire announcement with the given idx. A retired announcement will no longer be displayed to users.\n";
print STDERR " -R Purge announcement from DB with the given idx or uuid.\n";
print STDERR " -i Info about a particular announcement.\n";
print STDERR " -c Compatibility mode. When adding in compatibility mode, the sitevar is changed, thus setting the legacy Emulab announcement. When removing in compatibility mode, the sitevar is cleared.\n";
print STDERR " -U In add mode, specify a specific target user.\n";
exit(-1);
}
my $optlist = "hacp:s:b:u:m:lr:i:";
my $optlist = "haA:cp:s:b:u:m:lrR:i:U:t:I:S:E:";
my $add_mode = 0;
my $update_mode = 0;
my $update_target;
my $list_mode = 0;
my $retire_mode = 0;
my $purge_mode = 0;
my $info_mode = 0;
my $portal = undef;
my $style = "alert-info";
......@@ -63,6 +69,11 @@ my $max_seen = 20;
my $retire_idx = undef;
my $info_idx = undef;
my $text = undef;
my $target_user = undef;
my $uuid = undef;
my $display_start = undef;
my $display_end = undef;
my $priority = undef;
my $list_type = "active";
my $compatibility = 0;
......@@ -82,6 +93,7 @@ my $TBOPS = "@TBOPSEMAIL@";
#
use lib "@prefix@/lib";
use emdb;
use emutil;
use libtestbed;
use libdb;
use User;
......@@ -110,6 +122,10 @@ if (defined($options{"h"})) {
if (defined($options{"a"})) {
$add_mode = 1;
}
if (defined($options{"A"})) {
$update_mode = 1;
$update_target = $options{"A"};
}
if (defined($options{"l"})) {
$list_mode = 1;
}
......@@ -117,6 +133,10 @@ if (defined($options{"r"})) {
$retire_mode = 1;
$retire_idx = $options{"r"};
}
if (defined($options{"R"})) {
$purge_mode = 1;
$retire_idx = $options{"R"};
}
if (defined($options{"i"})) {
$info_mode = 1;
$info_idx = $options{"i"};
......@@ -133,9 +153,48 @@ if (defined($options{"b"})) {
if (defined($options{"u"})) {
$url = $options{"u"};
}
if (defined($options{"t"})) {
$uuid = $options{"t"};
if ($uuid !~ /^[-\w]*$/) {
print STDERR "Invalid uuid\n";
exit(1);
}
}
if (defined($options{"m"})) {
$max_seen = $options{"m"};
}
if (defined($options{"U"})) {
$target_user = User->Lookup($options{"U"});
if (!defined($target_user)) {
print STDERR "No such user!\n";
exit(-1);
}
}
if (defined($options{"S"})) {
$display_start = $options{"S"};
if ($display_start !~ /^\d+$/) {
$display_start = str2time($display_start);
if (!defined($display_start)) {
fatal("Could not parse -S option.");
}
}
}
if (defined($options{"E"})) {
$display_end = $options{"E"};
if ($display_end !~ /^\d+$/) {
$display_end = str2time($display_end);
if (!defined($display_end)) {
fatal("Could not parse -E option.");
}
}
}
if (defined($options{"I"})) {
$priority = $options{"I"};
if ($priority !~ /^\d+$/) {
fatal("Could not parse -I option.");
}
}
if (defined($options{"c"})) {
$compatibility = 1;
}
......@@ -153,16 +212,17 @@ if ($add_mode)
}
}
if ($add_mode + $list_mode + $retire_mode + $info_mode != 1) {
print STDERR "No mode selected. Must use one of -a, -r, -l, or -i\n\n";
if ($add_mode + $update_mode + $list_mode +
$retire_mode + $purge_mode + $info_mode != 1) {
print STDERR "No mode selected. Must use one of -a, -A, -r, -R, -l, or -i\n\n";
usage();
}
if ($add_mode) {
if ($add_mode || $update_mode) {
$text = join(' ', @ARGV);
}
if (! $list_mode && ! $add_mode && scalar(@ARGV) > 0) {
if (! $list_mode && ! $add_mode && ! $purge_mode && scalar(@ARGV) > 0) {
usage();
}
......@@ -184,11 +244,57 @@ if ($add_mode) {
$query .= ", link_label=" . DBQuoteSpecial($button);
$query .= ", link_url=" . DBQuoteSpecial($url);
}
if (defined($target_user)) {
$query .= ", uid_idx='" . $target_user->uid_idx() . "'";
}
if (defined($uuid)) {
$query .= ", uuid='$uuid'";
}
else {
$query .= ", uuid=uuid()";
}
if (defined($display_start)) {
$query .= ", display_start=FROM_UNIXTIME($display_start)";
}
if (defined($display_end)) {
$query .= ", display_end=FROM_UNIXTIME($display_end)";
}
if (defined($priority)) {
$query .= ", priority='$priority'";
}
DBQueryFatal($query);
if ($compatibility)
{
TBSetSiteVar("web/banner", $text);
}
} elsif ($update_mode) {
#
# Update existing announcement.
#
my $query = "update apt_announcements set ";
$query .= ", text=" . DBQuoteSpecial($text);
if (defined($button) && defined($url)) {
$query .= ", link_label=" . DBQuoteSpecial($button);
$query .= ", link_url=" . DBQuoteSpecial($url);
}
if (defined($display_start)) {
$query .= ", display_start=FROM_UNIXTIME($display_start)";
}
if (defined($display_end)) {
$query .= ", display_end=FROM_UNIXTIME($display_end)";
}
$query .= " ";
if (ValidUUID($update_target)) {
$query .= "where uuid='$update_target'";
}
elsif ($update_target =~ /^\d+$/) {
$query .= "where idx='$update_target'";
}
else {
fatal("Invalid argument for -A mode, must be uuid or idx");
}
DBQueryFatal($query);
} elsif ($list_mode) {
#
# List announcements
......@@ -211,6 +317,16 @@ if ($add_mode) {
my $portalpad = sprintf("%-10s", $portal);
print "$idx\t$portalpad $textbit\n"
}
} elsif ($purge_mode) {
#
# Purge an announcement
#
my $clause = (ValidUUID($retire_idx) ?
"a.uuid='$retire_idx'" : "a.idx='$retire_idx'");
DBQueryFatal("delete a, i ".
"from apt_announcements as a ".
"join apt_announcement_info as i on a.idx = i.aid ".
"where $clause");
} elsif ($retire_mode) {
#
# Retire an announcement
......
......@@ -57,6 +57,22 @@ function Do_Click()
}
}
#
# Return current announcements, use same html as quickvm_sup.php does.
#
function Do_Announcements()
{
global $this_user, $ajax_args;
$results = array();
$announcements = GET_ANNOUNCEMENTS($this_user, false);
if (!count($announcements)) {
SPITAJAX_RESPONSE($results);
return;
}
SPITAJAX_RESPONSE($announcements);
}
# Local Variables:
# mode:php
# End:
......
......@@ -101,6 +101,11 @@ window.APT_OPTIONS.initialize = function (sup)
return false;
});
}
/*
* Setup a timer to ask for announcements.
*/
setTimeout(function f() { window.APT_OPTIONS.Announcements() }, 10000);
window.APT_OPTIONS.startPage();
$(window).on('beforeunload.common', APT_OPTIONS.endPage);
$('body').show();
......@@ -259,3 +264,33 @@ window.APT_OPTIONS.postTutorial = function (data) {
catch (e) {}
}
window.APT_OPTIONS.Announcements = function () {
var callback = function(json) {
if (json.code) {
console.info("announcements", json);
return;
}
var newhtml = "";
if (json.value.length) {
console.info("announcements", json);
_.each(json.value, function(html) {
newhtml += html;
});
}
else {
// Clear current announcements; dismissed in another tab.
newhtml = "";
}
$('#portal-announcement-div').html(newhtml);
setTimeout(function f() { window.APT_OPTIONS.Announcements() }, 10000);
}
var xmlthing =
APT_OPTIONS.CallServerMethod('', 'announcement', 'Announcements', null);
// We want the callback all the time.
xmlthing.done(callback).fail(function () {
setTimeout(function f() { window.APT_OPTIONS.Announcements() }, 90000);
});
}
......@@ -218,24 +218,19 @@ $PAGEHEADER_FUNCTION = function($thinheader = 0, $ignore1 = NULL,
# the buttons when a logged in user shrinks the window the window down,
# and turn them on inside the action menu.
$hiddenxs = ($showmenus ? "hidden-xs" : "");
SPITNAV($hiddenxs, $navbar_status, $navbar_right, $login_uid);
# Put announcements, if any, right below the header.
if (!$cleanmode && $login_user && $login_user->IsActive() &&
!($login_status & CHECKLOGIN_WEBONLY)) {
# Always create empty div for announcements, for ajax update.
echo "<div id='portal-announcement-div'>\n";
$announcements = GET_ANNOUNCEMENTS($login_user);
for ($i = 0; $i < count($announcements); $i++) {
$current = $announcements[$i];
echo "<div class='alert ".$current['style']." alert-dismissible'
role='alert' style='margin-top: -10px; margin-left: 40px; margin-right: 40px;'>";
echo " <button onclick='window.APT_OPTIONS.announceDismiss(" . $current['aid'] . ")' type='button' class='close' data-dismiss='alert' aria-label='Close'><span aria-hidden='true'>&times;</span></button>";
echo " <span>" . $current["text"] . "</span>";
if ($current["url"]) {
echo " <a href='" . $current["url"] . "' class='btn btn-default' onclick='window.APT_OPTIONS.announceClick(" . $current["aid"] . ")' target='_blank'>" . $current["label"] . "</a>";
}
echo " </div>";
echo $announcements[$i];
}
echo "</div>";
}
if (NOLOGINS()) {
$message = TBGetSiteVar("web/message");
......@@ -491,39 +486,69 @@ echo "
}
function GET_ANNOUNCEMENTS($user)
function GET_ANNOUNCEMENTS($user, $update = true)
{
global $PORTAL_GENESIS;
$uid = $user->uid();
$uid_idx = $user->uid_idx();
$dblink = DBConnect("tbdb");
$result = array();
# Add an apt_announcement_info entry for any announcements which don't have one
$query_result = DBQueryWarn('select a.idx from apt_announcements as a left join apt_announcement_info as i on a.idx=i.aid and ((a.uid_idx is NULL and i.uid_idx="'.$uid_idx.'") or (a.uid_idx is not NULL and a.uid_idx=i.uid_idx)) where a.portal="'.$PORTAL_GENESIS.'" and a.retired=0 and i.uid_idx is NULL and (a.uid_idx is NULL or a.uid_idx="'.$uid_idx.'")');
while ($row = mysql_fetch_array($query_result, MYSQL_NUM)) {
DBQueryWarn('insert into apt_announcement_info set aid="'.$row[0].'", uid_idx="'.$uid_idx.'",seen_count=0');
}
$query_result = DBQueryWarn('select a.idx, a.text, a.link_label, a.link_url, i.seen_count, a.style '.
'from apt_announcements as a '.
'left join apt_announcement_info as i on a.idx=i.aid '.
'where (a.uid_idx is NULL or a.uid_idx="'.$uid_idx.'") and '.
'a.retired = 0 and a.portal="'.$PORTAL_GENESIS.'" and '.
'i.uid_idx="'.$uid_idx.'" and '.
'i.dismissed = 0 and i.clicked = 0 and '.
'(a.max_seen = 0 or i.seen_count < a.max_seen)', $dblink);
$result = array();
while ($row = mysql_fetch_array($query_result, MYSQL_NUM)) {
$item = array('text' => $row[1],
'style' => $row[5],
'label' => $row[2],
'aid' => $row[0],
'url' => $row[3]);
if ($row[3]) {
$item['url'] = preg_replace('/\{uid_idx\}/', $uid_idx, $item['url']);
$item['url'] = preg_replace('/\{uid\}/', $uid, $item['url']);
}
array_push($result, $item);
DBQueryWarn('update apt_announcement_info set seen_count='.($row[4]+1).' where aid="'.$row[0].'" and uid_idx="'.$uid_idx.'"');
$query_result =
DBQueryWarn('select a.idx, a.text, a.link_label, a.link_url, '.
' i.seen_count, a.style, a.priority '.
'from apt_announcements as a '.
'left join apt_announcement_info as i on a.idx=i.aid '.
'where (a.uid_idx is NULL or a.uid_idx="'.$uid_idx.'") and '.
' a.retired = 0 and a.portal="'.$PORTAL_GENESIS.'" and '.
' i.uid_idx="'.$uid_idx.'" and '.
' i.dismissed = 0 and i.clicked = 0 and '.
' (a.max_seen = 0 or i.seen_count < a.max_seen) and '.
' (a.display_start is null or now() > a.display_start) and '.
' (a.display_end is null or now() < a.display_end) '.
'order by a.priority asc');
while ($row = mysql_fetch_array($query_result)) {
$text = $row["text"];
$style = $row["style"];
$label = $row["link_label"];
$url = $row["link_url"];
$aid = $row["idx"];
$count = $row["seen_count"];
if ($update) {
$count = $count + 1;
DBQueryWarn("update apt_announcement_info set ".
" seen_count='$count' ".
"where aid='$aid' and uid_idx='$uid_idx'");
}
$html =
"<div class='alert $style alert-dismissible' ".
" role='alert' style='margin-top: -10px; margin-bottom: 12px; ".
" margin-left: 40px; margin-right: 40px; ".
" padding-top: 10px; padding-bottom: 10px;'>\n";
$html .=
" <button onclick='window.APT_OPTIONS.announceDismiss($aid)' " .
" type='button' class='close' ".
" data-dismiss='alert' aria-label='Close'>".
" <span aria-hidden='true'>&times;</span></button>".
" <span>$text</span>";
if ($url) {
$url = preg_replace('/\{uid_idx\}/', $uid_idx, $url);
$url = preg_replace('/\{uid\}/', $uid, $url);
$html .=
" <a href='$url' class='btn btn-xs btn-default' target='_blank' ".
" onclick='window.APT_OPTIONS.announceClick($aid)'>$label</a>";
}
$html .= "\n</div>\n";
$result[] = $html;
}
return $result;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment