Created
August 4, 2014 22:32
-
-
Save stillinbeta/fb30bb6d0ca999b92e27 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# The Incident Dashboard module | |
Rearview.module('Dashboard', (Dashboard, App, Backbone, Marionette, $, _) -> | |
Dashboard.Router = Backbone.Router.extend | |
routes: { | |
"dashboard(/)": "dashboard", | |
"dashboard/:team(/)": "dashboard" | |
} | |
## Layouts ################################################################# | |
class Dashboard.DashboardLayout extends Backbone.Marionette.Layout | |
template: '#dashboardlayout-tmpl' | |
id: 'dashboard-layout' | |
className: 'layout' | |
regions: { | |
datenav: '#datenav-region' | |
visualization: '#visualization-region' | |
incidents: '#incidents-region' | |
} | |
class Dashboard.DatenavLayout extends Backbone.Marionette.Layout | |
template: '#datenav-tmpl' | |
id: 'datenav-layout' | |
regions: { | |
datefilter: '#datefilter' | |
teamfilter: '#teamfilter' | |
offhours: '#stat-offhours' | |
daytime: '#stat-daytime' | |
minor: '#stat-minor' | |
alertless: '#stat-alertless' | |
} | |
# the incident timeline layout | |
class Dashboard.IncidentTimelineLayout extends Backbone.Marionette.Layout | |
template: '#incidenttimelinelayout-tmpl' | |
className: 'incidenttimeline' | |
regions: { | |
aggregates: '.aggregates', | |
timeline: '.timeline', | |
} | |
## Controllers ############################################################# | |
class Dashboard.Defaults extends Backbone.Marionette.Controller | |
team: 'all' | |
timeRangeEnd: moment.utc().toDate(), | |
timerangeStart: moment.utc().subtract(1, 'weeks').toDate(), | |
alertOfftime: true | |
alertOntime: true | |
alertMinor: true | |
alertLess: true | |
setDefaults: (team) => | |
if @team != team | |
@team = team | |
App.vent.trigger('set:team', @team) | |
class Dashboard.Url extends Backbone.Marionette.Controller | |
initialize: (router, defaults) -> | |
@router = router | |
@team = defaults.team | |
navigate: => | |
@router.navigate("/dashboard/#{@team}/#{@week}/#{@alertLevels}/#{@timeStart}", | |
{replace: true}) | |
## Views ################################################################### | |
class Dashboard.DateFilterView extends Backbone.View | |
tagName: 'input' | |
attributes: { | |
type: 'week', | |
name: 'range', | |
value: moment().utc().subtract('weeks', 1).format("YYYY-[W]WW"), | |
max: moment().utc().format("YYYY-[W]WW"), | |
} | |
render: => | |
log.debug("dateview render") | |
@$el.on('change', @onChange) | |
@onChange() | |
onChange: => | |
week_re = /(\d{4})-W(\d{2})/i | |
match = week_re.exec(@el.value) | |
if not match | |
return false # invalid value | |
start = moment.utc().year(match[1]).isoWeeks(match[2]).startOf('isoWeek') | |
end = moment(start).add(1, 'weeks') | |
@trigger('range:updated', start, end) | |
App.vent.trigger('range:updated', start, end) | |
return false | |
class Dashboard.TeamFilterView extends Backbone.View | |
className: "teamFilterWrap" | |
initialize: => | |
App.vent.on('teamsUpdated', @update) | |
App.vent.on('set:team', @update) | |
render: => | |
log.debug("teamview render") | |
@$el.append("<select name=\"team\" placeholder=\"Select a team…\"><option value=\"\">All teams</option></select>") | |
@select = @$el.find('select') | |
# @select.chosen({width: "100%", allow_single_deselect: true}) | |
@$el.on('change', @onChange) | |
@onChange() | |
update: (selected) => | |
log.debug("teamview update! selected: #{selected}") | |
@select.find('option[value!=""]').remove() | |
Dashboard.teams.each((team) => | |
@select.append("<option value=\"#{team.name()}\">#{team.name()}</option>") | |
) | |
onChange: => | |
log.debug(@select[0].value) | |
App.vent.trigger("filter:team", @select[0].value) | |
class Dashboard.MetadataView extends Backbone.Marionette.ItemView | |
template: '#metadata-tmpl' | |
initialize: -> | |
@collection.on("add remove change", _.debounce(@render, 100)) | |
@collection.on("reset", @render) | |
@render | |
serializeData: => | |
serialized = {} | |
if @collection.length >= 0 | |
serialized['incidentcount'] = @collection.length | |
serialized['pagecount'] = @collection.totalpages() | |
serialized['pagepeople'] = @collection.uniquepeople().length | |
return serialized | |
class Dashboard.StatToggleView extends Backbone.Marionette.ItemView | |
template: '#stattoggle-tmpl' | |
className: 'stattoggle-face' | |
defaults: { | |
incidenttype: null | |
name: null | |
} | |
# needs to receive an incident type, a reader-friendly name, and a filteredcollection | |
initialize: (options) -> | |
@options = _.defaults(options, @defaults) | |
@collection.on("add remove", _.debounce(@render, 100)) | |
@collection.on("reset", @render) | |
@render() | |
serializeData: => | |
json = {} | |
json['name'] = @options.name | |
json['count'] = @collection.filter((i) => i.incidentType() == @options.incidenttype).length | |
return json | |
onRender: => | |
toggle = @$el.find(':checkbox') | |
toggle.attr('checked', 'checked') | |
toggle.attr('checked', @options.filter.get(@options.incidenttype)) | |
toggle.on('change', @onChange) | |
onChange: (evt) => | |
if evt.target.checked | |
@trigger('incidenttype:selected', @options.incidenttype) | |
@options.filter.trigger("incidenttype:selected", @options.incidenttype) | |
if not evt.target.checked | |
@trigger('incidenttype:deselected', @options.incidenttype) | |
@options.filter.trigger("incidenttype:deselected", @options.incidenttype) | |
class Dashboard.IncidentListItemView extends Backbone.Marionette.ItemView | |
template: '#incidentdetail-tmpl' | |
className: 'incidentdetail' | |
serializeData: => | |
serialized = @model.toJSON() | |
serialized['duration'] = @model.duration().humanize() | |
serialized['alerts'] = _.map(@model.sortedPeoplePaged(), (person) => | |
user = @options.users.get(person.pagerduty_person_id) | |
if user | |
person['avatar_url'] = user.get('avatar_url') | |
person['email'] = user.get('email') | |
return person | |
) | |
serialized['alert_count'] = serialized['alerts'].length | |
serialized['people'] = @model.uniquePeoplePaged().length | |
return serialized | |
onRender: => | |
@$el.attr("data-incidenttype", @model.incidentType()) | |
@$el.attr("data-incidentid", @model.id) | |
@$el.on('click', (evt) => | |
App.vent.trigger('incident:focus', @model.id) | |
) | |
@$el.on('mouseenter', (evt) => | |
App.vent.trigger('incident:focus', @model.id) | |
) | |
class Dashboard.IncidentListView extends Backbone.Marionette.CollectionView | |
itemView: Dashboard.IncidentListItemView | |
className: 'incidents' | |
onRender: => | |
@visibleCache = new Rearview.Incidents() | |
$(window).on("resize", _.debounce(@resizeHandler, 100)) | |
@resizeHandler() | |
@$el.on("scroll", _.debounce(@visibleIncidents, 15)) | |
@$el.on('mouseleave', (evt) => | |
App.vent.trigger('incident:blur', null)) | |
@visibleIncidents() | |
resizeHandler: => | |
@bounds = @el.getBoundingClientRect() | |
visibleIncidents: => | |
if @bounds.height == 0 | |
return | |
views = @children.filter((incident) => | |
rect = incident.el.getBoundingClientRect() | |
return rect.bottom >= @bounds.top && rect.top <= @bounds.bottom) | |
visible = new Rearview.Incidents(_.pluck(views, 'model')) | |
ids = visible.pluck('id') | |
if _.isEqual(visible, @visibleCache) | |
return | |
@visibleCache = visible | |
App.vent.trigger('incidents:focus', @visibleCache) | |
class Dashboard.IncidentTimelineView extends Backbone.View | |
className: 'd3incidents' | |
defaults: { | |
width: 0, | |
height: 0, | |
paddingY: 0, | |
paddingX: 0, | |
dot_radii: { | |
offhours: 9, | |
daytime: 7, | |
minor: 3, | |
alertless: 3 | |
} | |
low_priority: ['minor', 'alertless'] | |
anim_duration: 350, | |
anim_offset: 100, | |
rangeEnd: moment.utc().toDate(), | |
rangeStart: moment.utc().subtract(1, 'weeks').toDate(), | |
focusEnd: new Date(0), | |
focusStart: new Date(0), | |
focusIncident: null, | |
} | |
initialize: (options) -> | |
log.debug("d3 init") | |
@options = _.defaults(options, @defaults) | |
@setup() | |
@collection.on("add remove change", _.debounce(@render, 100)) | |
@collection.on("reset", @render) | |
@collection.once("reset", @resizeHandler) | |
$(window).on("resize", _.debounce(@resizeHandler, 100)) | |
App.vent.on('range:updated', @setDateRange) | |
App.vent.on('incidents:focus', @setFocusRange) | |
App.vent.on('incident:focus', @setFocusIncident) | |
App.vent.on('incident:blur', @setFocusIncident) | |
setDateRange: (start, end) => | |
# The event sends moment instances, d3 needs dates. | |
@options.rangeStart = start.toDate() | |
@options.rangeEnd = end.toDate() | |
@render() | |
setFocusRange: (incidents) => | |
extents = d3.extent(incidents.starts()) | |
@options.focusStart = extents[0] | |
@options.focusEnd = extents[1] | |
@renderFocusOverlay() | |
setFocusIncident: (incidentid) => | |
@options.focusIncident = @collection.get(incidentid) | |
@renderFocusDot() | |
getDateRangeDisplayExtents: => | |
# return [moment.utc(@options.rangeStart).subtract(12, 'hours').toDate(), | |
# moment.utc(@options.rangeEnd).add(12, 'hours').toDate()] | |
return [@options.rangeStart, @options.rangeEnd] | |
resizeHandler: => | |
@recalculateDimensions() | |
@resize() | |
@render() | |
recalculateDimensions: => | |
@options.width = @$el.width() | |
@options.height = @$el.height() | |
log.debug("Recalculating dimensions: #{@options.width} x #{@options.height}") | |
@chartwidth = @options.width - (2 * @options.paddingX) | |
@chartheight = @options.height - (2 * @options.paddingY) | |
@chartleft = @options.paddingX | |
@chartright = @options.paddingX + @chartwidth | |
@charttop = @options.paddingY | |
@chartbottom = @options.paddingY + @chartheight | |
@timeAxisYPos = @chartheight - 50 | |
@criticalDotsCYPos = @chartheight - 80 | |
@minorDotsCYPos = @chartheight - 60 | |
@barpadding = 5 | |
@maxbarheight = @criticalDotsCYPos - @options.dot_radii.offhours - @barpadding | |
@spineHeightOvershoot = { | |
offhours: @barpadding, | |
daytime: @barpadding + (@options.dot_radii.offhours - @options.dot_radii.daytime), | |
} | |
setup: => | |
# chart setup goes here | |
# render will be called multiple times, so any common content | |
# (axes, groups, etc) should only be append()ed here, otherwise | |
# a new one will get added on render every time | |
log.debug("Timeline setup") | |
@recalculateDimensions() | |
@svg = d3.select(@el).append("svg") | |
# prepare scales | |
@timescale = d3.time.scale.utc().nice(d3.time.day) | |
.domain(@getDateRangeDisplayExtents()) | |
@peopleScale = d3.scale.linear() | |
# y-axis (people paged) | |
@peopleAxis = d3.svg.axis() | |
.scale(@peopleScale) | |
.orient('left') | |
.tickFormat(-> return null) | |
@peopleAxisLabels = d3.svg.axis() | |
.scale(@peopleScale) | |
.orient('right') | |
.tickSize(0) | |
.tickFormat( (e) => | |
# only return round numbers, you can't have 2.5 people paged | |
if Math.floor(e) != e | |
return | |
else | |
# label the topmost value in the chart | |
if e == @peopleScale.domain()[1] | |
return "#{e} #{Swag.helpers.inflect(e, 'person', 'people')}" | |
else | |
return e) | |
@peopleAxisg = @svg.append('g') | |
.classed('axis', true) | |
.classed('yaxis', true) | |
.classed('peopleaxis', true) | |
.classed('hidden', true) | |
.call(@peopleAxis) | |
@peopleAxisLabelsg = @svg.append('g') | |
.classed('axis', true) | |
.classed('yaxis', true) | |
.classed('peopleaxis', true) | |
.classed('hidden', true) | |
.call(@peopleAxisLabels) | |
@vis = @svg.append('g') | |
# x-axis major tickmarks (days) | |
@timeDaysAxis = d3.svg.axis() | |
.scale(@timescale) | |
.orient('bottom') | |
.ticks(d3.time.day.utc, 1) | |
.tickFormat('') | |
.tickSize(24) | |
.tickPadding(6) | |
@timeDaysAxisLabels = d3.svg.axis() | |
.scale(@timescale) | |
.orient('bottom') | |
.ticks(d3.time.hour.utc, 12) | |
.tickFormat((d) => | |
# only draw labels at noon, between the date boundaries | |
if d.getUTCHours() == 12 | |
# if the month changed | |
# or it's the first label, show the month | |
if d.getUTCDate() == 1 or d.toDateString() == @options.rangeStart.toDateString() | |
formatter = d3.time.format.utc('%a %d %b') | |
else | |
formatter = d3.time.format.utc('%a %d') | |
return formatter(d) | |
else | |
return null) | |
.tickSize(0) | |
.tickPadding(30) | |
# x-axis minor ticks (hours) | |
# Only label 9a and 6p | |
@timeHoursAxis = d3.svg.axis() | |
.scale(@timescale) | |
.orient('bottom') | |
.ticks(d3.time.hour.utc, 3) | |
.tickFormat((d) -> | |
hours = d.getUTCHours() | |
if hours == 9 | |
return '9a' | |
else if hours == 18 | |
return '6p' | |
else | |
return null) | |
.tickPadding(2) | |
@timeHoursAxisg = @vis.append('g') | |
.classed('axis', true) | |
.classed('xaxis', true) | |
.classed('timehoursaxis', true) | |
.call(@timeHoursAxis) | |
@timeDaysAxisg = @vis.append('g') | |
.classed('axis', true) | |
.classed('xaxis', true) | |
.classed('timedaysaxis', true) | |
.call(@timeDaysAxis) | |
@timeDaysAxisLabelsg = @vis.append('g') | |
.classed('axis', true) | |
.classed('xaxis', true) | |
.classed('timedaysaxis', true) | |
.call(@timeDaysAxisLabels) | |
@incidentdotgroups = {} | |
_.each(_.keys(@defaults.dot_radii), (incidenttype) => | |
@incidentdotgroups[incidenttype] = @vis.append('g') | |
.classed('incidentgroup', true) | |
.attr('data-incidenttype', incidenttype) | |
) | |
@incidentBarGroups = {} | |
@incidentBarGroups['daytime'] = @vis.append('g') | |
.classed('incidentbars', true) | |
.attr('data-incidenttype', 'daytime') | |
@incidentBarGroups['offhours'] = @vis.append('g') | |
.classed('incidentbars', true) | |
.attr('data-incidenttype', 'offhours') | |
# XXX TODO: nighttime shading | |
# @nightsg = @vis.append('g') | |
# .classed('nights') | |
@focusOverlay = @vis.append('rect') | |
.classed('focusOverlay', true) | |
@focusIncidentDot = @vis.append('circle') | |
.classed('focusIncidentDot', true) | |
.classed('hidden', true) | |
@resize() | |
@render() | |
return @ | |
resize: => | |
log.debug("Resizing") | |
@svg.attr("width", @options.width) | |
.attr("height", @options.height) | |
@vis.attr("transform", "translate(#{@options.paddingX},#{@options.paddingY})") | |
.attr("width", @chartwidth) | |
.attr("height", @chartheight) | |
@timescale.rangeRound([0, @chartwidth]) | |
@peopleScale.rangeRound([@maxbarheight, 0]) # TODO rangeround or range? | |
@peopleAxis.tickSize(@options.width) | |
@timeHoursAxisg.attr('transform', "translate(0, #{@timeAxisYPos})") | |
@timeDaysAxisg.attr('transform', "translate(0, #{@timeAxisYPos})") | |
@timeDaysAxisLabelsg.attr('transform', "translate(0, #{@timeAxisYPos})") | |
@peopleAxisg.attr('transform', "translate(#{@options.width}, 0)") | |
# place labels just below the line they relate to | |
@peopleAxisLabelsg.attr('transform', "translate(5, 10)") | |
@focusOverlay.attr('height', @chartheight) | |
render: => | |
log.debug("Rendering timeline") | |
if @options.width == 0 || @options.height == 0 | |
log.debug("Skipping render, chart is #{@options.width}x#{@options.height}") | |
return @ | |
if not @collection.contains(@options.focusIncident) | |
log.debug("Stale focusIncident, unsetting") | |
@options.focusIncident = null | |
# update pages scale and render axes | |
maxpeople = @collection.peopleextent()[1] | |
@peopleScale.domain([0, maxpeople]) | |
@peopleAxis.ticks(maxpeople) | |
@peopleAxisLabels.ticks(maxpeople) | |
@peopleAxisg.transition().duration(@options.anim_duration).call(@peopleAxis) | |
@peopleAxisg.classed('hidden', @collection.length == 0) | |
@peopleAxisLabelsg.transition().duration(@options.anim_duration).call(@peopleAxisLabels) | |
@peopleAxisLabelsg.classed('hidden', @collection.length == 0) | |
# update timescale and render axes | |
@timescale.domain(@getDateRangeDisplayExtents()) | |
# TODO: animate time transitions, right now there aren't any | |
# time_transition = .transition().duration(250) | |
@timeDaysAxisg.transition().duration(@options.anim_duration).call(@timeDaysAxis) | |
@timeDaysAxisLabelsg.transition().duration(@options.anim_duration).call(@timeDaysAxisLabels) | |
@timeHoursAxisg.transition().duration(@options.anim_duration).call(@timeHoursAxis) | |
# draw incident dots | |
_.each(_.keys(@defaults.dot_radii), (incidenttype, index) => | |
dotgroup = @incidentdotgroups[incidenttype] | |
.selectAll('.incidentdot.' + incidenttype) | |
.data(@collection.filter((i) -> i.incidentType() == incidenttype), | |
(d) -> return d.id ) | |
# update | |
# enter | |
dotgroup.enter().append('circle') | |
.attr('class', (d) -> return "incidentdot #{d.incidentType()}") | |
.attr('r', Rearview.zeroish) | |
.attr('cy', (d) => | |
if d.incidentType() in @defaults.low_priority | |
return @minorDotsCYPos | |
else | |
return @criticalDotsCYPos) | |
.attr('cx', (d) => return @timescale(d.opened_at())) | |
.attr('data-incidentid', (d) -> return d.id) | |
# enter+update | |
# stagger animation of the four groups of incidents | |
# see http://vis.berkeley.edu/papers/animated_transitions/ | |
dotgroup.transition().duration(@options.anim_duration).delay(index*@options.anim_offset) | |
.attr('r', (d) => | |
return @defaults.dot_radii[d.incidentType()]) | |
.attr('cy', (d) => | |
if d.incidentType() in @defaults.low_priority | |
return @minorDotsCYPos | |
else | |
return @criticalDotsCYPos) | |
.attr('cx', (d) => return @timescale(d.opened_at())) | |
# exit | |
dotgroup.exit().transition() | |
.delay((d) => | |
if d.is_critical() then @options.anim_duration else 0) | |
.duration(@options.anim_duration) | |
.attr('r', Rearview.zeroish) | |
) | |
barwidth = (d) => | |
x0 = @timescale(d.opened_at()) | |
if d.closed_at()? | |
x1 = @timescale(d.closed_at()) | |
else | |
x1 = @timescale(new Date()) | |
width = x1-x0 | |
if width < 0 | |
width = 0 | |
return width | |
# draw bars | |
_.each(_.keys(@incidentBarGroups), (incidenttype, index) => | |
bars = @incidentBarGroups[incidenttype].selectAll('.incidentbarg') | |
.data(@collection.filter((i) -> i.incidentType() == incidenttype), | |
(d) -> return d.id) | |
# UPDATE | |
# ENTER | |
# a group for each new bar, because I want to draw multiple | |
# shapes for each bar. | |
newbars = bars.enter().append('g') | |
.classed('incidentbarg', true) | |
.attr('data-incident', (d) -> d.get('id')) | |
.attr('data-service', (d) -> d.get('service')) | |
.attr('data-team', (d) -> d.get('team')) | |
.attr('data-numpages', (d) -> d.counts().total) | |
# draw the new bars | |
newbars.append('rect') | |
.classed('incidentbar', true) | |
.attr('x', (d) => @timescale(d.opened_at())) | |
.attr('width', barwidth) | |
.attr('height', Rearview.zeroish) | |
# draw the bar spines. | |
newbars.append('rect') | |
.classed('incidentbarspine', true) | |
.attr('x', (d) => @timescale(d.opened_at()) - 1) | |
.attr('width', 1) | |
.attr('y', @maxbarheight + @spineHeightOvershoot[incidenttype]) | |
.attr('height', Rearview.zeroish) | |
# ENTER+UPDATE: update bar positions | |
bars.select('.incidentbar') | |
.transition().duration(@options.anim_duration).delay(index*@options.anim_offset) | |
.attr('x', (d) => @timescale(d.opened_at())) | |
.attr('width', barwidth) | |
.attr('y', (d) => @peopleScale(d.uniquePeoplePaged().length)) | |
.attr('height', (d) => @maxbarheight - @peopleScale(d.uniquePeoplePaged().length)) | |
bars.select('.incidentbarspine') | |
.transition().duration(@options.anim_duration).delay(index*@options.anim_offset) | |
.attr('x', (d) => @timescale(d.opened_at()) - 1) | |
.attr('width', 1) | |
.attr('y', (d) => @peopleScale(d.uniquePeoplePaged().length)) | |
.attr('height', (d) => | |
@maxbarheight - @peopleScale(d.uniquePeoplePaged().length) + @spineHeightOvershoot[incidenttype] ) | |
exitbars = bars.exit() | |
exitbars.select('.incidentbar') | |
.transition().duration(@options.anim_duration) | |
.attr('y', @maxbarheight) | |
.attr('height', Rearview.zeroish) | |
exitbars.select('.incidentbarspine') | |
.transition().duration(@options.anim_duration) | |
.attr('y', @maxbarheight + @spineHeightOvershoot[incidenttype]) | |
.attr('height', Rearview.zeroish) | |
) | |
@renderFocusOverlay() | |
@renderFocusDot() | |
return @ | |
renderFocusOverlay: => | |
if _.isUndefined(@options.focusStart) or _.isUndefined(@options.focusEnd) | |
return | |
left = @timescale(@options.focusStart) - 5 | |
right = @timescale(@options.focusEnd) + 5 | |
if left < 0 | |
left = 0 | |
if right > @chartwidth | |
right = @chartwidth | |
width = right - left | |
if width <= 0 | |
width = Rearview.zeroish | |
@focusOverlay.transition().duration(100) | |
.style('opacity', '1') | |
.attr('width', width) | |
.attr('transform', "translate(#{left}, 0)") | |
.transition() | |
.delay(1000) | |
.duration(1000) | |
.style('opacity', '0') | |
renderFocusDot: => | |
if not @options.focusIncident | |
@focusIncidentDot.classed('hidden', true) | |
return | |
incidentType = @options.focusIncident.incidentType() | |
cy = if incidentType in @options.low_priority \ | |
then @minorDotsCYPos | |
else @criticalDotsCYPos | |
cx = @timescale(@options.focusIncident.opened_at()) | |
r = @options.dot_radii[incidentType] | |
@focusIncidentDot.attr('class', "focusIncidentDot incidentdot #{incidentType}") | |
.attr('cy', cy) | |
.attr('cx', cx) | |
.attr('r', r) | |
Dashboard.on('before:start', -> | |
log.debug('Dashboard: before start') | |
@incidents = new Rearview.Incidents() | |
@filteredIncidents = new Rearview.FilteredIncidents(@incidents) | |
@filter = new Rearview.IncidentFilter({filtered: @filteredIncidents}) | |
@services = new Rearview.Services() | |
@teams = new Rearview.Teams() | |
@users = new Rearview.Users() | |
@services.on('reset', -> App.vent.trigger('servicesUpdated')) | |
@teams.on('reset', -> App.vent.trigger('teamsUpdated')) | |
@users.on('reset', -> App.vent.trigger('usersUpdated')) | |
@services.fetch({reset: true}) | |
@teams.fetch({reset: true}) | |
@users.fetch({reset: true}) | |
) | |
Dashboard.addInitializer(-> | |
@router = new Dashboard.Router() | |
@defaults = new Dashboard.Defaults() | |
log.debug("defaults:") | |
log.debug(@defaults) | |
@urlController = new Dashboard.Url(@router, @defaults) | |
@router.on('route:dashboard', (team) => | |
@defaults.setDefaults(team) if team? | |
) | |
log.debug('Dashboard: initializing') | |
@dashboardLayout = new Dashboard.DashboardLayout() | |
@datenavLayout = new Dashboard.DatenavLayout() | |
Rearview.global.show(@dashboardLayout) | |
@dashboardLayout.datenav.show(@datenavLayout) | |
@loadingView = new Rearview.LoadingView() | |
@incidentList = new Dashboard.IncidentListView({ | |
collection: @filteredIncidents | |
itemViewOptions: { users: @users } | |
}) | |
App.vent.on('range:updated', (start, end) => | |
@dashboardLayout.incidents.show(@loadingView) | |
@incidents.reset() | |
@incidents.once('sync', => | |
@incidentList = new Dashboard.IncidentListView({ | |
collection: @filteredIncidents | |
itemViewOptions: { users: @users } | |
}) | |
@dashboardLayout.incidents.show(@incidentList) | |
@incidentList.resizeHandler() | |
@incidentList.visibleIncidents() | |
) | |
@incidents.fetch({ | |
data: { | |
start: start.format(), | |
end: end.format(), | |
} | |
}) | |
) | |
App.vent.on('filter:team', (team) => | |
log.debug("filtering teams...") | |
if team == '' | |
@filter.unset('team') | |
else | |
@filter.set({team: team}) | |
) | |
@itlview = new Dashboard.IncidentTimelineView({collection: @filteredIncidents}) | |
@dashboardLayout.visualization.show(@itlview) | |
@datenavLayout.datefilter.show(new Dashboard.DateFilterView()) | |
@datenavLayout.teamfilter.show(new Dashboard.TeamFilterView()) | |
@toggle_offhours = new Dashboard.StatToggleView({ | |
incidenttype: 'offhours', | |
name: "Off-hours Critical", | |
collection: @filteredIncidents, | |
filter: @filter, | |
}) | |
@toggle_daytime = new Dashboard.StatToggleView({ | |
incidenttype: 'daytime', | |
name: "Daytime Critical", | |
collection: @filteredIncidents, | |
filter: @filter, | |
}) | |
@toggle_minor = new Dashboard.StatToggleView({ | |
incidenttype: 'minor', | |
name: "Minor", | |
collection: @filteredIncidents, | |
filter: @filter, | |
}) | |
@toggle_alertless = new Dashboard.StatToggleView({ | |
incidenttype: 'alertless', | |
name: "Alertless", | |
collection: @filteredIncidents, | |
filter: @filter, | |
}) | |
@datenavLayout.offhours.show(@toggle_offhours) | |
@datenavLayout.daytime.show(@toggle_daytime) | |
@datenavLayout.minor.show(@toggle_minor) | |
@datenavLayout.alertless.show(@toggle_alertless) | |
@dashboardLayout.incidents.show(@loadingView) | |
# @incidents.fetch({reset: true}) | |
# autofetch = => | |
# log.debug("Autofetching at #{new Date()}...") | |
# @incidents.fetch() | |
# window.setInterval(autofetch, 30*1000) | |
) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment