├── 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 | }
--------------------------------------------------------------------------------