. Convert to just code
function componentCodeFromClass(className) {
return className.split('-')[1];
}
// Convert number to string pixel measurement
function intToPixels(number) {
return number.toString() + 'px';
}
function truncate(str) {
return str.substring(0, EVENT_MAX_LENGTH) +
(str.length > EVENT_MAX_LENGTH ? '...' : '');
}
// Tooltip Handling class constructor
function UptimeTooltipHandler(frameWidth) {
this.visible = false;
this.activeDay = {
hovered: false
};
this.tooltip = document.getElementById('uptime-tooltip');
this.frameWidth = frameWidth === undefined ? window.innerWidth : frameWidth;
this.scrolling = false;
window.addEventListener('mousemove', this.tooltipListener.bind(this));
window.addEventListener('orientationchange', this.orientationListener.bind(this));
// on tooltip creation, determine whether to display touch-specific controls
var tooltipCloseButton = document.querySelector('.tooltip-close');
if (touchDevice()) {
var componentsContainer = document.querySelector('.components-container');
componentsContainer.addEventListener('touchstart', this.handleTouch.bind(this));
tooltipCloseButton.addEventListener('touchstart', this.unhoverTooltip.bind(this));
} else {
window.addEventListener('resize', this.resizeListener.bind(this));
// classList not supported by IE < 9
tooltipCloseButton.className += ' hidden';
}
// Handle toggle of group elements
var groupComponents = document.querySelectorAll('[data-js-hook=component-group-opener]');
for (var i = 0; i < groupComponents.length; i++) {
groupComponents[i].addEventListener('click', this.hideTooltip.bind(this));
}
var tooltipBox = document.querySelector('#uptime-tooltip .tooltip-box');
tooltipBox.addEventListener('mouseenter', this.mouseEnteredTooltip.bind(this));
tooltipBox.addEventListener('mouseleave', this.unhoverTooltip.bind(this));
}
document.querySelectorAll('.uptime-day').forEach(function (rect) {
rect.addEventListener('focus', function (event) {
var tooltipHandler = new UptimeTooltipHandler();
tooltipHandler.updateHoveredDay(event);
tooltipHandler.updateTooltip(event);
});
rect.addEventListener('blur', function () {
var tooltipHandler = new UptimeTooltipHandler();
tooltipHandler.unhoverTooltip();
});
rect.addEventListener('keydown', function (event) {
if (event.key === 'Escape' || event.keyCode === 27) {
var tooltipHandler = new UptimeTooltipHandler();
tooltipHandler.unhoverTooltip();
}
});
});
UptimeTooltipHandler.prototype.tooltipListener = function(event) {
if (!this.tooltipHovered) {
this.updateHoveredDay(event);
this.updateTooltip(event);
}
}
// this handler will accommodate for mobile orientation change
UptimeTooltipHandler.prototype.orientationListener = function(event) {
// just close the tooltip
this.unhoverTooltip();
}
UptimeTooltipHandler.prototype.resizeListener = function(event) {
this.frameWidth = window.innerWidth;
}
UptimeTooltipHandler.prototype.handleTouch = function (event) {
if (event.target.classList.contains('uptime-day')) {
event.stopPropagation();
this.bladeTouched(event);
}
}
UptimeTooltipHandler.prototype.mouseEnteredTooltip = function() {
// Necessary to clear the timeout set for closing the tooltip when the mouse
// moves off the blade or timeline, so the tooltip isnt closed on hover
clearTimeout(timeoutId);
// Sets it to null so the timeout can be set later, as clearTimeout only
// cancels the timer, and we need to allow it to be reset in the mouse
// move handler below
timeoutId = null;
this.tooltipHovered = true;
}
UptimeTooltipHandler.prototype.unhoverTooltip = function() {
this.tooltipHovered = false;
this.activeDay.hovered = false;
this.hideTooltip();
}
UptimeTooltipHandler.prototype.bladeTouched = function (event) {
event.preventDefault();
var classes = event.target.getAttribute('class').split(' ');
var componentCode = componentCodeFromClass(classes[1])
var index = dayNumberFromClass(classes[2]);
// If open and tapped on same component and day, close tooltip
if (this.visible && this.activeDay.component === componentCode && this.activeDay.index === index) {
this.hideTooltip();
} else {
this.updateHoveredDay(event);
this.updateTooltip(event);
}
}
UptimeTooltipHandler.prototype.updateHoveredDay = function(event) {
var classes = event.target.getAttribute('class'); // classList doesn't work in IE
var onDay = classes != null && classes.split(' ').indexOf('uptime-day') !== -1;
if (onDay) {
classes = classes.split(' ');
var componentCode = componentCodeFromClass(classes[1]);
this.activeDay = {
index: dayNumberFromClass(classes[2]),
component: componentCode,
bounds: event.target.getBoundingClientRect(),
isGroup: uptimeData[componentCode].component.isGroup,
hovered: true
}
} else {
this.activeDay.hovered = false;
}
}
UptimeTooltipHandler.prototype.updateTooltip = function(event) {
var classes = event.target.getAttribute('class'); // classList doesn't work in IE
var hoveredOnGraphic = classes != null && classes.split(' ').indexOf('availability-time-line-graphic') !== -1;
if (this.activeDay.hovered) {
this.updateTooltipData();
this.positionTooltip();
} else if (this.visible && !this.activeDay.hovered && !hoveredOnGraphic) {
// Important: since this is on mouse move it will be called multiple times
// which will clear timeoutId and reset it to the new value, meaning
// it is a race condition to cancel it
if (!timeoutId) {
var _this = this;
timeoutId = setTimeout(function() {
_this.hideTooltip();
timeoutId = null;
}, 250);
}
}
}
UptimeTooltipHandler.prototype.updateTooltipData = function() {
// Get the data for the day we're hovered on
var day = uptimeData[this.activeDay.component].days[this.activeDay.index];
// Update the date for the tooltip
var date = new Date(day.date);
// Get the component's start date. Note that it will be undefined here unless it is populated in our database
var startDay = uptimeData[this.activeDay.component].component.startDate;
var startDate = startDay ? new Date(startDay) : null;
// Determine whether current date falls before component's start date.
var beforeStartDate = startDate ? date.getTime() < startDate.getTime() : false;
// UTC necessary since days are passed yyyy-mm-dd, and new Date uses midnight UTC, so local times
// are presented as the day before
var dateString = date.getUTCDate() + " " + monthStrings[date.getUTCMonth()] + " " + date.getUTCFullYear();
document.querySelector('#uptime-tooltip .date').innerHTML = dateString;
// Update the outage fields
if (this.activeDay.isGroup) {
this.updateGroupOutageFields()
} else {
this.updateOutageFields(day.outages.p, day.outages.m, day.related_events, beforeStartDate);
}
}
UptimeTooltipHandler.prototype.hoursFromSeconds = function(s) {
return Math.floor(s / 3600);
}
UptimeTooltipHandler.prototype.minutesFromSeconds = function(s) {
// If less than a minute, round up to 1 minute to show that some outage existed
if (s > 0 && s < 60) {
return 1;
}
// Otherwise use floor
return Math.floor((s % 3600) / 60);
}
UptimeTooltipHandler.prototype.updateGroupOutageFields = function() {
// Hide time info
document.querySelector('#uptime-tooltip .outage-field.major').style.display = 'none';
document.querySelector('#uptime-tooltip .outage-field.partial').style.display = 'none';
document.querySelector(".related-events h3").style.display = 'none';
document.querySelector('.no-related-msg').style.display = 'none';
var eventList = document.getElementById("related-events-list")
var cloneList = eventList.cloneNode(false);
eventList.parentNode.replaceChild(cloneList, eventList);
var partialCount = 0;
var majorCount = 0;
/**
We were originally using the operationalCount as part of the no outage copy for group components,
but ultimately decided not to use it. I opted to leave the variable in place in case we ever
decide to use it in the future.
*/
var operationalCount = 0;
var noDataCount = 0;
var showcasedComponentsCount = 0;
var components = uptimeData[this.activeDay.component].component.group
for (var i = 0; i < components.length; i++) {
if (!uptimeData[components[i]]) continue;
showcasedComponentsCount++;
var outages = uptimeData[components[i]].days[this.activeDay.index].outages;
var currentDay = uptimeData[components[i]].days[this.activeDay.index];
var currentDate = new Date(currentDay.date);
// Get the component's start date. Note that it will be undefined here unless it is populated in our database
var startDay = uptimeData[components[i]].component.startDate;
var startDate = startDay ? new Date(startDay) : null;
if (outages.p) {
partialCount += 1;
}
if (outages.m) {
majorCount += 1;
}
// Only increase operational count if component has data for this day
if (!outages.p && !outages.m) {
if (startDate && currentDate.getTime() < startDate.getTime()) {
noDataCount +=1;
}
else {
operationalCount +=1;
}
}
}
document.querySelector('#major-outage-group-count').style.display = majorCount ? 'block' : 'none';
document.querySelector('#partial-outage-group-count').style.display = partialCount ? 'block' : 'none';
document.querySelector('#major-outage-group-count .count').innerText = majorCount + (majorCount === 1 ? " component" : " components");
document.querySelector('#partial-outage-group-count .count').innerText = partialCount + (partialCount === 1 ? " component" : " components ");
// Show no data message only if we do not have data for any showcased components in the group
var showNoDataMessage = noDataCount === showcasedComponentsCount;
// Show no outages message if we have data for the components and no outages in that data
document.querySelector('#uptime-tooltip .no-outages-msg').style.display = (majorCount || partialCount || showNoDataMessage) ? 'none' : 'block';
document.querySelector('#uptime-tooltip .no-data-msg').style.display = showNoDataMessage ? 'block' : 'none';
}
UptimeTooltipHandler.prototype.updateOutageFields = function(partial, major, relatedEvents, beforeStartDate) {
// Hide group info
document.querySelector('#major-outage-group-count').style.display = 'none';
document.querySelector('#partial-outage-group-count').style.display = 'none';
// Show the message that no outage present, if none is present
if (partial || major || beforeStartDate) {
document.querySelector('#uptime-tooltip .no-outages-msg').style.display = 'none';
} else {
document.querySelector('#uptime-tooltip .no-outages-msg').style.display = 'block';
}
if (beforeStartDate) {
document.querySelector('#uptime-tooltip .no-data-msg').style.display = 'block';
}
else {
document.querySelector('#uptime-tooltip .no-data-msg').style.display = 'none';
}
// Update partial outage field if an outage exists, otherwise hide it
if (partial) {
var hrs = this.hoursFromSeconds(partial);
var mins = this.minutesFromSeconds(partial);
document.querySelector('#uptime-tooltip .outage-field.partial .value-hrs').innerHTML = hrs.toString() + ' hrs';
document.querySelector('#uptime-tooltip .outage-field.partial .value-mins').innerHTML = mins.toString() + ' mins';
document.querySelector('#uptime-tooltip .outage-field.partial').style.display = 'flex';
} else {
document.querySelector('#uptime-tooltip .outage-field.partial').style.display = 'none';
}
// Update major outage field if an outage exists, otherwise hide it
if (major) {
var hrs = this.hoursFromSeconds(major);
var mins = this.minutesFromSeconds(major);
document.querySelector('#uptime-tooltip .outage-field.major .value-hrs').innerHTML = hrs.toString() + ' hrs';
document.querySelector('#uptime-tooltip .outage-field.major .value-mins').innerHTML = mins.toString() + ' mins';
document.querySelector('#uptime-tooltip .outage-field.major').style.display = 'flex';
} else {
document.querySelector('#uptime-tooltip .outage-field.major').style.display = 'none';
}
var eventList = document.getElementById("related-events-list")
var cloneList = eventList.cloneNode(false);
document.querySelector(".related-events h3").style.display = (relatedEvents.length ? 'block' : 'none');
for (var i = 0; i < relatedEvents.length; i++) {
var listItem = document.createElement("li");
listItem.className = "related-event";
var anchor = document.createElement("a");
anchor.className = "related-event-link";
anchor.target = "_blank";
anchor.href = window.Routes.incident_path(relatedEvents[i].code);
var text = document.createTextNode(truncate(relatedEvents[i].name));
anchor.appendChild(text);
listItem.appendChild(anchor);
cloneList.appendChild(listItem);
}
const displayNoRelatedMsg = ((major || partial) && !relatedEvents.length);
document.querySelector('.no-related-msg').style.display = (displayNoRelatedMsg ? 'block' : 'none');
eventList.parentNode.replaceChild(cloneList, eventList);
}
UptimeTooltipHandler.prototype.positionTooltip = function() {
this.calculatePointerCenter();
this.calculateBoxPosition();
// show tooltip
this.tooltip.style.display = 'block';
// position pointer
var pointer = this.tooltip.getElementsByClassName('pointer-container')[0];
pointer.style.left = intToPixels(this.pointerCenter.x - 8);
pointer.style.top = intToPixels(this.pointerCenter.y - 5);
// position display box
var box = this.tooltip.getElementsByClassName('tooltip-box')[0];
box.style.left = intToPixels(this.boxLeft);
box.style.top = intToPixels(this.pointerCenter.y + 5);
this.visible = true;
}
UptimeTooltipHandler.prototype.calculatePointerCenter = function() {
var bounds = this.activeDay.bounds;
var rectLeft = bounds.left + window.pageXOffset;
var rectBottom = bounds.bottom + window.pageYOffset;
var rectWidth = bounds.right - bounds.left;
this.pointerCenter = {
x: rectLeft + Math.floor(rectWidth / 2),
y: rectBottom + 5
}
}
UptimeTooltipHandler.prototype.calculateBoxPosition = function() {
var sideWidth = 162.5;
if (this.pointerCenter.x - sideWidth < 0) {
this.boxLeft = 0;
} else if (this.pointerCenter.x + sideWidth > this.frameWidth) {
this.boxLeft = this.frameWidth - sideWidth * 2;
} else {
this.boxLeft = this.pointerCenter.x - sideWidth;
}
}
UptimeTooltipHandler.prototype.hideTooltip = function() {
this.tooltip.style.display = 'none';
this.visible = false;
}
new UptimeTooltipHandler();