|
| 1 | +function ConvertTo-DbaTimeline { |
| 2 | + <# |
| 3 | + .SYNOPSIS |
| 4 | + Converts InputObject to a html timeline using Google Chart |
| 5 | + |
| 6 | + .DESCRIPTION |
| 7 | + This function accepts input as pipeline from the following psdbatools functions: |
| 8 | + Get-DbaAgentJobHistory |
| 9 | + Get-DbaBackupHistory |
| 10 | + (more to come...) |
| 11 | + And generates Bootstrap based, HTML file with Google Chart Timeline |
| 12 | + |
| 13 | + .PARAMETER InputObject |
| 14 | + |
| 15 | + Pipe input, must an output from the above functions. |
| 16 | + |
| 17 | + .PARAMETER EnableException |
| 18 | + By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. |
| 19 | + This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. |
| 20 | + Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. |
| 21 | + |
| 22 | + .NOTES |
| 23 | + Tags: Chart |
| 24 | + Author: Marcin Gminski (@marcingminski) |
| 25 | + Dependency: ConvertTo-JsDate, Convert-DbaTimelineStatusColor |
| 26 | + |
| 27 | + Website: https://dbatools.io |
| 28 | + Copyright: (C) Chrissy LeMaire, clemaire@gmail.com |
| 29 | +- License: MIT https://opensource.org/licenses/MIT |
| 30 | + |
| 31 | + .LINK |
| 32 | + https://dbatools.io/ConvertTo-DbaTimeline |
| 33 | + |
| 34 | + .EXAMPLE |
| 35 | + Get-DbaAgentJobHistory -SqlInstance sql-1 -StartDate '2018年08月13日 00:00' -EndDate '2018年08月13日 23:59' -NoJobSteps | ConvertTo-DbaTimeline | Out-File C:\temp\DbaAgentJobHistory.html -Encoding ASCII |
| 36 | + |
| 37 | + Creates an output file containing a pretty timeline for all of the agent job history results for sql-1 the whole day of 2018年08月13日 |
| 38 | + |
| 39 | + .EXAMPLE |
| 40 | + Get-DbaRegisteredServer -SqlInstance sqlcm | Get-DbaBackupHistory -Since '2018年08月13日 00:00' | ConvertTo-DbaTimeline | Out-File C:\temp\DbaBackupHistory.html -Encoding ASCII |
| 41 | + |
| 42 | + Creates an output file containing a pretty timeline for the agent job history since 2018年08月13日 for all of the registered servers on sqlcm |
| 43 | + |
| 44 | + .EXAMPLE |
| 45 | + $messageParameters = @{ |
| 46 | + Subject = "Backup history for sql2017 and sql2016" |
| 47 | + Body = Get-DbaBackupHistory -SqlInstance sql2017, sql2016 -Since '2018年08月13日 00:00' | ConvertTo-DbaTimeline |
| 48 | + From = "dba@ad.local" |
| 49 | + To = "dba@ad.local" |
| 50 | + SmtpServer = "smtp.ad.local" |
| 51 | + } |
| 52 | + Send-MailMessage @messageParameters -BodyAsHtml |
| 53 | + |
| 54 | + Sends an email to dba@ad.local with the results of Get-DbaBackupHistory. Note that viewing these reports may not be supported in all email clients. |
| 55 | + #> |
| 56 | + |
| 57 | + [CmdletBinding()] |
| 58 | + Param ( |
| 59 | + [parameter(Mandatory, ValueFromPipeline)] |
| 60 | + [object[]]$InputObject, |
| 61 | + [switch]$EnableException |
| 62 | + ) |
| 63 | + begin { |
| 64 | + $body = $servers = @() |
| 65 | + $begin = @" |
| 66 | +<html> |
| 67 | +<head> |
| 68 | +<!-- Developed by Marcin Gminski, https://marcin.gminski.net, 2018 --> |
| 69 | +<!-- Load jQuery required to autosize timeline --> |
| 70 | +<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> |
| 71 | +<!-- Load Bootstrap --> |
| 72 | +<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> |
| 73 | +<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> |
| 74 | +<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> |
| 75 | +<!-- Load Google Charts library --> |
| 76 | +<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> |
| 77 | +<!-- a bit of custom styling to work with bootstrap grid --> |
| 78 | +<style> |
| 79 | + |
| 80 | + html,body{height:100%;background-color:#c2c2c2;} |
| 81 | + .viewport {height:100%} |
| 82 | + |
| 83 | + .chart{ |
| 84 | + background-color:#fff; |
| 85 | + text-align:left; |
| 86 | + padding:0; |
| 87 | + border:1px solid #7D7D7D; |
| 88 | + -webkit-box-shadow:1px 1px 3px 0 rgba(0,0,0,.45); |
| 89 | + -moz-box-shadow:1px 1px 3px 0 rgba(0,0,0,.45); |
| 90 | + box-shadow:1px 1px 3px 0 rgba(0,0,0,.45) |
| 91 | + } |
| 92 | + .badge-custom{background-color:#939} |
| 93 | + .container { |
| 94 | + height:100%; |
| 95 | + } |
| 96 | + .fill{ |
| 97 | + width:100%; |
| 98 | + height:100%; |
| 99 | + min-height:100%; |
| 100 | + padding:10px; |
| 101 | + } |
| 102 | + .timeline-tooltip{ |
| 103 | + border:1px solid #E0E0E0; |
| 104 | + font-family:Arial,Helvetica; |
| 105 | + font-size:10pt; |
| 106 | + padding:12px |
| 107 | + } |
| 108 | + .timeline-tooltip div{padding:6px} |
| 109 | + .timeline-tooltip span{font-weight:700} |
| 110 | +</style> |
| 111 | + <script type="text/javascript"> |
| 112 | + google.charts.load('43', {'packages':['timeline']}); |
| 113 | + google.charts.setOnLoadCallback(drawChart); |
| 114 | + function drawChart() { |
| 115 | + var container = document.getElementById('Chart'); |
| 116 | + var chart = new google.visualization.Timeline(container); |
| 117 | + var dataTable = new google.visualization.DataTable(); |
| 118 | + dataTable.addColumn({type: 'string', id: 'vLabel'}); |
| 119 | + dataTable.addColumn({type: 'string', id: 'hLabel'}); |
| 120 | + dataTable.addColumn({type: 'string', role: 'style' }); |
| 121 | + dataTable.addColumn({type: 'date', id: 'date_start'}); |
| 122 | + dataTable.addColumn({type: 'date', id: 'date_end'}); |
| 123 | + |
| 124 | + dataTable.addRows([ |
| 125 | +"@ |
| 126 | + } |
| 127 | + |
| 128 | + process { |
| 129 | + # build html container |
| 130 | + $BaseObject = $InputObject.PsObject.BaseObject |
| 131 | + |
| 132 | + # create server list to support multiple servers |
| 133 | + if ($InputObject[0].SqlInstance -notin $servers) { |
| 134 | + $servers += $InputObject[0].SqlInstance |
| 135 | + } |
| 136 | + # This is where do column mapping. |
| 137 | + |
| 138 | + # Check for types - this will help support if someone assigns a variable then pipes |
| 139 | + # AgentJobHistory is a forced type while backuphistory is a legit type |
| 140 | + if ($InputObject[0].TypeName -eq 'AgentJobHistory') { |
| 141 | + $CallerName = "Get-DbaAgentJobHistory" |
| 142 | + $data = $InputObject | Select-Object @{ Name = "SqlInstance"; Expression = { $_.SqlInstance } }, @{ Name = "InstanceName"; Expression = { $_.InstanceName } }, @{ Name = "vLabel"; Expression = { $_.Job } }, @{ Name = "hLabel"; Expression = { $_.Status } }, @{ Name = "Style"; Expression = { $(Convert-DbaTimelineStatusColor($_.Status)) } }, @{ Name = "StartDate"; Expression = { $(ConvertTo-JsDate($_.StartDate)) } }, @{ Name = "EndDate"; Expression = { $(ConvertTo-JsDate($_.EndDate)) } } |
| 143 | + |
| 144 | + } |
| 145 | + elseif ($InputObject[0] -is [Sqlcollaborative.Dbatools.Database.BackupHistory]) { |
| 146 | + $CallerName = "Get-DbaBackupHistory" |
| 147 | + $data = $InputObject | Select-Object @{ Name = "SqlInstance"; Expression = { $_.SqlInstance } }, @{ Name = "InstanceName"; Expression = { $_.InstanceName } }, @{ Name = "vLabel"; Expression = { $_.Database } }, @{ Name = "hLabel"; Expression = { $_.Type } }, @{ Name = "StartDate"; Expression = { $(ConvertTo-JsDate($_.Start)) } }, @{ Name = "EndDate"; Expression = { $(ConvertTo-JsDate($_.End)) } } |
| 148 | + } |
| 149 | + else { |
| 150 | + # sorry to be so formal, can't help it ;) |
| 151 | + Stop-Function -Message "Unsupported input data. To request support for additional commands, please file an issue at dbatools.io/issues and we'll take a look" |
| 152 | + return |
| 153 | + } |
| 154 | + $body += "$($data | ForEach-Object{ "['$($_.vLabel)','$($_.hLabel)','$($_.Style)',$($_.StartDate), $($_.EndDate)]," })" |
| 155 | + } |
| 156 | + end { |
| 157 | + if (Test-FunctionInterrupt) { return } |
| 158 | +$end = @" |
| 159 | +]); |
| 160 | + var paddingHeight = 20; |
| 161 | + var rowHeight = dataTable.getNumberOfRows() * 41; |
| 162 | + var chartHeight = rowHeight + paddingHeight; |
| 163 | + dataTable.insertColumn(2, {type: 'string', role: 'tooltip', p: {html: true}}); |
| 164 | + var dateFormat = new google.visualization.DateFormat({ |
| 165 | + pattern: 'dd/MM/yy HH:mm:ss' |
| 166 | + }); |
| 167 | + for (var i = 0; i < dataTable.getNumberOfRows(); i++) { |
| 168 | + var duration = (dataTable.getValue(i, 5).getTime() - dataTable.getValue(i, 4).getTime()) / 1000; |
| 169 | + var hours = parseInt( duration / 3600 ) % 24; |
| 170 | + var minutes = parseInt( duration / 60 ) % 60; |
| 171 | + var seconds = duration % 60; |
| 172 | + var tooltip = '<div class="timeline-tooltip"><span>' + |
| 173 | + dataTable.getValue(i, 1).split(",").join("<br />") + '</span></div><div class="timeline-tooltip"><span>' + |
| 174 | + dataTable.getValue(i, 0) + '</span>: ' + |
| 175 | + dateFormat.formatValue(dataTable.getValue(i, 4)) + ' - ' + |
| 176 | + dateFormat.formatValue(dataTable.getValue(i, 5)) + '</div>' + |
| 177 | + '<div class="timeline-tooltip"><span>Duration: </span>' + |
| 178 | + hours + 'h ' + minutes + 'm ' + seconds + 's '; |
| 179 | + dataTable.setValue(i, 2, tooltip); |
| 180 | + } |
| 181 | + var options = { |
| 182 | + timeline: { |
| 183 | + rowLabelStyle: { }, |
| 184 | + barLabelStyle: { }, |
| 185 | + }, |
| 186 | + hAxis: { |
| 187 | + format: 'dd/MM HH:mm', |
| 188 | + }, |
| 189 | + } |
| 190 | + // Autosize chart. It would not be enough to just count rows and expand based on row height as there can be overlappig rows. |
| 191 | + // this will draw the chart, get the size of the underlying div and apply that size to the parent container and redraw: |
| 192 | + chart.draw(dataTable, options); |
| 193 | + // get the size of the chold div: |
| 194 | + var realheight= parseInt(`$("#Chart div:first-child div:first-child div:first-child div svg").attr( "height"))+70; |
| 195 | + // set the height: |
| 196 | + options.height=realheight |
| 197 | + // draw again: |
| 198 | + chart.draw(dataTable, options); |
| 199 | + } |
| 200 | +</script> |
| 201 | +</head> |
| 202 | +<body> |
| 203 | + <div class="container-fluid"> |
| 204 | + <div class="pull-left"><h3><code>$($CallerName)</code> timeline for server <code>$($servers -join ', ')</code></h3></div><div class="pull-right text-right"><img class="text-right" style="vertical-align:bottom; margin-top: 10px;" src="https://dbatools.io/wp-content/uploads/2016/05/dbatools-logo-1.png" width=150></div> |
| 205 | + <div class="clearfix"></div> |
| 206 | + <div class="col-12"> |
| 207 | + <div class="chart" id="Chart"></div> |
| 208 | + </div> |
| 209 | + <hr> |
| 210 | + <p><a href="https://dbatools.io">dbatools.io</a> - the community's sql powershell module. Find us on Twitter: <a href="https://twitter.com/psdbatools">@psdbatools</a> | Chart by <a href="https://twitter.com/marcingminski">@marcingminski</a></p> |
| 211 | +</div> |
| 212 | +</body> |
| 213 | +</html> |
| 214 | +"@ |
| 215 | + $begin, $body, $end |
| 216 | + } |
| 217 | +} |
0 commit comments