├── BugValet.safariextension ├── Settings.plist ├── GlobalPage.html ├── Main.js ├── GlobalPage.js ├── FragilePageScraping.js ├── AugmentOpenRadar.js ├── Info.plist └── AugmentAppleBugReporter.js ├── .gitignore ├── PublicUpdates.plist └── README.md /BugValet.safariextension/Settings.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | *.DS_Store 3 | 4 | # Xcode 5 | *.pbxuser 6 | *.mode1v3 7 | *.mode2v3 8 | *.perspectivev3 9 | *.xcuserstate 10 | project.xcworkspace/ 11 | xcuserdata/ 12 | 13 | # Generated files 14 | build/ 15 | *.[oa] 16 | *.pyc 17 | 18 | # Backup files 19 | *~.nib 20 | 21 | # Espresso project 22 | *.esproj -------------------------------------------------------------------------------- /BugValet.safariextension/GlobalPage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Open Radar Helper 6 | 7 | // Put the JS in a separate file so that Safari's debugger can handle it more sanely 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /BugValet.safariextension/Main.js: -------------------------------------------------------------------------------- 1 | 2 | // We only augment pages from bugreport.apple.com and OpenRadar. 3 | if (document.URL.indexOf("bugreport.apple.com") != -1) 4 | { 5 | augmentAppleBugReporter(); 6 | } 7 | else if ((document.URL.indexOf("openradar.me") != -1) || 8 | (document.URL.indexOf("openradar.appspot.com") != -1)) 9 | { 10 | augmentOpenRadar(); 11 | } 12 | -------------------------------------------------------------------------------- /BugValet.safariextension/GlobalPage.js: -------------------------------------------------------------------------------- 1 | function respondToMessage(messageEvent) { 2 | if (messageEvent.name == "saveToLocalStorage") 3 | { 4 | var storageKey = messageEvent.message[0]; 5 | var storageValue = messageEvent.message[1]; 6 | localStorage.setItem(storageKey, JSON.stringify(storageValue)); 7 | } 8 | else if (messageEvent.name == "readFromLocalStorage") 9 | { 10 | var value = localStorage.getItem(messageEvent.message); 11 | var returnedValue = [messageEvent.message, JSON.parse(value)]; 12 | safari.application.activeBrowserWindow.activeTab.page.dispatchMessage("didReadFromLocalStorage", returnedValue); 13 | } 14 | } 15 | 16 | safari.application.addEventListener("message", respondToMessage,false); 17 | -------------------------------------------------------------------------------- /PublicUpdates.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Extension Updates 6 | 7 | 8 | CFBundleIdentifier 9 | com.punkitup.bugvalet 10 | Developer Identifier 11 | 493CVA9A35 12 | CFBundleVersion 13 | 1 14 | CFBundleShortVersionString 15 | 1.0 16 | URL 17 | https://github.com/danielpunkass/BugValet/releases/download/1.0/BugValet1.0.safariextz 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /BugValet.safariextension/FragilePageScraping.js: -------------------------------------------------------------------------------- 1 | // Centralize access to DOM elements by name and ID, so we can raise a big fuss if we're not able to find something 2 | function fragileElementLookupById(theId) 3 | { 4 | var foundElement = document.getElementById(theId); 5 | if (foundElement == undefined) 6 | { 7 | var failureReason = "the element with id \"" + theId + "\" could not be found." 8 | displayFailureAlert(failureReason); 9 | } 10 | 11 | return foundElement; 12 | } 13 | 14 | function fragileElementTextById(theId) 15 | { 16 | var theText = ""; 17 | var theElement = fragileElementLookupById(theId); 18 | if (theElement != undefined) 19 | { 20 | theText = theElement.innerText.trim(); 21 | } 22 | 23 | return theText; 24 | } 25 | 26 | function fragileElementsByClassName(theClassName) 27 | { 28 | var theElements = document.getElementsByClassName(theClassName); 29 | if (theElements.length == 0) 30 | { 31 | var failureReason = "no elements with class name \"" + theClassName + "\" could be found." 32 | displayFailureAlert(failureReason); 33 | } 34 | 35 | return theElements; 36 | } 37 | 38 | function displayFailureAlert(specificFailure) 39 | { 40 | window.alert("Ouch! The OpenRadar Helper extension has lost its mind because Apple's Bug Reporter is no longer designed the way it was when the extension was developed. Please disable the extension and/or update to a newer version.\n\nSpecifically: " + specificFailure); 41 | } -------------------------------------------------------------------------------- /BugValet.safariextension/AugmentOpenRadar.js: -------------------------------------------------------------------------------- 1 | function augmentOpenRadar() 2 | { 3 | // If we're looking at the Open Radar "add" page, then populate it with 4 | // details we stashed in local storage, if we have them. 5 | if (document.URL.indexOf("/myradars/add") != -1) 6 | { 7 | safari.self.tab.dispatchMessage("readFromLocalStorage", "pendingRadarDetails"); 8 | } 9 | } 10 | 11 | function safeString(stringOrBogus) 12 | { 13 | var returnedString = ""; 14 | 15 | if (stringOrBogus != undefined) 16 | { 17 | returnedString = stringOrBogus; 18 | } 19 | 20 | return returnedString; 21 | } 22 | 23 | function openRadarFormFieldByName(fieldName) 24 | { 25 | return document.getElementsByName(fieldName)[0]; 26 | } 27 | 28 | function populateNewRadarFormWithData(radarData) 29 | { 30 | openRadarFormFieldByName("number").value = safeString(radarData.number); 31 | openRadarFormFieldByName("title").value = safeString(radarData.title); 32 | openRadarFormFieldByName("description").value = safeString(radarData.description); 33 | openRadarFormFieldByName("status").value = safeString(radarData.state); 34 | openRadarFormFieldByName("originated").value = safeString(radarData.date); 35 | openRadarFormFieldByName("product").value = safeString(radarData.product); 36 | } 37 | 38 | function getMessage(messageEvent) 39 | { 40 | if (messageEvent.name == "didReadFromLocalStorage") 41 | { 42 | var retrievedDataKey = messageEvent.message[0]; 43 | var retrievedData = messageEvent.message[1]; 44 | 45 | populateNewRadarFormWithData(retrievedData); 46 | } 47 | } 48 | 49 | safari.self.addEventListener("message", getMessage, false); 50 | -------------------------------------------------------------------------------- /BugValet.safariextension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Author 6 | Daniel Jalkut 7 | Builder Version 8 | 10600.5.3.1 9 | CFBundleDisplayName 10 | Bug Valet 11 | CFBundleIdentifier 12 | com.punkitup.bugvalet 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleVersion 18 | 1 19 | Chrome 20 | 21 | Database Quota 22 | 5242880 23 | Global Page 24 | GlobalPage.html 25 | 26 | Content 27 | 28 | Scripts 29 | 30 | End 31 | 32 | AugmentOpenRadar.js 33 | AugmentAppleBugReporter.js 34 | Main.js 35 | FragilePageScraping.js 36 | 37 | 38 | 39 | Description 40 | Easily copy bug information from Apple's Bug Reporter to OpenRadar 41 | DeveloperIdentifier 42 | 493CVA9A35 43 | ExtensionInfoDictionaryVersion 44 | 1.0 45 | Permissions 46 | 47 | Website Access 48 | 49 | Allowed Domains 50 | 51 | openradar.appspot.com 52 | bugreport.apple.com 53 | www.openradar.me 54 | openradar.me 55 | www.bugreport.apple.com 56 | www.openradar.appspot.com 57 | 58 | Include Secure Pages 59 | 60 | Level 61 | Some 62 | 63 | 64 | Update Manifest URL 65 | https://raw.githubusercontent.com/danielpunkass/BugValet/master/PublicUpdates.plist 66 | Website 67 | https://github.com/danielpunkass/BugValet 68 | 69 | 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bug Valet 2 | ========= 3 | 4 | Bug Valet: because copying a bug from Apple's Bug Reporter to Open Radar should be easy. 5 | 6 | Installation 7 | ------------ 8 | 9 | Download and install the latest version of this Safari Extension. Future updates can be automatically installed by Safari's update infrastructure. 10 | 11 | Use 12 | --- 13 | 14 | With the plugin installed, you will find an additional button in the user interface when examining your own (already filed) bugs in Apple's Bug Reporter. Select a bug from your list by clicking on it, then find the button labeled "Send to Open Radar". Click the button to open a new tab to Open Radar, where you will find the "New Radar" form pre-populated with some of the most important information from your bug report. 15 | 16 | Caveats 17 | ------- 18 | 19 | Currently only the following items are scraped from the existing bug report's content: 20 | 21 | - Bug number 22 | - Title 23 | - Product 24 | - Status 25 | - Description 26 | 27 | Other values such as the classification, product version, reproducible status, etc., are harder to glean easily from the submitted bug display in Apple's Bug Reporter. 28 | 29 | It might be possible to get them out somehow, and such breakthroughs will be welcomed as pull requests, but in practice I find it's helpful enough to get a big head start on copying over the pertinent information. 30 | 31 | Rationale 32 | --------- 33 | 34 | I have had the idea for a long time to put together something like this, and I know I'm not the first. There are other solutions that facilitate "cross posting" to both Apple's Bug Reporter and Open Radar at the same time, but most of these involve a custom UI that doesn't support all the features of Apple's canonical web-based system. Usually I found that I just ended up going to the Bug Reporter site and going through the normal web interface, so it was compelling to me to come up with something that lets me copy any existing bug report's essential information into Open Radar for easy sharing. 35 | 36 | While putting this together, I leaned on Guillaume Campagna's Open Radar Helper, which is also a Safari extension, but which appears to have become non-functional after the last round of Bug Reporter redesigns. I started this project as a fork initially of Guillaume's project, but decided ultimately that I am only interested in the subset of behavior that allows easy transfer of content from the Bug Reporter to Open Radar. Other features such as updating existing bug content, and duping bugs from Open Radar to Bug Reporter, are not as important to me. So I decided to make my own streamlined extension that does what I need. Hopefully it does something you need, as well. -------------------------------------------------------------------------------- /BugValet.safariextension/AugmentAppleBugReporter.js: -------------------------------------------------------------------------------- 1 | 2 | function titleFromCurrentBug() 3 | { 4 | var theText = ""; 5 | var theElement = fragileElementLookupById("probTitleUpdateBox"); 6 | if (theElement != undefined) 7 | { 8 | // It's an "input" node with title as value 9 | theText = theElement.value; 10 | } 11 | 12 | return theText; 13 | } 14 | 15 | function descriptionFromCurrentBug() 16 | { 17 | var theText = ""; 18 | 19 | // Updates to the bug's running description are all in individual 20 | // class="preContent" elements. Typically this will just be one item, 21 | // the first item just generated for the bug. But on existing bugs where 22 | // some number of updates exist there could be e.g. updates from Apple 23 | // or whatever, and those shouldn't be propagated out of Radar. Longer term 24 | // it makes sense to try to deduce which are which, but in the shorter term, 25 | // we only copy out the description for the FIRST entry, the original bug 26 | // report, which should correlate to the LAST item in the list. 27 | var allPreContentElements = fragileElementsByClassName("preContent"); 28 | var elementCount = allPreContentElements.length; 29 | if (elementCount > 0) 30 | { 31 | var lastElement = allPreContentElements[elementCount - 1]; 32 | theText = lastElement.innerText; 33 | } 34 | 35 | return theText; 36 | } 37 | 38 | function creationDateFromCurrentBug() 39 | { 40 | var theText = ""; 41 | 42 | // Similarly to description above, each update to the bug shows with a 43 | // corresponding date. We'll just assume the last item (being the first entry 44 | // chronologically) contains the span that has the date we want. 45 | document.getElementsByClassName("fromID")[0].lastChild 46 | var allFromIDElements = fragileElementsByClassName("fromID"); 47 | var elementCount = allFromIDElements.length; 48 | if (elementCount > 0) 49 | { 50 | var lastElement = allFromIDElements[elementCount - 1]; 51 | 52 | // The element is a

element with text and a span child which contains the date 53 | theText = lastElement.lastChild.innerText; 54 | } 55 | 56 | return theText; 57 | } 58 | 59 | function dictionaryOfBugDetailsFromNode(detailsViewNode) 60 | { 61 | // Could use the detailsViewNode to do more limited search e.g. with Xpath, 62 | // but for now we're basically hard-coding everything based on full-document 63 | // scraping given the understanding that we are currently focused on a specific bug 64 | return {number: fragileElementTextById("displayFullDetailsProblemID"), 65 | title: titleFromCurrentBug(), 66 | description: descriptionFromCurrentBug(), 67 | state: fragileElementTextById("problemState"), 68 | product: fragileElementTextById("productFieldTab"), 69 | date: creationDateFromCurrentBug() 70 | }; 71 | } 72 | 73 | // Save the active bug's details to the extension's global page local storage, and 74 | // open a new tab for OpenRadar where the injected scripts will read the details 75 | // and populate the UI. 76 | function sendToOpenRadar() 77 | { 78 | var activeBugDetails = dictionaryOfBugDetailsFromNode(fragileElementLookupById("detailsView")); 79 | 80 | safari.self.tab.dispatchMessage("saveToLocalStorage", ["pendingRadarDetails", activeBugDetails]); 81 | 82 | window.open('http://openradar.me/myradars/add', "new tab"); 83 | } 84 | 85 | // Add the "Send to OpenRadar" button 86 | function augmentHeaderSection(headerNode) 87 | { 88 | // We just stick the button next to the "priorityContainer" node for now 89 | var buttonSibling = fragileElementLookupById("priorityContainer"); 90 | 91 | var sendToOpenRadarButton = document.createElement('input'); 92 | sendToOpenRadarButton.setAttribute('type','button'); 93 | sendToOpenRadarButton.setAttribute("value", "Send to Open Radar"); 94 | sendToOpenRadarButton.setAttribute("style", "margin-left:24; margin-top:6; position:relative; top:2;") 95 | sendToOpenRadarButton.onclick = sendToOpenRadar; 96 | buttonSibling.parentElement.appendChild(sendToOpenRadarButton); 97 | } 98 | 99 | function augmentAppleBugReporter() 100 | { 101 | // Anywhere else in Apple Bug Reporter, let's see if we have the cue to 102 | // create a new problem, and always add UI pertinent to cloning to OpenRadar 103 | // safari.self.tab.dispatchMessage("getOpenRadarValue", "wantsDuplicateRadar"); 104 | 105 | // We inject our UI enhancements by monitoring changes to the DOM for the page 106 | var observer = new MutationObserver(function(mutationRecords) { 107 | var recordCount = mutationRecords.length; 108 | for (thisRecordIndex = 0; thisRecordIndex < recordCount; thisRecordIndex++) { 109 | var thisRecord = mutationRecords[thisRecordIndex]; 110 | if (thisRecord.target.id == "detailsView") { 111 | var theseAddedNodes = thisRecord.addedNodes; 112 | if ((theseAddedNodes != undefined) && (theseAddedNodes.length > 0)) { 113 | var nodeCount = theseAddedNodes.length; 114 | for (thisNodeIndex = 0; thisNodeIndex < nodeCount; thisNodeIndex++) { 115 | var thisNode = theseAddedNodes[thisNodeIndex]; 116 | 117 | // We are inserting a node into the details view. We're interested specifically 118 | // in the node that is the "header" for a bug containing e.g. bug ID Number, state, 119 | // etc. And the node that is the content 120 | var isHeaderNode = (thisNode.id.indexOf("wrapper-header") == 0); 121 | if (isHeaderNode) { 122 | augmentHeaderSection(thisNode); 123 | } 124 | var isContentNode = (thisNode.id == "detailsViewContent"); 125 | if (isContentNode) { 126 | console.log("Adding content node:", + thisNode); 127 | } 128 | } 129 | } 130 | } 131 | } 132 | }); 133 | 134 | observer.observe(document.body, 135 | { // options: 136 | subtree: true, // observe the subtree rooted at myNode 137 | childList: true, // include information childNode insertion/removals 138 | attribute: false // include information about changes to attributes within the subtree 139 | }); 140 | } --------------------------------------------------------------------------------