Implement site banner. Set banner for Google Sign-In. (#1304)
* Implement site banner. Set banner for Google Sign-In. * Addressed review comments
This commit is contained in:
Родитель
27312f6f08
Коммит
ca1d31b0ea
|
@ -32,6 +32,7 @@ import settings
|
|||
from framework import permissions
|
||||
from framework import ramcache
|
||||
from framework import secrets
|
||||
from framework import utils
|
||||
from framework import xsrf
|
||||
from internals import models
|
||||
|
||||
|
@ -277,7 +278,9 @@ class FlaskHandler(BaseHandler):
|
|||
'APP_TITLE': settings.APP_TITLE,
|
||||
'current_path': current_path,
|
||||
'TEMPLATE_CACHE_TIME': settings.TEMPLATE_CACHE_TIME,
|
||||
}
|
||||
'banner_message': settings.BANNER_MESSAGE,
|
||||
'banner_time': utils.get_banner_time(settings.BANNER_TIME),
|
||||
}
|
||||
|
||||
user = self.get_current_user()
|
||||
if user:
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import calendar
|
||||
import datetime
|
||||
import flask
|
||||
import logging
|
||||
|
@ -121,3 +122,32 @@ def render_atom_feed(request, title, data):
|
|||
'Content-Type': 'application/atom+xml;charset=utf-8'}
|
||||
text = feed.writeString('utf-8')
|
||||
return text, headers
|
||||
|
||||
|
||||
_ZERO = datetime.timedelta(0)
|
||||
|
||||
class _UTCTimeZone(datetime.tzinfo):
|
||||
"""UTC"""
|
||||
def utcoffset(self, _dt):
|
||||
return _ZERO
|
||||
def tzname(self, _dt):
|
||||
return "UTC"
|
||||
def dst(self, _dt):
|
||||
return _ZERO
|
||||
|
||||
_UTC = _UTCTimeZone()
|
||||
|
||||
|
||||
def get_banner_time(timestamp):
|
||||
"""Converts a timestamp into data so it can appear in the banner.
|
||||
Args:
|
||||
timestamp: timestamp expressed in the following format:
|
||||
[year,month,day,hour,minute,second]
|
||||
e.g. [2009,3,20,21,45,50] represents March 20 2009 9:45:50 PM
|
||||
Returns:
|
||||
EZT-ready data used to display the time inside the banner message.
|
||||
"""
|
||||
if timestamp is None:
|
||||
return None
|
||||
ts = datetime.datetime(*timestamp, tzinfo=_UTC)
|
||||
return calendar.timegm(ts.timetuple())
|
||||
|
|
|
@ -107,3 +107,13 @@ class UtilsFunctionTests(unittest.TestCase):
|
|||
handlerInstance.handlerMethod('/request/path/')
|
||||
self.assertIsNone(handlerInstance.handler_called_with)
|
||||
self.assertEqual('/request/path', handlerInstance.redirected_to)
|
||||
|
||||
def test_get_banner_time__None(self):
|
||||
"""If no time specified, it returns None."""
|
||||
self.assertIsNone(utils.get_banner_time(None))
|
||||
|
||||
def test_get_banner_time__tuple(self):
|
||||
"""If a time tuple is specified, it returns a timestamp."""
|
||||
time_tuple = (2019, 6, 13, 18, 30)
|
||||
actual = utils.get_banner_time(time_tuple)
|
||||
self.assertEqual(1560450600, actual)
|
||||
|
|
11
settings.py
11
settings.py
|
@ -27,6 +27,17 @@ SEND_ALL_EMAIL_TO = 'cr-status-staging-emails+%(user)s+%(domain)s@google.com'
|
|||
|
||||
BOUNCE_ESCALATION_ADDR = 'cr-status-bounces@google.com'
|
||||
|
||||
|
||||
# Display a site maintenance banner on every page. Or, an empty string.
|
||||
BANNER_MESSAGE = ('This site will have a new method of signing in. '
|
||||
'Users will need to sign in again after ')
|
||||
|
||||
# Timestamp used to notify users when the read only mode or other status
|
||||
# described in the banner message takes effect. Or, None. It is
|
||||
# expressed as a tuple of ints: (year, month, day[, hour[, minute[, second]]])
|
||||
# e.g. (2009, 3, 20, 21, 45) represents March 20 2009 9:45PM UTC.
|
||||
BANNER_TIME = (2021, 3, 11, 20, 00) # May 11, noon pacific
|
||||
|
||||
################################################################################
|
||||
|
||||
PROD = False
|
||||
|
|
|
@ -14,6 +14,7 @@ import '@polymer/paper-styles/color.js';
|
|||
// chromedash components
|
||||
import './elements/icons';
|
||||
import './elements/chromedash-accordion';
|
||||
import './elements/chromedash-banner';
|
||||
import './elements/chromedash-callout';
|
||||
import './elements/chromedash-color-status';
|
||||
import './elements/chromedash-feature';
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import {LitElement, css, html} from 'lit-element';
|
||||
import {nothing} from 'lit-html';
|
||||
import SHARED_STYLES from '../css/shared.css';
|
||||
|
||||
|
||||
class ChromedashBanner extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
timestamp: {type: Number},
|
||||
message: {type: String},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.timestamp = null; // Unix timestamp: seconds since 1970-01-01.
|
||||
this.message = '';
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
SHARED_STYLES,
|
||||
css`
|
||||
div {
|
||||
display: inline-block;
|
||||
background: var(--warning-background);
|
||||
color: var(--warning-color);
|
||||
border-radius: var(--border-radius);
|
||||
padding: var(--content-padding);
|
||||
}
|
||||
`];
|
||||
}
|
||||
|
||||
computeLocalDateString() {
|
||||
if (!this.timestamp) {
|
||||
return nothing;
|
||||
}
|
||||
const date = new Date(this.timestamp * 1000);
|
||||
const formatOptions = {
|
||||
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
|
||||
hour: 'numeric', minute: 'numeric'}; // No seconds
|
||||
return date.toLocaleString(undefined, formatOptions);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.message) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div>
|
||||
${this.message}
|
||||
${this.computeLocalDateString()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chromedash-banner', ChromedashBanner);
|
|
@ -96,13 +96,15 @@ limitations under the License.
|
|||
<div id="content">
|
||||
<div id="spinner"><img src="/static/img/ring.svg"></div>
|
||||
<div id="column-container">
|
||||
<div id="drawer-column">{% block drawer %}{% endblock %}</div>
|
||||
<div id="content-column">
|
||||
<div id="horizontal-sub-nav">{% block horizontalsubnav %}{% endblock %}</div>
|
||||
{% block subheader %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="drawer-column">{% block drawer %}{% endblock %}</div>
|
||||
<div id="content-column">
|
||||
<div id="horizontal-sub-nav">{% block horizontalsubnav %}{% endblock %}</div>
|
||||
<chromedash-banner message="{{banner_message}}"
|
||||
timestamp="{{banner_time}}"></chromedash-banner>
|
||||
{% block subheader %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</app-header-layout>
|
||||
|
|
Загрузка…
Ссылка в новой задаче