Wednesday, February 27, 2008

NYBI Meetup #1 Recap

The Recap
The first New York Business Intelligence Meetup ran about 40 minutes over its 90-minute allocation. Membership grew from 3 to 26 members in under two weeks. Ron Moore played host, in a cozy, makeshift conference room, to 11 members (12 total). I was the only person constantly looking at the clock - but that came with the job description.

Agenda was "oatmeal" - basic ingredients congealing into something healthy and edible by a diverse group of specialists and enthusiasts, all feeling out NYBI Meetup's stomping ground. Introductions and individual identification with the BI space were in order. There seemed to be an even distribution of hard-core practitioners of BI, entrepreneurs looking to carve out a niche, enthusiasts (like myself) looking for a clue, instructors, and technologists struggling with the "what should I" and "how should I" aspects of the field.

This naturally lent itself to a prolonged deliberation on various topics. From philosophy to best practices and usability, the group was etching out boundaries and establishing interest camps. For a taste, some thoughts floating around covered:
  1. Who's job is it to define Vision for BI within an organization? What role do Technologists play?
  2. How does one go from a home-brewed analytics engine with Excel as the interface to a sustainable, cost-friendly vendor solution?
  3. What do end-users want out of BI? Do they even know?
  4. What are some common applications of BI? What are the new, creative applications that are enabled by recent trends in how people and systems exchange information?
  5. What is the balance between need-based drivers and "cool" technology-based drivers?
  6. Who's job is it to create awareness and to sell?
  7. Why is the Microsoft stack more attractive than other stacks? What is the criteria?
  8. What is the BI Pyramid and what is it's Tip?
  9. Why is data integrity so darn difficult?
  10. Can Open Source solutions compete?
Some entertaining, but paraphrased, sound bites:
  • Technologists tend to lead innovation within BI because they can build "It". However, technologists also build a lot of "S. H. It".
  • If the computer looked like a frying pan maybe my grandma would be more willing to use it. Shouldn't the same hold true for BI visualization tools?
  • So, you could trade 2 Julia Roberts for 1 Richard Gere? -- In reference to a startup in Russia that created a data-mining driven "forex" market where people trade the perceived value of a celebrity
Ron passed up his presentation on Essbase for these discussions to continue. We did however, get to see a brief intro to Microsoft's Performance Point Server from Victor Shamanovsky.

For a wrap up, I wanted to set a clearer trajectory for the Meetup and to focus conversations towards more tangible/practical means. Consensus appears to be: 50% Process/50% Technology. Philosophy, high-level discussion akin to what's highlighted above would fall into the former category. Product demos and real hands on BI technologies for the latter one. Essbase and PPS presentations will roll over to the next event.

My take...
Achieving topical focus for a diverse group of interests and experiences is quite challenging. Discussion should most likely pivot (horrible pun intended) on:
  • Business sectors and verticals. Past, current, future role of BI. Practical applications, best practices, room for innovation, market leaders, vendors, entrepreneurs etc.
  • The discipline itself. Warehousing, ETL, working with structured/unstructured data, core technologies, architecture, data design and integrity, reporting, UI.
Perhaps each get-together should recognize a focal point for the Process portion, ie, Profitability modeling in Finance or Sentiment Analysis and Tonality. The Technology section should then dive into available solutions, innovation, and best practices within the realm of the topics discussed in the Process portion.

Where can this take us?
  • A high-caliber Meetup around BI can obviously provide a business networking forum for industry professionals. Strengthening NYBI's reputation and brand will attract high caliber sponsors, vendors, and professionals. I would love for employers to look to the meetup for capable talent pool, for entrepreneurs to find collaborators, and so on.
  • Can this meetup influence the trajectory of innovation within BI? Will vendors and entrepreneurs be interested in vetting new products and ideas through the meetup? Can we do more than discuss and look at demos and actually deliver our musings, evaluations and conclusions to the vendors themselves? In turn, can we build a reputation and level of trust for all of this to become a reality??
I submit the above as evidence of a budding Meetup. I leave it to the members to keep my optimism grounded. Thanks to everyone who could make it. To those who couldn't, hope to see you next time! Special thanks to Alon P, my buddy and co-organizer of the Meetup and to Ron Moore for hosting.

To sign up, please visit the NYBI Meetup Homepage. There will be a healthy amount of activity through the businessintell-2@meetup.com mail-list as we build and prepare for our next event in March.

Saturday, February 23, 2008

New York Business Intelligence First Meetup!

In my musings on the next-generation enterprise, I keep coming back to the position that any social and contextual insights gleamed from provisioning new collaborative working models must influence an organization's business intelligence systems. This is not limited to rich interfaces and better interactivity. In fact, collaborative revolutions within the Enterprise will both feed and consume information from internally facing BI systems - the true neural network for an enterprise's brain.

I also recognize that my understanding of BI is rather rudimentary and lacks the perspective to truly participate in innovation within this field. And so, the New York Business Intelligence Meetup is born.



First meetup is Tuesday, February 23rd at 7pm and is sponsored by Ron Moore, Founder and President of Marketing Technologies Group, an Essbase consulting and training firm. Ron has also, graciously offered his office space for the event, conveniently located in the Financial District, 55 Broad St. 10th Floor.

For more details, please visit the Event Page.

Wednesday, February 6, 2008

Customizing Firefox's Reporter for the Enterprise

Objective
This post will provide a step-through for customizing Mozilla Firefox's Reporter Extension. Goal is to rewire publishing functionality so that submissions via Help->Report Broken Web Site... can be directed to the Enterprise's internal repository rather than Mozilla's. Caveat: modifications have been tested and are in production for deployments of Firefox 2.

Motivation
  • Early in the adoption process, not every site/webapp on the Intranet will be Firefox-friendly. A balanced view of "low hanging fruit" is required to prioritize remediation.
  • Reporting will help you identify applications causing the most "pain" to end-users. Every submission of a URL is essentially a Vote. Parlaying popularity with business criticality of the application itself will aid the remediation teams in scheduling work that takes into account, both, the needs of community members and your institution.
  • In short, creating transparency for what could be a tedious process along with active engagement of your community of users is
    • a crowd pleaser
    • will allow you to avoid lonesome hours of investigating and cataloging broken apps
    • will enable you to leverage your community for all phases of the remediation process, from discovery to testing
"Wall of Shame" Dashboard
Where should these results be published? The ideal data structure is a basic list. The important columns are already provided by "vanilla" Reporter:
  1. Web site URL
  2. Problem Type
  3. Describe Problem
  4. Email -> For the Enterprise, this Optional field is changed to Required, is locked and is auto-populated with the user's domain UID, basically whatever is used to log on to the work environment and is set to the Environment's %USERNAME% variable.
Whether you decide to build your own database with a frontend + REST/SOAP interface or simply leverage something like Sharepoint, where list-based reporting of this exact nature is the bread and butter of the platform, a few things are recommended:
  1. Make your data Public - allow anyone in the Firm to view the list of "broken" websites. Depending on your company's culture, you may even wish to induce a meme to the effect of "not supporting Firefox is shameful...". My preference is to title the Dashboard "Wall of Shame".
  2. Group entries by "Product" not by "URL". Since all you are getting from the report is a potentially long URL, without getting too fancy, you may wish to simply truncate it at the top directory level. At this point, grouping by "URL" is good enough.
  3. If you are piping the data into something that also facilitates Status Tracking data columns, you should expose the status of remediation efforts for that Product to the public.
High level overview of How Reporter Works
  1. User fills out a form, some fields are automatically picked up, i.e. URL, firefox build etc
  2. User submits form. Under the hood, a POST request is sent over HTTP, via XMLHttpRequest object, to some designated Web Service.
  3. The Web Service enters form data into it's database and throws back a response.
  4. Reporter renders success/failure page.
Anatomy of the Reporter Extension
In source code, Reporter lives in root/extensions/reporter. Layout of source code is shown to the right. For clarity, only the tweaked source files are shown.

After compilation and packaging, the resources folder becomes bin/chrome/reporter.jar. The locales section is merged into bin/chrome/en-US.jar/locale/en-US/reporter.

Privacy Notice -> Splash Page
Let's start by customizing the Privacy Notice dialog -- the very first thing our end-users will see when reporting a broken web site. Caveat: to disable the dialog that asks the user to acknowledge Mozilla's Privacy Policy, set extensions.reporter.hidePrivacyStatement in your global/default settings. I default this setting to true via Mission Control but using GPOs or CCK just to get that pref into the profile will work as well.
  • resources/skin/classic/reporter/firefoxlogo.gif -
    Any changes to the file name have to be reflected in reportWziard.xul (below). I replace the logo with the logo of my team, to visually indicate to the user that they are, in fact, interacting with my internal system. My image is a gif, sized 350x133 pixels.


  • resources/content/reporter/reportWizard.xul - any changes to logo or layout of splash page happen here
    This is what I have:
    <!-- Privacy Notice -->
    <wizardpage id="privacyNotice"
    onpageshow="initPrivacyNotice()"
    label = "&privacyNotice.label;">
    <!--description>&reportWizardPrivacy.description;</description-->
    <vbox id="privacyFrame" flex="1" style="padding:10px">
    <hbox>
    <html:img width="350px" height="133px"
    src="chrome://reporter/skin/firefoxlogo.gif" />
    </hbox>
    <hbox height="100px"></hbox>
    <hbox style="padding:4px;">
    <description align="end" flex="1"
    style="text-align:right;">&reportWizardPrivacy.description;</description>
    </hbox>
    </vbox>
    </wizardpage>

  • locales/en_US/chrome/reportWizard.dtd - just changing text here...
    <!ENTITY privacyNotice.label ""My" Firefox Reporter Agent">
    <!ENTITY reportWizardPrivacy.description "This tool allows you to tell the MY.Team about web sites that do not work properly in &brandShortName;, or shut &brandShortName; out. This is your way to help us ensure the best possible experience for &brandShortName; users.">
Report Form
  • resources/content/reporter/reportWizard.xul - only change is in disabling the textbox (since it will auto-populate)
     <row align="center">
    <label control="email" value="&reportForm.email.title;" accesskey="&reportForm.email.accesskey;"/>
    <textbox id="email" size="60" class="noborder" disabled="true"/>
    </row>
  • locales/en_US/chrome/reportWizard.dtd - only cosmetic changes here as well...
    <!ENTITY reportForm.email.title  "Username (Required):">
    <!ENTITY reportForm.email.accesskey "U">

  • resources/content/reporter/reportWizard.js - the username is pulled from the environment in this snippet. getUsername() is the function of interest.
    function initForm() {
    var strbundle=document.getElementById("strings");
    var reportWizard = document.getElementById('reportWizard');

    reportWizard.canRewind = true;
    document.getElementById('url').value = gURL;
    document.getElementById('email').value = getUsername();

    // Change next button to "submit report"
    reportWizard.getButton('next').label = strbundle.getString("submitReport") + " >";

    // We don't let the user go forward until they fufill certain requirements - see validateform()
    reportWizard.canAdvance = false;
    }

    function getUsername() {
    var env = Components.classes["@mozilla.org/process/environment;1"]
    .getService(Components.interfaces.nsIEnvironment);

    var username = env.get('USERNAME');
    return username;
    }
Send Data
All interesting pieces happen under the hood. In my environment, a SOAP envelope is constructed from the input form and is sent via XMLHttpRequest to a Sharepoint 2003 list. Obviously the implementation will have to be tweaked to work in your specific environment. However, the process of gathering and sending data over REST via XMLHttpRequest object should not change. Please explore the full listing of reportWizard.js available below.

Note-worthy areas are:
  • const declarations - POST parameters: url, operationName, listName, soapAction, myXMLNS, actionURI
  • prepareRequest() - converts form data into a qualified SOAP envelope for Sharepoint. See Example Envelop comment in the function.
  • callReporter() - executes the AJAX POST against the server and depending on response prepares the Results pane of the wizard
  • sendReport() - this is the master function, it pulls visible and hidden data from the form, stuffs it into an Array, calls prepareRequest() to convert the array into the SOAP envelope, and finally, calls callReporter() to fire off the data to my Sharepoint server.
The Results Page
At this point, our submission either succeeded or not. A report.xhtml or an error.xhtml page are rendered into an iframe on the final screen. These pages are dynamically populated by JavaScript.
  • resources/content/reporter/reportWizard.xul

    <!-- Finish -->
    <wizardpage id="finish"
    label="&finish.label;">
    <textbox id="finishSummary" size="60" readonly="true"/>
    <hbox>
    <checkbox id="showDetail" label="&reportResults.showDetail.title;" accesskey="&reportResults.showDetail.accesskey;" oncommand="showDetail()"/>
    </hbox>
    <vbox id="finishExtendedFrame" flex="1">
    <iframe id="finishExtendedSuccess" type="content" src="report.xhtml" flex="1"/>
    <iframe id="finishExtendedFailed" type="content" src="error.xhtml" flex="1"/>
    </vbox>
    </wizardpage>

    </wizard>

  • resources/content/reporter/report.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
    <!ENTITY % reporterDTD SYSTEM "chrome://reporter/locale/reportResults.dtd" >
    %reporterDTD;
    ]>
    <!-- ***** BEGIN LICENSE BLOCK *****
    - Version: MPL 1.1/GPL 2.0/LGPL 2.1
    -
    - The contents of this file are subject to the Mozilla Public License Version
    - 1.1 (the "License"); you may not use this file except in compliance with
    - the License. You may obtain a copy of the License at
    - http://www.mozilla.org/MPL/
    -
    - Software distributed under the License is distributed on an "AS IS" basis,
    - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
    - for the specific language governing rights and limitations under the
    - License.
    -
    - The Original Code is Mozilla Reporter (r.m.o).
    -
    - The Initial Developer of the Original Code is
    - Robert Accettura <robert@accettura.com>.
    -
    - Portions created by the Initial Developer are Copyright (C) 2004
    - the Initial Developer. All Rights Reserved.
    -
    - Contributor(s):
    -
    - Alternatively, the contents of this file may be used under the terms of
    - either the GNU General Public License Version 2 or later (the "GPL"), or
    - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
    - in which case the provisions of the GPL or the LGPL are applicable instead
    - of those above. If you wish to allow use of your version of this file only
    - under the terms of either the GPL or the LGPL, and not to allow others to
    - use your version of this file under the terms of the MPL, indicate your
    - decision by deleting the provisions above and replace them with the notice
    - and other provisions required by the LGPL or the GPL. If you do not delete
    - the provisions above, a recipient may use your version of this file under
    - the terms of any one of the MPL, the GPL or the LGPL.
    -
    - ***** END LICENSE BLOCK ***** -->
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <title>reporter</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <link rel="stylesheet" type="text/css" href="chrome://reporter/skin/reportResults.css"/>
    </head>
    <body>
    <table>
    <tr>
    <th>&reportSite;:</th>
    <td><span id="urlStri"/></td>
    </tr>
    <tr>
    <th>&reportProblemType;:</th>
    <td><span id="problemTypeStri"/></td>
    </tr>
    <tr>
    <th>&reportDecsription;:</th>
    <td><span id="descriptionStri"/></td>
    </tr>
    <tr>
    <th>&reportPlatform;:</th>
    <td><span id="platformStri"/></td>
    </tr>
    <tr>
    <th>&reportProduct;:</th>
    <td><span id="productStri"/></td>
    </tr>
    <tr>
    <th>&reportoscpu;:</th>
    <td><span id="oscpuStri"/></td>
    </tr>
    <tr>
    <th>&reportGecko;:</th>
    <td><span id="geckoStri"/></td>
    </tr>
    <tr>
    <th>&reportBuildConfig;:</th>
    <td><span id="buildConfigStri"/></td>
    </tr>
    <tr>
    <th>&reportUseragent;:</th>
    <td><span id="userAgentStri"/></td>
    </tr>
    <tr>
    <th>&reportLanguage;:</th>
    <td><span id="langStri"/></td>
    </tr>
    <tr>
    <th>&reportEmail;:</th>
    <td><span id="emailStri"/></td>
    </tr>
    </table>
    </body>
    </html>
  • resources/content/reporter/error.xhtml
    <?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
    <!ENTITY % reporterDTD SYSTEM "chrome://reporter/locale/reportResults.dtd" >
    %reporterDTD;
    ]>
    <!-- ***** BEGIN LICENSE BLOCK *****
    - Version: MPL 1.1/GPL 2.0/LGPL 2.1
    -
    - The contents of this file are subject to the Mozilla Public License Version
    - 1.1 (the "License"); you may not use this file except in compliance with
    - the License. You may obtain a copy of the License at
    - http://www.mozilla.org/MPL/
    -
    - Software distributed under the License is distributed on an "AS IS" basis,
    - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
    - for the specific language governing rights and limitations under the
    - License.
    -
    - The Original Code is Mozilla Reporter (r.m.o).
    -
    - The Initial Developer of the Original Code is
    - Robert Accettura <robert@accettura.com>.
    -
    - Portions created by the Initial Developer are Copyright (C) 2004
    - the Initial Developer. All Rights Reserved.
    -
    - Contributor(s):
    -
    - Alternatively, the contents of this file may be used under the terms of
    - either the GNU General Public License Version 2 or later (the "GPL"), or
    - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
    - in which case the provisions of the GPL or the LGPL are applicable instead
    - of those above. If you wish to allow use of your version of this file only
    - under the terms of either the GPL or the LGPL, and not to allow others to
    - use your version of this file under the terms of the MPL, indicate your
    - decision by deleting the provisions above and replace them with the notice
    - and other provisions required by the LGPL or the GPL. If you do not delete
    - the provisions above, a recipient may use your version of this file under
    - the terms of any one of the MPL, the GPL or the LGPL.
    -
    - ***** END LICENSE BLOCK ***** -->

    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <title>reporter</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <link rel="stylesheet" type="text/css" href="chrome://reporter/skin/reportResults.css"/>
    </head>
    <body>
    <h3>&error;</h3>
    <div id="messagediv"><b>Error Code: </b><span id="faultCode"/><br/><b>Error Message: </b><span id="faultMessage"/></div>
    </body>
    </html>

Full Listing of reportWizard.js

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Reporter (r.m.o).
*
* The Initial Developer of the Original Code is
* Robert Accettura .
*
* Portions created by the Initial Developer are Copyright (C) 2004
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Boris Zbarsky
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */

/********************************************************
* *** Warning ****
* DO _NOT_ MODIFY THIS FILE without first contacting
* Robert Accettura
* or a reporter.mozilla.org Admin!
*******************************************************/

const gURL = window.arguments[0];
const gLanguage = window.navigator.language;
const gRMOvers = "0.2"; // Do not touch without contacting reporter admin!

// Globals
var gReportID;
var gSysID;
var gFaultCode;
var gFaultMessage;
var gSOAPerror = false;
var gPrefBranch;
const gAsync = false;
var gButton;
var reportWizard;

var myRequest;
var xmlEnvelope;
var consoleService;
/* SOAP Services for Sharepoint 2003 */
const url = 'http://myshareporint/sites/MyMozillaSite/_vti_bin/Lists.asmx';
const operationName = 'UpdateListItems';
const listName = 'MyReporterList';
const soapAction = 'SOAPAction';
const myXMLNS = 'http://schemas.microsoft.com/sharepoint/soap/';
const actionURI = myXMLNS+operationName;


function getReporterPrefBranch() {
if (!gPrefBranch) {
gPrefBranch = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService)
.getBranch("extensions.reporter.");
}
return gPrefBranch;
}


function getBoolPref(prefname, aDefault) {
try {
var prefs = getReporterPrefBranch();
return prefs.getBoolPref(prefname);
} catch(ex) {
return aDefault;
}
}


function getCharPref(prefname, aDefault) {
try {
var prefs = getReporterPrefBranch();
return prefs.getCharPref(prefname);
} catch(ex) {
return aDefault;
}
}


function initPrivacyNotice() {
var strbundle=document.getElementById("strings");
var reportWizard = document.getElementById('reportWizard');

// Change next button to "submit report"
reportWizard.getButton('next').label = "Next >";

reportWizard.canRewind = false;
reportWizard.canAdvance = true;
}

function setPrivacyPref(){
if (document.getElementById('dontShowPrivacyStatement').checked){
var prefs = getReporterPrefBranch();
prefs.setBoolPref("hidePrivacyStatement", true);
}
}

function initForm() {
var strbundle=document.getElementById("strings");
var reportWizard = document.getElementById('reportWizard');

reportWizard.canRewind = true;
document.getElementById('url').value = gURL;
document.getElementById('email').value = getUsername();

// Change next button to "submit report"
reportWizard.getButton('next').label = strbundle.getString("submitReport") + " >";

// We don't let the user go forward until they fufill certain requirements - see validateform()
reportWizard.canAdvance = false;
}

function getUsername() {
var env = Components.classes["@mozilla.org/process/environment;1"]
.getService(Components.interfaces.nsIEnvironment);

var username = env.get('USERNAME');
return username;
}

function validateForm() {
var canAdvance = document.getElementById('problem_type').value != "0";
document.getElementById('reportWizard').canAdvance = canAdvance;
}


function registerSysID(){
var param = new Array();;
param[0] = new SOAPParameter(gLanguage, "language");

// get sysID
callReporter("register", param, setValSysID);

// saving
if (gSysID != undefined){
var prefs = getReporterPrefBranch();
prefs.setCharPref("sysid", gSysID);
return gSysID;
}
return "";
}


function getSysID() {
var sysId = getCharPref("sysid", "");
if (sysId == "")
//sysId = registerSysID();

return sysId;
}

function sendReport() {
// we control the user path from here.
var reportWizard = document.getElementById('reportWizard');

reportWizard.canRewind = false;
reportWizard.canAdvance = false;
// why would we need a cancel button?
reportWizard.getButton("cancel").disabled = true;

var strbundle=document.getElementById("strings");
var statusDescription = document.getElementById('sendReportProgressDescription');
var statusIndicator = document.getElementById('sendReportProgressIndicator');

// Data from form we need
var myData = new Array();
myData['rmoVers'] = gRMOvers;
myData['url'] = gURL;
myData['problem_type'] = document.getElementById('problem_type').value;
myData['description'] = document.getElementById('description').value;
myData['behind_login'] = (document.getElementById('behind_login').checked ? 'Yes' : 'No');
myData['platform'] = navigator.platform;
myData['oscpu'] = navigator.oscpu;

myData['gecko'] = getGecko();
myData['product'] = getProduct();
myData['useragent'] = navigator.userAgent;
myData['buildconfig'] = getBuildConfig();
myData['language'] = gLanguage;
myData['email'] = document.getElementById('email').value;
myData['sysid'] = getSysID();

//build XMLHttpRequest
consoleService = Components.classes['@mozilla.org/consoleservice;1'].getService(Components.interfaces.nsIConsoleService);

doLog("init(): creating xmlEnvelope");
xmlEnvelope = prepareRequest(operationName,listName,myData);

var s = new XMLSerializer();
//var strMessage = s.serializeToString(xmlEnvelope);
doLog("doc: "+s.serializeToString(xmlEnvelope));

statusIndicator.setAttribute("value", "5%");
statusDescription.setAttribute("value", strbundle.getString("sendingReport"));

//CALL REPORTER
callReporter(operationName, xmlEnvelope); //setValReportID);

var finishSummary = document.getElementById('finishSummary');
var finishExtendedFailed = document.getElementById('finishExtendedFailed');
var finishExtendedSuccess = document.getElementById('finishExtendedSuccess');
if (!gSOAPerror) {
// If successful
finishExtendedFailed.setAttribute("class", "hide");

statusIndicator.setAttribute("value", "95%");
statusDescription.setAttribute("value", strbundle.getString("reportSent"));

reportWizard.canAdvance = true;
statusIndicator.setAttribute("value", "100%");

// Send to the finish page
reportWizard.advance();

// report ID returned from the web service
finishSummary.setAttribute("value", strbundle.getString("successfullyCreatedReport") + " " + gReportID);

finishExtendedDoc = finishExtendedSuccess.contentDocument;
finishExtendedDoc.getElementById('urlStri').textContent = myData['url'];
finishExtendedDoc.getElementById('problemTypeStri').textContent = myData['problem_type'];
finishExtendedDoc.getElementById('descriptionStri').textContent = myData['description'];
finishExtendedDoc.getElementById('platformStri').textContent = myData['platform'];
finishExtendedDoc.getElementById('oscpuStri').textContent = myData['oscpu'];
finishExtendedDoc.getElementById('productStri').textContent = myData['product'];
finishExtendedDoc.getElementById('geckoStri').textContent = myData['gecko'];
finishExtendedDoc.getElementById('buildConfigStri').textContent = myData['buildconfig'];
finishExtendedDoc.getElementById('userAgentStri').textContent = myData['useragent'];
finishExtendedDoc.getElementById('langStri').textContent = myData['language'];
finishExtendedDoc.getElementById('emailStri').textContent = myData['email'];

reportWizard.canRewind = false;
} else {
doLog('Failed to update list!');
// If there was an error from the server
finishExtendedSuccess.setAttribute("class", "hide");

// Change the label on the page so users know we have an error
var finishPage = document.getElementById('finish');
finishPage.setAttribute("label",strbundle.getString("finishError"));

reportWizard.canAdvance = true;
reportWizard.advance();

finishSummary.setAttribute("value",strbundle.getString("failedCreatingReport"));

finishExtendedDoc = finishExtendedFailed.contentDocument;
finishExtendedDoc.getElementById('faultCode').textContent = gFaultCode;
finishExtendedDoc.getElementById('faultMessage').textContent = gFaultMessage;
}
document.getElementById('finishExtendedFrame').collapsed = true;
reportWizard.canRewind = false;
reportWizard.getButton("cancel").disabled = true;
}


function showDetail() {
var hideDetail = document.getElementById('showDetail').checked ? false : true;
document.getElementById('finishExtendedFrame').collapsed = hideDetail;
}


function getBuildConfig() {
// bz and Biesi are my heroes for writing/debugging this chunk.
try {
netscape.security.PrivilegeManager
.enablePrivilege("UniversalXPConnect UniversalBrowserRead UniversalBrowserWrite");
var ioservice =
Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var channel = ioservice.newChannel("chrome://global/content/buildconfig.html", null, null);
var stream = channel.open();
var scriptableInputStream =
Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Components.interfaces.nsIScriptableInputStream);
scriptableInputStream.init(stream);
var data = "";
var curBit = scriptableInputStream.read(4096);
while (curBit.length) {
data += curBit;
curBit = scriptableInputStream.read(4096);
}
// Strip out the part, since it's not valid XML
data = data.replace(/^]*>/, "");
// Probably not strictly needed, but what the heck
data = data.replace(/^/, "");
var parser = new DOMParser();
var buildconfig = parser.parseFromString(data, "application/xhtml+xml");
var text = buildconfig.getElementsByTagName("body")[0].textContent;
var start= text.indexOf('Configure arguments')+19;
return text.substring(start);
} catch(ex) {
dump(ex);
return "Unknown";
}
}

// Execute an AJAX call
function callReporter(method, message) {
//var serviceURL = getCharPref("serviceURL", myServiceURL);

doLog("init(): sending request to "+url);
myRequest = new XMLHttpRequest();

myRequest.onreadystatechange=function() {
if (myRequest.readyState==4) {
if (myRequest.status==200) {
doLog("URL Exists!");
doLog(myRequest.getAllResponseHeaders());
doLog(myRequest.responseText);
alert("URL '"+url+"' exists");
alert(myRequest.responseText);
} else if(myRequest.status==404) {
doLog("URL doesn't exist!");

} else if(myRequest.status==500) {
doLog("Server Failed "+myRequest.status);
doLog("theResponse: "+myRequest.responseText);
gSOAPerror = true;
gFaultCode = myRequest.status;
gFaultMessage = myRequest.responseText;
} else {
doLog("unknown error!");
gSOAPerror = true;
doLog("Server Failed "+myRequest.status);
doLog("theResponse: "+myRequest.responseText);
}
}
};

doLog("xmlEvenlope: Serialized -- "+message);

try{
myRequest.open("POST", url,gAsync);
myRequest.setRequestHeader(soapAction,actionURI);
myRequest.setRequestHeader('Content-Type','text/xml');
doLog("myRequest: "+myRequest);
myRequest.send(message);
doLog("myRequest: sent -- \n"+message);

if(!gAsync) {
if(myRequest.status != 200) {
gSOAPerror = true;
gFaultCode = myRequest.status;
gFaultMessage = myRequest.responseText;
}
}
} catch(e) {
doLog("exception: "+e);
}
}


function setValSysID(results) {
if (results) {
var params = results.getParameters(false,{});
for (var i = 0; i < gsysid =" params[i].value;" params =" results.getParameters(false,{});" i =" 0;" greportid =" params[i].value;" appinfo =" Components.classes[" appinfo =" Components.classes[" doc =" document.implementation.createDocument(myXMLNS," env =" document.createElement(" body =" document.createElement(" operation =" document.createElement(" listname =" document.createElement(" op ="=" op ="=" env="http://schemas.xmlsoap.org/soap/envelope/" xsi="http://www.w3.org/2001/XMLSchema-instance" xsd="http://www.w3.org/2001/XMLSchema">ReporterNewkrylovy

*/

// XML document
var Updates = document.createElement("a0:updates");
Operation.appendChild(Updates);

var Batch = document.createElement("Batch");
Batch.setAttribute("OnError","Return");
Batch.setAttribute("ListVersion","1");
Updates.appendChild(Batch);

var Method = document.createElement("Method");
Method.setAttribute("ID","1");
Method.setAttribute("Cmd","New");
Batch.appendChild(Method);

var Field;
var key;

//one-time operations
Field = createField('ID','New');
Method.appendChild(Field);

Field = createField('Title',data['url']);
Method.appendChild(Field);

for(key in data) {
doLog('key: '+key+' value: '+data[key]);
Field = createField(key,data[key]);
Method.appendChild(Field);
}
}
return Doc;
}

function createField(key,value) {
var field = document.createElement('Field');
field.setAttribute('Name',key);

var text = document.createTextNode(value);
field.appendChild(text);

return field;
}

function doLog(aMessage) {
consoleService.logStringMessage("MSReporter: "+aMessage);
}


Full Listing of reportWizard.xul
<?xml version="1.0"?>
<!-- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is Mozilla Reporter (r.m.o).
-
- The Initial Developer of the Original Code is
- Robert Accettura <robert@accettura.com>.
-
- Portions created by the Initial Developer are Copyright (C) 2004
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the LGPL or the GPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://reporter/skin/reportWizard.css" type="text/css"?>

<!DOCTYPE wizard [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%brandDTD;
<!ENTITY % reportWizardDTD SYSTEM "chrome://reporter/locale/reportWizard.dtd">
%reportWizardDTD;
]>

<wizard id="reportWizard" title="&reportWizard.title;"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<script type="application/x-javascript" src="chrome://reporter/content/reportWizard.js"/>
<stringbundle id="strings" src="chrome://reporter/locale/reportWizard.properties"/>

<!-- Privacy Notice -->
<wizardpage id="privacyNotice"
onpageshow="initPrivacyNotice()"
label = "&privacyNotice.label;">
<!--description>&reportWizardPrivacy.description;</description-->
<vbox id="privacyFrame" flex="1" style="padding:10px">
<hbox>
<html:img width="350px" height="133px" src="chrome://reporter/skin/firefoxlogo.gif" />
</hbox>
<hbox height="100px"></hbox>
<hbox style="padding:4px;">
<description align="end" flex="1" style="text-align:right;">&reportWizardPrivacy.description;</description>
</hbox>
</vbox>
</wizardpage>

<!-- Report Form -->
<wizardpage id="reportForm"
onpageshow="initForm()"
label="&reportForm.label;">
<description>&reportForm.description;</description>
<separator />
<grid>
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
<row align="center">
<label control="url" value="&reportForm.url.title;"/>
<textbox id="url" size="60" readonly="true" class="noborder"/>
</row>
<row align="center">
<spacer/>
<checkbox id="behind_login" label="&reportForm.behind_login.title;" accesskey="&reportForm.behind_login.accesskey;"/>
</row>
<row align="center">
<label control="problem_type" value="&reportForm.problem_type.title;" accesskey="&reportForm.problem_type.accesskey;"/>
<!-- XXX: Perhaps this should eventually/maybe come from somewhere else? Eh, not sure so lets just hardcode this for now. -->
<menulist label="problem_type" id="problem_type" oncommand="validateForm()">
<menupopup>
<!-- ************* WARNING *************** -->
<!-- DO *NOT* Add/change/modify without consulting with r.m.o server admin first! -->
<!-- ************ /WARNING *************** -->
<menuitem label="&reportForm.problem_type.chooseOne.title;" value="0"/>
<menuitem label="&reportForm.problem_type.item1.title;" value="1"/>
<menuitem label="&reportForm.problem_type.item2.title;" value="2"/>
<menuitem label="&reportForm.problem_type.item3.title;" value="3"/>
<menuitem label="&reportForm.problem_type.item4.title;" value="4"/>
<menuitem label="&reportForm.problem_type.item5.title;" value="5"/>
<menuitem label="&reportForm.problem_type.item6.title;" value="6"/>
<menuitem label="&reportForm.problem_type.item7.title;" value="7"/>
</menupopup>
</menulist>
</row>
<row>
<label control="description" value="&reportForm.describe.title;" accesskey="&reportForm.describe.accesskey;"/>
<textbox id="description" value="" cols="40" rows="5" multiline="true" size="40" class="noborder"/>
</row>
<row align="center">
<label control="email" value="&reportForm.email.title;" accesskey="&reportForm.email.accesskey;"/>
<textbox id="email" size="60" class="noborder" disabled="true"/>
</row>
<!--row align="center">
<spacer/>
<hbox>
<label id="privacyPolicy" class="text-link"
value="&reportForm.privacyPolicy.title;"
tooltiptext="&reportForm.privacyPolicy.tooltip;"/>
</hbox>
</row-->
</rows>
</grid>
</wizardpage>

<!-- Send Data -->
<wizardpage id="sendReport"
onpageshow="sendReport()"
label="&sendReport.label;">
<description>&sendReport.description;</description>
<separator />
<description id="sendReportProgressDescription"/>
<progressmeter id="sendReportProgressIndicator" mode="undetermined" value="0%"/>
</wizardpage>

<!-- Finish -->
<wizardpage id="finish"
label="&finish.label;">
<textbox id="finishSummary" size="60" readonly="true"/>
<hbox>
<checkbox id="showDetail" label="&reportResults.showDetail.title;" accesskey="&reportResults.showDetail.accesskey;" oncommand="showDetail()"/>
</hbox>
<vbox id="finishExtendedFrame" flex="1">
<iframe id="finishExtendedSuccess" type="content" src="report.xhtml" flex="1"/>
<iframe id="finishExtendedFailed" type="content" src="error.xhtml" flex="1"/>
</vbox>
</wizardpage>

</wizard>

Testing and Packaging
Backup the .jar's with Reporter's components. For quick/dirty tests, simply open up each of the aforementioned jars with something like WinRAR and simply overwrite/add the pristine files with your version. Changes should take effect after restart of the browser.

Optionally, and I have not done this, you can create the manifests and treat reporter as just another extension under development. This is probably the best way to go if changes to the extension are significant.

My team compiles our own version of Firefox for internal distribution. Changes to the reporter extension are part of our "pre-build" process. Luckily, this extension has not changed over the lifetime of Firefox 2.

Other options are simply to use CCK or a wrapper to drop in your replacement jars prior to roll-out.

Spread the word
Users need to know that the "Report a Broken Website..." has been customized and is available for use. Within a few weeks we accumulated over 300 entries with around 10 unique sites making our "To be Remediated List". Yeah, I wussed out on the "Wall of Shame" title =)

Wrap up

Hopefully, the walk-through illustrated some strategies in customizing the Reporter extension. Although my example publishes to Sharepoint in SOAP format, the mechanisms for submitting to any Web Service are standard and should be fairly straight-forward to customize for your own needs. The resulting Dashboard/Reporting aspect is community-friendly and provides useful statistics to guide the prioritization of remediation work.

Errata will certainly follow in the form of comments and I of course welcome corrections, opinions, feedback and your own stories from the frontier. Cheers.