NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Add Salesforce Debug Log Buttons // @namespace SFDC // @version 0.3.5 // @description Add Salesforce Debug Log Buttons // @author motiko // @match https://*.salesforce.com/setup/ui/listApexTraces.apexp* // @grant GM_xmlhttpRequest // @run-at document-end // ==/UserScript== /*eslint new-cap:0 no-underscore-dangle:0*/ /*global GM_xmlhttpRequest*/ (function(){ var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var userId; var sid = document.cookie.match(/(^|;\s*)sid=(.+?);/)[2]; const LOGS_TABLE_ID = 'Apex_Trace_List:traceForm:traceTable:thetracetable:tb'; const LOGS_TABLE_ID_ESCAPED = LOGS_TABLE_ID.replace(/:/g, '\\:'); const USERS_TABLE_ID = 'Apex_Trace_List:monitoredUsersForm'; var showLogsNum = 50; var tableElement = document.getElementById(LOGS_TABLE_ID); function inject(fn) { var script = document.createElement('script'); script.setAttribute("type", "application/javascript"); script.textContent = '(' + fn + ')();'; document.body.appendChild(script); // run the script document.body.removeChild(script); // clean up } function initPage(){ getUserId(); removeOldDeleteBtn(); addDeleteAllBtn(); addAddUserBtn(); addReloadControllers(); addSearchControllers(); } initPage(); function removeOldDeleteBtn(){ var oldButtons = document.querySelectorAll('[value="Delete All"]'); toArray(oldButtons).forEach(function(button){ button.parentNode.removeChild(button); }); } function getUserId(){ window.addEventListener("message", function(event) { if(event.data.type === "userId"){ userId = event.data.content; } }); function sendBackUserId(){ if(window.UserContext){ window.postMessage({type: `userId`, content: UserContext.userId}, "*"); } } inject(sendBackUserId); } function addAddUserBtn(){ var pbButton = document.getElementById(USERS_TABLE_ID).querySelector('.pbButton'); var addUserButton = document.createElement('input'); addUserButton.type = 'button'; addUserButton.className = 'btn'; addUserButton.value = 'Add Current User'; addUserButton.onclick = addCurrentUser; pbButton.appendChild(addUserButton); } function addReloadControllers(){ //loadLogs() document.getElementById('Apex_Trace_List:traceForm:traceTableNextPrev') .style.display = 'none'; document.getElementById('Apex_Trace_List:traceForm:traceTable') .querySelector('.mainTitle').style.display = 'none'; // var autoReloadLabel = document.createElement('label'); // autoReloadLabel.style.float = 'right'; // autoReloadLabel.style.paddingLeft = '5px'; // autoReloadLabel.innerHTML = '<input type="checkbox" name="checkbox"' + // 'id="autoReload" />Auto Reload'; // autoReloadLabel.firstElementChild.onchange = function(){ // if(this.checked){ // reloadIntervalId = setInterval(loadNewLogs, 5000); // }else{ // clearInterval(reloadIntervalId); // } // }; var numOfLogsLabel = document.createElement('label'); numOfLogsLabel.style.float = 'right'; numOfLogsLabel.innerHTML = `Maximum logs per Page: ` + `<input type="number" min="25" max="1000" step="25" value="${showLogsNum}" >`; numOfLogsLabel.firstElementChild.onchange = function(){ showLogsNum = this.value; loadLogs(); }; // document.getElementById("Apex_Trace_List:traceForm") // .querySelector('.pbButton').appendChild(autoReloadLabel); document.getElementById("Apex_Trace_List:traceForm") .querySelector('.pbButton').appendChild(numOfLogsLabel); } function addDeleteAllBtn(){ var deleteAllContainer = document.getElementById("Apex_Trace_List:traceForm") .querySelector('.pbButton'); var realDeleteAllBtn = document.createElement('input'); realDeleteAllBtn.type = 'button'; realDeleteAllBtn.className = 'btn'; realDeleteAllBtn.value = 'Delete All'; realDeleteAllBtn.onclick = realDeleteAll; deleteAllContainer.appendChild(realDeleteAllBtn); var loadNewLogsBtn = document.createElement('input'); loadNewLogsBtn.type = 'button'; loadNewLogsBtn.className = 'btn'; loadNewLogsBtn.value = 'Load New Logs'; loadNewLogsBtn.onclick = loadNewLogs; deleteAllContainer.appendChild(loadNewLogsBtn); } function realDeleteAll(event){ event.preventDefault(); document.body.style.cursor = 'wait'; request('/services/data/v32.0/tooling/query/?q=' + encodeURIComponent('Select Id From ApexLog')) .then(function(result){ var reponseObjects = JSON.parse(result); var logIdsCsv = reponseObjects.records.map(function(logObj){ return `"${logObj.Id}"`; }).reduce(function(sum, id){ return sum + '\n' + id; }, '"Id"'); console.log(logIdsCsv); return createJob('ApexLog', 'delete').then(function(jobId){ console.log('jobId: ' + jobId); createBatch(jobId, logIdsCsv).then(function(batchId){ console.log('batchId: ' + batchId); pollBatchStatus(jobId, batchId).then(function(){ location.reload(); }); }); }); }); } // var logsCounter = logIds.length; // if(logsCounter > 1000){ // if(!confirm('This willl consume more than 1000 API calls. Proceed ?')) return; // } // Promise.all(logIds.map(function(id){ // return request('/services/data/v32.0/tooling/sobjects/ApexLog/' + id, 'DELETE'); // })).then(function(){ // document.body.style.cursor = 'deafult'; // window.location.href = window.location.href; // }); // }); function loadedLogIds(){ var trs = toArray(document.getElementById(LOGS_TABLE_ID).children); return trs.map(function(tr){ var td = tr.firstElementChild; var a = td.firstElementChild; var params = a.href.split('='); var id = params.pop(); return id; }); } function getMonitoredUsers(){ return toArray(document.querySelectorAll('th[scope="row"]')).map(function(th){ var href = th.firstElementChild.href; return "'" + href.substr(href.lastIndexOf('/') + 1) + "'"; }).join(','); } function loadNewLogs(){ var oldLogIds = loadedLogIds(); requestLogs().then(function(logs){ var deltaLogs = logs.filter(function(log){ return oldLogIds.indexOf(log.Id) === -1; }); var deltaLogTrs = deltaLogs.map(logRecordToTr); deltaLogTrs.forEach(addToTable); animateTrsAddition(deltaLogTrs); removeOldLogs(); }); } function removeOldLogs(){ var oldTrs = toArray(document.querySelectorAll(`#${LOGS_TABLE_ID_ESCAPED} tr:nth-child(n+${parseInt(showLogsNum, 10) + 1})`)); animateTrsRemoval(oldTrs); setTimeout(function(){ [].map.call(oldTrs, function(tr){ try{ tableElement.removeChild(tr); }catch(e){ console.log('race?'); } }); }, 1000); } function loadLogs(event){ if(event){ event.preventDefault(); } clearTable(); return requestLogs().then(function(logs){ console.log(1); logs.map(logRecordToTr).forEach(addToTable); }); } function addToTable(tr){ if(tableElement.firstChild){ tableElement.insertBefore(tr, tableElement.firstChild); }else{ tableElement.appendChild(tr); } } function requestLogs(){ var monitoredUsers = getMonitoredUsers(); var selectQuery = [`SELECT LogUser.Name,Application,DurationMilliseconds,`, `Id,LastModifiedDate,Location,LogLength,LogUserId,`, `Operation,Request,StartTime,Status,SystemModstamp From `, `ApexLog Where LogUserId in (${monitoredUsers}) ORDER BY `, `LastModifiedDate ASC LIMIT ${showLogsNum}`].join(''); return request('/services/data/v32.0/tooling/query/?q=' + encodeURIComponent(selectQuery)) .then(function(rawResult){ return JSON.parse(rawResult).records; }).catch(function(err){ console.log(err); }); } function animateTrsRemoval(trs){ trs.forEach(function(tr){ [].map.call(tr.children, function(td){ td.className = ''; td.style.padding = '0px'; }); tr.style.fontSize = '0px'; tr.style.padding = '0px !important'; tr.style.height = '0px'; }); } function animateTrsAddition(trs){ prepareTransition(trs); setTimeout(function makeTransition(){ trs.forEach(function(tr){ tr.style.height = '23px'; }); }, 0); setTimeout(function finishTransition(){ trs.forEach(function(tr){ tr.style.fontSize = '12px'; [].map.call(tr.children, function(td, i){ td.className = i === 0 ? 'dataCell actionColumn' : 'dataCell'; td.style.padding = ''; }); }); }, 1000); } function prepareTransition(logTrs){ logTrs.forEach(function(tr){ tr.style.fontSize = '0px'; tr.style.padding = '0px !important'; tr.style.height = '0px'; [].map.call(tr.children, function(td){ td.className = ''; td.style.padding = '0px'; }); }); } function logRecordToTr(logObj){ var tr = document.createElement('tr'); tr.style.webkitTransition = 'all 500ms'; tr.style.mozTransition = 'all 500ms'; tr.style.transition = 'all 500ms'; tr.style.height = '23px'; tr.onfocus = "if (window.hiOn){hiOn(this);}"; tr.dataset.id = logObj.Id; var startTime = new Date(Date.parse(logObj.StartTime)).toLocaleString(); tr.innerHTML = `<td class="dataCell actionColumn" colspan="1"> <a href="/p/setup/layout/ApexDebugLogDetailEdit/d?apex_log_id=${logObj.Id}" class="actionLink">View</a> | <a href="/servlet/servlet.FileDownload?file=${logObj.Id}" class="actionLink">Download</a> | <a class="actionLink deleteActionLink" href="#" >Delete</a></td> <td class="dataCell" colspan="1"><a href="/{logObj.LogUserId}" class="actionLink">${logObj.LogUser.Name}</a></td> <td class="dataCell" colspan="1">${logObj.Request}</td> <td class="dataCell" colspan="1">${logObj.Application}</td> <td class="dataCell" colspan="1">${logObj.Operation}</td> <td class="dataCell" colspan="1">${logObj.Status}</td> <td class="dataCell" colspan="1">${logObj.DurationMilliseconds}</td> <td class="dataCell" colspan="1">${logObj.LogLength}</td> <td class="dataCell" colspan="1">${startTime}</td>`; return tr; } function clearTable(){ document.getElementById(LOGS_TABLE_ID).innerHTML = ''; } function addCurrentUser(event){ if(event){ event.preventDefault(); } document.location.assign("/setup/ui/listApexTraces.apexp?user_id=" + userId + "&user_logging=true"); } function addSearchControllers(){ var iframe = document.createElement('iframe'); iframe.id = 'remember'; iframe.name = 'remember'; iframe.style.display = 'none'; iframe.src = 'about:blank'; document.body.appendChild(iframe); var form = document.createElement('form'); form.method = 'post'; form.target = 'remember'; form.action = '/'; form.onsubmit = searchLogs; var input = document.createElement('input'); input.type = 'text'; input.id = 'FilterByText'; input.autocomplete = 'on'; input.placeholder = 'Search logs..'; input.onkeydown = handleSearchKey; var filter = document.createElement('button'); filter.textContent = 'Search'; filter.type = 'submit'; var clearFilterBtn = document.createElement('button'); clearFilterBtn.textContent = 'Clear'; clearFilterBtn.onclick = clearFilter; var loadingImage = document.createElement('img'); loadingImage.src = '/img/loading.gif'; loadingImage.style.display = 'none'; loadingImage.id = 'LoadinImage'; form.appendChild(input); form.appendChild(filter); form.appendChild(clearFilterBtn); form.appendChild(loadingImage); var logsTitle = document.querySelector('.apexp .pbTitle'); logsTitle.appendChild(form); } function handleSearchKey(e){ if(e.keyCode === 27){ clearFilter(e); } } function clearFilter(e){ e.preventDefault(); document.getElementById('FilterByText').value = ''; resetResults(); } function resetResults(){ toArray(document.getElementById(LOGS_TABLE_ID).children) .forEach(function(element){ element.style.background = 'white'; }); } function toArray(nodeElements){ return [].slice.call(nodeElements); } function searchLogs(){ resetResults(); document.body.style.cursor = 'wait'; document.getElementById('LoadinImage').style.display = 'inline'; var searchText = document.getElementById('FilterByText').value; var searchRegex = new RegExp(escapeRegExp(searchText), 'gi'); var logTableRows = toArray(document.getElementById(LOGS_TABLE_ID).children); var visibleLogRows = logTableRows.map(function(row){ if(!row.querySelector('td>a')){ return null; } // consider for perfomance: row.children[0].children[0] var link = row.querySelector('td>a').href; logIdParam = link.split('?')[1].split('&').filter(function(keyVal){ return keyVal.indexOf('apex_log_id=') === 0; }); logIdParam = logIdParam[0]; return { element: row, id: logIdParam.split('=')[1]}; }).filter(function(e){return e; }); var promises = visibleLogRows.map(function(logRow){ return request('/services/data/v32.0/tooling/sobjects/ApexLog/' + logRow.id + '/Body') .then(function(rawLogContents){ if(searchRegex.test(rawLogContents)){ logRow.element.style.background = 'rgb(104, 170, 87)'; return true; } return false; }).catch(function(err){ console.log(err); document.body.style.cursor = 'default'; document.getElementById('LoadinImage').style.display = 'none'; }); }); Promise.all(promises).then(function(){ document.body.style.cursor = 'default'; document.getElementById('LoadinImage').style.display = 'none'; }); } function createJob(objectName, operation){ var queryJob = `<?xml version="1.0" encoding="UTF-8"?> <jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload"> <operation>${operation}</operation> <object>${objectName}</object> <concurrencyMode>Parallel</concurrencyMode> <contentType>CSV</contentType> </jobInfo>`; console.log(queryJob); return bulkRequest('/services/async/34.0/job', 'POST', {'Content-Type': 'application/xml'}, queryJob ).then(function(response){ return response.match(/<id>(.*)<\/id>/)[1]; }); } function pollBatchStatus(jobId, batchId){ return new Promise(function(resolve, reject){ var intervalId = setInterval(function(){ checkBatchStatus(jobId, batchId).then(function(state){ console.log(state); if(state === "Completed"){ resolve(state); clearInterval(intervalId); } if(state === "Error" || state === "Not Processed"){ reject(state); clearInterval(intervalId); } }); }, 1000); }); } function checkBatchStatus(jobId, batchId){ console.log(`/services/async/34.0/job/${jobId}/batch/${batchId}`); return bulkRequest(`/services/async/34.0/job/${jobId}/batch/${batchId}`) .then(function(resultXml){ return resultXml.match(/<state>(.*)<\/state>/)[1]; }); } function createBatch(jobId, csv){ return bulkRequest(`/services/async/34.0/job/${jobId}/batch`, 'POST', {'Content-Type': 'text/csv; charset=UTF-8'}, csv ).then(function(response){ return response.match(/<id>(.*)<\/id>/)[1]; }); } function bulkRequest(url, method, headers, body){ method = method || 'GET'; if(typeof GM_xmlhttpRequest === "function"){ return new Promise(function(fulfill, reject){ GM_xmlhttpRequest({ method: method, url: url, headers: _extends({}, headers, { 'X-SFDC-Session': sid }), data: body, onload: function(response){ if( response.status.toString().indexOf('2') === 0){ fulfill(response.response); }else{ reject(Error(response.statusText)); } }, onerror: function(){ reject(Error("Network Error")); } }); }); } return new Promise(function(fulfill, reject){ var xhr = new XMLHttpRequest(); xhr.open(method, url); xhr.onload = function(){ if( xhr.status.toString().indexOf('2') === 0){ fulfill(xhr.response); }else{ reject(Error(xhr.statusText)); } }; xhr.onerror = function(){ reject(Error("Network Error")); }; xhr.setRequestHeader('X-SFDC-Session', sid); for(var header in headers){ xhr.setRequestHeader(header, headers[header]); } xhr.send(body); }); } function request(url, method){ method = method || 'GET'; if(typeof GM_xmlhttpRequest === "function"){ return new Promise(function(fulfill, reject){ GM_xmlhttpRequest({ method: method, url: url, headers: { Authorization: 'Bearer ' + sid, Accept: '*/*' }, onload: function(response){ if( response.status.toString().indexOf('2') === 0){ fulfill(response.response); }else{ reject(Error(response.statusText)); } }, onerror: function(){ reject(Error("Network Error")); } }); }); } return new Promise(function(fulfill, reject){ var xhr = new XMLHttpRequest(); xhr.open(method, url); xhr.onload = function(){ if( xhr.status.toString().indexOf('2') === 0){ fulfill(xhr.response); }else{ reject(Error(xhr.statusText)); } }; xhr.onerror = function(){ reject(Error("Network Error")); }; xhr.setRequestHeader('Authorization', 'Bearer ' + sid); xhr.send(); }); } function escapeRegExp(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); } })();