NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name MTurk Status Page Chart // @namespace localhost // @author ThirdClassInternationalMasterTurker // @description Adds some, hopefully slightly useful, eyecandy to your status page // @include https://www.mturk.com/mturk/status // @version 1.6 // @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js // @require http://code.highcharts.com/highcharts.js // @grant GM_getValue // @grant GM_setValue // ==/UserScript== // // 2012-09-07 First public release by ThirdClassInternationalMasterTurker // // 2012-09-09 Version 1.1 Minor fix to make it work on both pages (when over 30 days) // 1.2 Another fix, @included too many pages // // 2012-09-14 Version 1.3 Added support for time tracking statistics with MTurk Time Tracker script // // 2012-09-19 Version 1.4 You can modify work time by clicking it on status page // // 2012-12-02 Version 1.5: Added @downloadURL and @updateURL // // 2012-12-12 Version 1.6: Added options dialog // // --- DEFAULT SETTINGS ------------------------------------------------ // // Set your own target earnings here var EARNINGS_OK = 10; var EARNINGS_HIGH = 20; // Change background colour or font colour (true/false) var COLOUR_ROWS = true; // Apply colours to rows with pending hits (true/false) var COLOUR_PENDING_ROWS = false; // Make quick links for rejected and pending hits (true/false) var LINK_REJECTED = true; var LINK_PENDINGS = true; // Background colours (no effect if COLOUR_ROW is false) var COLOUR_HIGH_ODD = '#44DD44'; var COLOUR_HIGH_EVEN = '#88EE88'; var COLOUR_OK_ODD = '#f1f3eb'; var COLOUR_OK_EVEN = '#FFFFFF'; var COLOUR_LOW_ODD = '#FF5555'; var COLOUR_LOW_EVEN = '#FF8888'; var COLOUR_PENDING_ODD = '#FFFF66'; var COLOUR_PENDING_EVEN = '#FFFFAA'; // Font colours (no effect if COLOUR_ROW is true) var COLOUR_PENDING = '#FFFF66'; var COLOUR_REJECTED = '#FF0000'; var COLOUR_APPROVED = 'green'; var COLOUR_EARNINGS = 'orange'; var COLOUR_HOURLY_RATE = 'black'; var COLOUR_LOW = '#FF0000'; var COLOUR_OK = '#000000'; var COLOUR_HIGH = '#008000'; // Set this false if you don't want borders around rejects // '2px solid red' adds red borders var BORDER_STYLE_REJECTED = false; //'2px solid red'; var DRAW_BAR_CHART = true; // -------------------------------------------------------------------- // var ROWS = []; if (typeof GM_getValue !== 'undefined') { var CONFIG_DIALOG = null; if (GM_getValue('EARNINGS_OK') !== undefined) EARNINGS_OK = GM_getValue('EARNINGS_OK'); if (GM_getValue('EARNINGS_HIGH') !== undefined) EARNINGS_HIGH = GM_getValue('EARNINGS_HIGH'); if (GM_getValue('DRAW_BAR_CHART') !== undefined) DRAW_BAR_CHART = GM_getValue('DRAW_BAR_CHART'); function config_dialog_close_func(save, inputs) { return function() { if (save == false) { inputs[0].value = EARNINGS_OK; inputs[1].value = EARNINGS_HIGH; if (DRAW_BAR_CHART == true) inputs[2].checked = true; } else { var error = false; try { EARNINGS_OK = parseInt(inputs[0].value); EARNINGS_HIGH = parseInt(inputs[1].value); DRAW_BAR_CHART = inputs[2].checked == true; GM_setValue('EARNINGS_OK', EARNINGS_OK); GM_setValue('EARNINGS_HIGH', EARNINGS_HIGH); GM_setValue('DRAW_BAR_CHART', DRAW_BAR_CHART); if (EARNINGS_HIGH <= EARNINGS_OK) error = true; } catch(err) { error = true; } if (error) return; for (var i=0; i<ROWS.length; i++) { var row = ROWS[i]; if (row.pending == 0 && row.earnings < EARNINGS_OK) row.element.style.backgroundColor = (i%2 == 1) ? COLOUR_LOW_ODD : COLOUR_LOW_EVEN; else if (row.pending == 0 && row.earnings > EARNINGS_HIGH) row.element.style.backgroundColor = (i%2 == 1) ? COLOUR_HIGH_ODD : COLOUR_HIGH_EVEN; else if (row.pending == 0) row.element.style.backgroundColor = (i%2 == 1) ? COLOUR_OK_ODD : COLOUR_OK_EVEN; } } CONFIG_DIALOG.style.display = 'none'; }; } function config_dialog() { if (CONFIG_DIALOG == null) { CONFIG_DIALOG = document.createElement('div'); CONFIG_DIALOG.style.display = 'block'; CONFIG_DIALOG.style.position = 'fixed'; CONFIG_DIALOG.style.width = '500px'; CONFIG_DIALOG.style.height = '100px'; CONFIG_DIALOG.style.left = '50%'; CONFIG_DIALOG.style.right = '50%'; CONFIG_DIALOG.style.margin = '-250px auto auto -250px'; CONFIG_DIALOG.style.top = '400'; CONFIG_DIALOG.style.padding = '10px'; CONFIG_DIALOG.style.border = '2px'; CONFIG_DIALOG.style.textAlign = 'center'; CONFIG_DIALOG.style.verticalAlign = 'middle'; CONFIG_DIALOG.style.borderStyle = 'solid'; CONFIG_DIALOG.style.borderColor = 'black'; CONFIG_DIALOG.style.backgroundColor = 'white'; CONFIG_DIALOG.style.color = 'black'; CONFIG_DIALOG.style.zIndex = '100'; var inputs = []; inputs[0] = document.createElement('input'); inputs[1] = document.createElement('input'); inputs[2] = document.createElement('input'); var label0 = document.createElement('label'); var label1 = document.createElement('label'); var label2 = document.createElement('label'); label0.textContent = 'Earnings OK: $'; label1.textContent = 'Earnings High: $'; label2.textContent = 'Draw Bar Chart: '; inputs[0].maxLength = '3'; inputs[0].size = '3'; inputs[0].defaultValue = EARNINGS_OK; inputs[1].maxLength = '3'; inputs[1].size = '3'; inputs[1].defaultValue = EARNINGS_HIGH; inputs[2].setAttribute('type', 'checkbox'); if (DRAW_BAR_CHART == true) inputs[2].checked = true; else inputs[2].checked = false; label0.title = inputs[0].title = 'If earnings for a day is less than this, day is coloured red'; label1.title = inputs[1].title = 'If earnings for a day is more than this, day is coloured green'; label2.title = inputs[2].title = 'Draw Bar Chart at the bottom of the page'; var save_button = document.createElement('button'); save_button.textContent = 'Save'; save_button.addEventListener("click", config_dialog_close_func(true, inputs), false); save_button.style.margin = '5px'; var cancel_button = document.createElement('button'); cancel_button.textContent = 'Cancel'; cancel_button.addEventListener("click", config_dialog_close_func(false, inputs), false); cancel_button.style.margin = '5px'; CONFIG_DIALOG.appendChild(label0); CONFIG_DIALOG.appendChild(inputs[0]); CONFIG_DIALOG.appendChild(document.createElement('br')); CONFIG_DIALOG.appendChild(label1); CONFIG_DIALOG.appendChild(inputs[1]); CONFIG_DIALOG.appendChild(document.createElement('br')); CONFIG_DIALOG.appendChild(label2); CONFIG_DIALOG.appendChild(inputs[2]); CONFIG_DIALOG.appendChild(document.createElement('br')); CONFIG_DIALOG.appendChild(cancel_button); CONFIG_DIALOG.appendChild(save_button); document.body.appendChild(CONFIG_DIALOG); } else { CONFIG_DIALOG.style.display = 'block'; } } function config_func() { return function() { config_dialog(); }; } var config_button = document.createElement('button'); var title = document.getElementsByClassName('IKnowYou')[0]; config_button.textContent = 'Status Page Options'; config_button.addEventListener("click", config_func(), false); title.appendChild(config_button); } /* ----------------------------------------------------------------------------------- */ function formatTime(msec) { if (isNaN(msec)) return "-"; var seconds = Math.floor(msec / 1000) % 60; var minutes = Math.floor((msec / 1000) / 60) % 60; var hours = Math.floor(((msec / 1000) / 60) / 60) % 24; if (hours > 0) seconds = ""; else seconds = "" + seconds + "s"; minutes == 0 ? minutes = "" : minutes = "" + minutes + "m "; hours == 0 ? hours = "" : hours = "" + hours + "h "; return hours + minutes + seconds; } function formatHourlyRate(dollars, msec, formatForTable) { if (dollars == 0 || isNaN(msec)) { if (formatForTable) return "-"; return 0; } var rate = (dollars/(msec/1000/60/60)); if (formatForTable) return "$" + rate.toFixed(2); return rate; } function change_time(date) { return function() { oldTime = localStorage[date]; newTime = prompt('Time worked on ' + date + ' (H:MM)\nInsert - to empty time\nReload page to see all changes', ""); if (newTime != null && newTime != "") { if (newTime.match("^-$")) { localStorage.removeItem(date); document.getElementById(date).innerHTML = "-";; } else if (!newTime.match('[0-2]?[0-9]:[0-5][0-9]')) { alert("Invalid time!"); } else { var t = newTime.split(':'); hours = parseInt(t[0]); minutes = parseInt(t[1]); newTime = ((hours*60+minutes)*60000); if (newTime < 0 || newTime >= 86400000) alert("Invalid time!"); else { localStorage[date] = newTime; document.getElementById(date).innerHTML = formatTime(newTime); } } } } } function clear_localstorage() { if (confirm("Press OK to clear all data from Local Storage!")) localStorage.clear(); }; function clear_cache_items() { if (confirm("Press OK to clear Time Tracker Cache items from Local Storage!")) { for (i=0; i<=localStorage.length-1; i++) { key = localStorage.key(i); if (key.match('STATUS_')) localStorage.removeItem(key); } } }; (function() { var rows = document.evaluate('//tr[@class]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); var container = document.createElement('div'); var chart; var options = { chart: { renderTo: 'container', margin: [100,100,100,100], zoomType: 'xy' }, title: { text: '' }, xAxis: [{ categories: [], labels: { rotation: 90, align: 'left' }, }], yAxis: [{ // Primary yAxis minorTickInterval: 'auto', gridLineWidth: 2, max: null, min: 0, labels: { formatter: function() { return this.value +' HITs'; }, }, title: { text: 'HITs', } }, { // Secondary yAxis minorTickInterval: 'auto', gridLineWidth: 2, allowDecimals: true, max: null, min: 0, minRange: 10, title: { text: 'Earnings', }, labels: { formatter: function() { return '\$' + this.value; }, }, opposite: true }, { minorTickInterval: 'auto', gridLineWidth: 2, allowDecimals: true, max: null, min: 0, minRange: 10, title: { text: 'Hourly Rate', }, labels: { formatter: function() { return '\$' + this.value + '/h'; }, }, opposite: true }], plotOptions: { series: { stacking: 'normal' }, column: { pointPadding: 0, groupPadding: 0.05, } }, series: [{ name: 'pending', color: COLOUR_PENDING, type: 'column', yAxis: 0, data: [] }, { name: 'rejected', color: COLOUR_REJECTED, type: 'column', yAxis: 0, data: [] }, { name: 'approved', color: COLOUR_APPROVED, type: 'column', yAxis: 0, data: [] }, { name: 'earnings', color: COLOUR_EARNINGS, type: 'spline', yAxis: 1, stack: 1, data: [] }, { name: '$/h', color: COLOUR_HOURLY_RATE, type: 'spline', yAxis: 2, stack: 2, data: [] }] }; container.id = 'container'; container.height = 400; container.width = '800'; document.body.appendChild(container); var data =[]; var data2 =[]; var data3 =[]; var data4 =[]; var data5 =[]; var data6 =[]; for (var i=0;i<rows.snapshotLength;i++) { var row = rows.snapshotItem(i); if (row.cells.length != 6) continue; if (row.className.match('grayHead')) { // extra columns only if using time tracker script if (localStorage["LOG START"] != undefined) row.innerHTML += "<th>Time</th><th>$/h</th>"; continue; } if (row.className.match('odd|even') == null) { continue; } var odd = row.className.match('odd'); var approved = parseInt(row.cells[2].innerHTML); var rejected = parseInt(row.cells[3].innerHTML); var pending = parseInt(row.cells[4].innerHTML); var earnings = row.cells[5].childNodes[0].innerHTML; var dollars = parseFloat(earnings.slice(earnings.search('\\$')+1)); var date = row.cells[0].childNodes[1].href.substr(53); ROWS.push({element: row, earnings: dollars, pending: pending}); data.unshift(pending); data2.unshift(rejected); data3.unshift(approved); data4.unshift(dollars); data5.unshift(row.cells[0].textContent.replace(/, 20../, "")); if (pending > 0) { row.cells[4].style.color = COLOUR_PENDING; } if (parseInt(row.cells[3].innerHTML) > 0) { row.cells[3].style.color = COLOUR_REJECTED; } if (COLOUR_ROWS) { if (pending != 0 && !COLOUR_PENDING_ROWS) { if (odd) row.style.backgroundColor = COLOUR_PENDING_ODD; else row.style.backgroundColor = COLOUR_PENDING_EVEN; } if ((pending == 0 || COLOUR_PENDING_ROWS) && dollars >= EARNINGS_HIGH) { if (odd) row.style.backgroundColor = COLOUR_HIGH_ODD; else row.style.backgroundColor = COLOUR_HIGH_EVEN; } else if ((pending == 0 || COLOUR_PENDING_ROWS) && dollars < EARNINGS_OK) { if (odd) row.style.backgroundColor = COLOUR_LOW_ODD; else row.style.backgroundColor = COLOUR_LOW_EVEN; } else if (pending == 0 || COLOUR_PENDING_ROWS) { if (odd) row.style.backgroundColor = COLOUR_OK_ODD; else row.style.backgroundColor = COLOUR_OK_EVEN; } } else { if ((pending == 0 || COLOUR_PENDING_ROWS) && dollars < EARNINGS_OK) { row.cells[5].style.color = COLOUR_LOW; } else if ((pending == 0 || COLOUR_PENDING_ROWS) && dollars >= EARNINGS_HIGH) { row.cells[5].style.color = COLOUR_HIGH; } else if (pending == 0 || COLOUR_PENDING_ROWS) { row.cells[5].style.color = COLOUR_OK; } } if (LINK_REJECTED && rejected > 0) { row.cells[3].innerHTML = '<a href="' + row.cells[0].childNodes[1].href + '&sortType=Rejected">' + rejected + '</a>'; } if (LINK_PENDINGS && pending > 0) { row.cells[4].innerHTML = '<a href="' + row.cells[0].childNodes[1].href + '&sortType=Pending">' + pending + '</a>'; } if (BORDER_STYLE_REJECTED && rejected > 0) { row.cells[3].style.border = BORDER_STYLE_REJECTED; } // extra columns only if using time tracker script if (localStorage["LOG START"] != undefined) { row.innerHTML += "<td style=\"text-align:right\" class=\"timeWorked\" id=\"" + date + "\">" + formatTime(parseInt(localStorage[date])) + "</td>"; row.innerHTML += "<td style=\"text-align:right\">" + formatHourlyRate(dollars, parseInt(localStorage[date]), true) + "</td>"; data6.unshift(formatHourlyRate(dollars, parseInt(localStorage[date]), false)); } } /* ----------------------------------------------------------------------------------- */ options.series[0].data = data; options.series[1].data = data2; options.series[2].data = data3; options.series[3].data = data4; options.series[4].data = data6; options.xAxis[0].categories = data5; if (DRAW_BAR_CHART == true) { $(document).ready(function() { chart = new Highcharts.Chart(options); }); } })(); var clear_storage_button = document.createElement('button'); var clear_cache_button = document.createElement('button'); clear_storage_button.textContent = 'Clear Local Storage'; clear_cache_button.textContent = 'Clear Time Tracker Cache'; document.body.appendChild(document.createElement('hr')); document.body.appendChild(clear_storage_button); document.body.appendChild(clear_cache_button); clear_storage_button.addEventListener("click", clear_localstorage, false); clear_cache_button.addEventListener("click", clear_cache_items, false); if (localStorage["LOG START"] != undefined) { var tds = document.getElementsByClassName('timeWorked'); for (var i=0; i<tds.length; i++) { var a = tds[i].id; tds[i].addEventListener("click", change_time(tds[i].id), false); } }