Tuesday, September 2, 2008

Google Chrome for the Enterprise?

Google's Chrome is an impressive little beta. Performant? Check... Slick? You bet... More intelligence packed into navigation? Yup... Developer tools baked in? As warm as apple pie...

But does Google Chrome have an Enterprise twinkle in its eye?

A few things to be excited about:
  1. javascript running in a VM
    As the Google Chrome Comic Book states running JS within a VM allows the browser to optimize javascript and actually compile the structure into machine code so that "interpreted" code is not over-interpreted. Chrome is supposed to be fast. And this should make Knowledge Workers with 40+ tabs, happy.
  2. process-level isolation
    Process per tab, process per site. every app, be it web app or a component of Chrome runs as it's own application. Many benefits to this, e.g. crashes are limited to impacted process/tab, resources are allocated to active tab not the entire browser, jail-like security per process, and so on. This design boosts overall stability of the browser.
  3. developer tools baked in
    Beginnings of a superb JavaScript debugger and analyzer. Dom element highlighting, in-line style info, style metrics and element properties. And the niftiest addition: web-page performance analytics with charts. I'm sure someone will do a punch-for-punch againt Firefox's FireBug extension.
  4. wrench->options->under the hood->change proxy settings
    Looks like Connection Settings are delegated to Internet Explorer's - now that's interesting. This means that Group Policies that govern IE's settings can also apply to Google Chrome. I wonder what else can be managed...
  5. about:memory
    Measures memory usage of this multi-threaded browser
  6. about:plugins
    There is an about:plugins page that shows loaded plugins. Looks exactly like Firefox's

Things to look forward to:
  1. Extensions
  2. RSS support
  3. Spell checker
  4. ...and so on...
As for Enterprise-friendliness. Much of our ability to adopt Google Chrome in the enterprise will depend on granular Settings Management for Browser and Gears via a MissionControl-like mechanism or GPO. Enforceable and customizeable Security Zones will permit richer interoperability with the Desktop. Perhaps the extension system will facilitate this with ease. But of couse, this is tbd.

What is Google going after? As a colleague put it, the web browser is the unsung hero of the desktop. It is perhaps the most important application there. But it is not just a gateway to the net. The web browser is an application container in it's own right. The gap between desktop and web apps is quickly shrinking. Gears are not simply an offline mode but perhaps the bridge to interoperability between the web browser and other desktop-bound components. Perhaps we'll see these concepts in play on our mobile devices sooner than on our desktops. Perhaps Google is really after an ad revenue-powered "virtual" desktop for the consumer, reminiscent of what Citrix is trying to do for the enterprise?

As for Enterprise-wide deployment of Google Chrome, we'll have to look at its about:config equivalent first!

Wednesday, May 14, 2008

NYBI Meetup #3 Recap

The Recap
The third Meetup was fully dedicated to discussion. It was driven by a few PowerPoint slides from one NYBI member's presentation given at the CTO Summit in Summer of 2007.

One slide of interest was about a proposed BI Ontology reproduced, in part, below:

BI Ontology
  • Decision Space
    • Decision Purpose
    • Decision Frequency
    • Decision Impact
  • Tool Space
    • Data
    • Models
    • Experience
    • Feedback
There was another level of detail for each sub-bullet that will become available when I post the original slides. Ontologies are important because they provide a shared and common understanding of concepts presented and discussed in forum. The same way that we hope to derive consensus on Trends and Truths within the BI space, so do we hope to establish a descriptive (and shared) language about BI with the aid of the proposed Ontology.

An introductory slide or two spoke to the "State of Affairs" as well the "This is how we came to be here" and spurred on 2 full hours of discussion.

Some highlights:
  • Most organizations looking to introduce BI into their fold succeed in rolling out a Data Warehouse. Rolling out Reporting infrastructure is the difficult part. If the organization is lucky enough to succeed, this reporting infrastructure tends to be the most expensive investment.
  • The industry itself evolved from ad-hoc tools to very structured tools and mechanisms. Approach to successful BI implementation became a Trade and a science in of itself.
  • The BI Ecosystem is very large. There are a few established Tools and Tool Vendors but community of people that provide quality or reporting services is for these Tools is significantly larger.
  • Vendor tools, however, are going through mergers and the Vendors are losing independence. Will a community emerge to not just support the Vendors but to actually drive innovation in the space and offset the aforementioned loss of independence by Vendors?
  • Paper distribution has been consistently declining over the last 4 years to be replaced by digital systems. Are paper statements in existence today exercises in Data Summarization? Why are our banking statements and our bills still so confusing and uninformative?
  • In turn, with an encroaching demand from end-users to be able to do real-time analytics and trending of their own personal data, why are banks so slow to spice up their online banking systems? Are new sites like Mint.com and QuickenOnline setting a new standard in personal finance analytics?
  • How much longer will we wait for the higher activities of analysis? Is the next step in BI Tools that aid in decision making process rather than just visualization? Can "actions" exposed by these new BI Tools be contextually relevant and drive specific end-results, such as yield competitive advantage?
  • Volume of data is now manageable. Tools that are making sense of this volume are driving innovation.
  • Is the notion of Business Intelligence simply too constrained to fully encompass possibilities? Are we transitioning to a more general-purpose, behavioral, Collective Intelligence that enables an organization to tap into data previously unaccessible or simply discarded? Can this aid an organization's process of innovation and feed competitive advantage?
  • Is it possible to phase out manual Knowledge Management along with paper distribution and plugin in systems that interpret data on a Semantic level?
  • Are Personalization of Services and Individual Privacy on opposing sides? Are the notions of privacy on the Internet and a Corporate Intranet so drastic that most innovation in Collective Intelligence will, in fact, occur on the Intranet where Individual Privacy is a more manageable Can of Worms?
Truths and Trends
  • Truth: There are lots more data sources of interest than what is currently accessible on the internet
  • Truth: Improving on what computers tend to do well does not necessarily result in value to the end-user
  • Truth: Audio processing is more difficult than Video processing
  • Trend: Use of Voice and Video as datasources is a new frontier. We wonder how much of it is native analysis vs metadata processing.
  • Trend: More real-time decision-making at the hands of the user.
  • Trend: Enterprise environment will give way to enterprise community.
  • Trend: Web2.0 Collaborative tools are becoming more accepted in the enterprise as both the enterprises and the vendors mature
  • Truth: There are lots more data sources of interest than what is currently accessible on the internet
  • Truth: Improving on what computers tend to do well does not necessarily result in value to the end-user
  • Truth: Audio processing is more difficult than Video processing
  • Trend: Use of Voice and Video as datasources is a new frontier. We wonder how much of it is native analysis vs metadata processing
  • Trend: More real-time decision-making at the hands of the user.
Next Meetup
SAP Business Objects folks will be joining us to help us explore Xcelsius, their new analytics tool. They will also speak to the rest of the near-future product road-map. Details are available on the http://nybimeetup.org site.

Thursday, May 1, 2008

Intro to Enterprise RSS

Firstly, what is RSS and how does it relate to what is familiar to the "Enterprise" us?
In of itself RSS is nothing special. It is just a format for "Latest Headlines". Roughly it says that Content A is about something and the full posting of the same is located at http://A. Content B is about something else and lives at http://B. Where-as prior to popularization of "syndication" the user had to go to sites of interest to peruse updated content, RSS now allows content producers to simply "push" updates to us as we busy ourselves with more important tasks.

Value in RSS is introduced by the Publisher who takes ownership of publishing the latest headlines in a known or easily discoverable location that does not change while keeping that list of headlines up to date. Value is passed on to the Consumer through software that knows how to deliver fresh content to you, the Knowledge Worker.

A Use Case
Let's say a Team wants to maintain a high-level, chronological log of project activity and make this log available for others to peruse at individual convenience (buzzwords: asynchronous consumption of syndicated content). A classical way of disseminating this information (in the past) has been to spend man-hours designing these headlines into a Newsletter format and to send the Newsletter out to everyone via email. Those who care and those who don't care would receive this email aka spam.

Now let's say this Team hosts an RSS file that is auto-magically updated on a daily basis by some publishing system that you, the user, should not really know or care about. Let's say it's Sharepoint (since Sharepoint 2007 does this very well...). The system is pre-configured in such a manner so that whenever a member of the Team publishes an article via Sharepoint, the RSS file representing all such articles published is automatically updated with the latest headlines on published documents.

Those interested in latest headlines from that group can direct their RSS Client to keep an "eye" on that Feed. Specifically, your client "pulls" the latest content on an interval, directly from the "source" without involving Newsletters and without spamming your Inbox with content of suspect relevance to you.

Two important terms here are: RSS Feed - that which is owned and updated by the content owner, and RSS Reader (or RSS Client) through which the end-user maintains subscriptions of interest and by which the end-user can always see the "latest" headlines coming from the Content Owner.

For further details on what RSS is and is not, please take a look at the Wikipedia entry on RSS.

Why are we even bothering with "centralized" or "IT"-driven RSS?
Let's take a very practical (and conservative), risk-minded perspective on infrastructure common to most Fortune 500 enterprise-size companies. Assumption here is that RSS Clients of all sorts run rampant in the environment's Intranet and RSS content is treated as any other web content. This means that all outbound requests for web-hosted content on the "out there" internet as well as the content retrieved from the web go through a Proxy tier commonly maintained by some division of Corporate IT.

Imagine everyone (that's us, to be referred to as "we") in this hypothetical Firm maintaining a list of 50-100 subscriptions to external and internal sources of content. Let's also say that because we always subscribe to Content of Interest and therefore content of Relevance, we wish our RSS Clients to "keep an eye" on each feed and to pull interesting updates to our subscriptions every 30 minutes.

In reality, what this means is that everyone in the Firm is asking the CNN's of the world for updates on ALL headlines of interest every 30 minutes.

A sidebar on Proxies and their role within our environment is important here. We know that we access the "outside" internet through Proxies. Why Proxies? Proxies do some security things. More importantly, Proxies do some content caching and networking and load optimization that are essential to maintaining healthy connectivity to the internal and external networks. Proxies are critical components of an enterprise's infrastructure and are, essentially, Gateway Keepers to the Web. Most Proxy infrastructures are probably designed to accommodate Humans perusing sites. Systems that do demand a high load of network content are usually provisioned, accounted for, and paid for separately as a specific requirement. The Business Unit requiring such special capacity will usually take on this cost. Recall in our original assumption that no such "special" considerations were in place for RSS traffic.

Your RSS Client asking for updates from the web is equivalent to your Computer perusing sites. Your Computer can do a lot of things quickly, such as pull 50-100 subscription updates within a matter of seconds: something that would take us much more time to do by hand. What does this mean? It means that the amount of traffic to and from the outside Internet increases drastically. This happens because polling of many CNNs is condensed to a short amount of time. This happens per individual with RSS feeds over the course of the day. In other words, unrestrained RSS Clients Gone Wild in our environment will create too much unplanned traffic through the Network and will overload the Proxy servers not provisioned to handle such load. Risk here is disrupting daily routing and more importantly business critical applications relying on the network. Enterprises should deem this as an unacceptable risk. Does this mean RSS should not be allowed in the environment? Absolutely not!

Solution
As mentioned before, Proxies do some caching. For example, when you browse for CNN.com homepage and I browse for CNN.com homepage the Proxy is smart enough to realize that CNN.com is not a unique resource and that more than one person wants to and will probably want to see the same content. Rather than using up Networking resources to deliver content to you from CNN.com directly, Proxies will simply cache that page locally and serve out the local (cached) copy to the user for subsequent requests. This is economical.

The exact same model needs to apply for all RSS traffic in and out of our environment. Something needs to sit between your RSS Client and the RSS Feed Owner to provide exactly the same type of mediation and optimization.

To enable RSS within the Firm, the Firm must deploy some RSS-specific Proxy-like infrastructure to Gatekeep RSS traffic. IT should own this task.

Now that RSS is going through a centralized "hub", what else can we do with it?
When data is managed in a centralized location, all sorts of interesting analytics, and intelligence-driven applications can come into play. For example, subscription data, read/unread status information in of itself can yield rough ranking of popularity. Tagging of content can yield human-driven meta information that democratizes categorization of content coming in from a very large and daunting repository: the web. Popularity ranking and democratized categorization yields personalized, intelligent recommendations and filtering. All these buzz words imply boost to productivity by way of highlighting information we "know" is relevant to us and by way of not spamming us with content that is not.

This subscription-based content-delivery mechanism, when coupled with such intelligence, is a valuable paradigm in an environment plagued with info-glut, tight deadlines, and the ever-present abuse of emails. How many mailgroups do you belong to?

Why not simply invest in more Proxies?

  1. General-purpose proxy servers commonly purchased are extremely expensive - can cost as much as $80,000 per box
  2. Proxies are not specialized to provide the bells and whistles mentioned in the previous section and therefore under-represent the value of syndication within an Enterprise environment

Taking it a step further
Let's imagine that we have this piece of infrastructure sitting in-house, that allows us to aggregate, categorize, rank and disseminate content not only intelligently but also via a standardized and globally-acceptable content format: RSS (XML). When the Firm federates this system with already-existing and already invested-in internal Collaborative Platforms, syndicated, relevant content does not have to originate from traditional "news" sources alone. In fact, such syndication can expose internally generated data of all sorts. Think improved portal adoption through Dashboarding, Agile Project Management and Knowledge Bases, system status notifications and so on. For specific ideas on how to leverage Blogging and syndication for Agile Product Management and Knowledge Bases, take a look at my Blog's series called "Taking Baby Steps".

Taking it one more step further
RSS in the Enterprise can drive Social discovery of peer-recommended and peer-generated content by virtue of mining this centralized repository of content and subscriptions. Cleverly federated with Collaborative systems like Sharepoint, the Enterprise RSS Server can socialize boring document repositories and lists in Sharepoint. Imagine "following" content generated, reviewed and starred by the head of your department. Newsfeeds of social activity made popular by Facebook can be easily constructed by marrying Enterprise RSS infrastructure with Collaborative infrastructure such as Sharepoint. And this is only the beginning... For example, semantic analysis of subscriptions and consumed content can yield better understanding of relevancy and quality of recommendations as well as intelligent filters and subscriptions.

Conclusion
Centralized, enterprise-class RSS infrastructure is an enabler of more than news consumption. Social, asynchronous feedback loops are critical aspects of collaboration and are made possible by investing in RSS as infrastructure.

Vendors such as NewsGator, Attensa, and KnowNow are currently leading the market in providing the Enterprise with a "Buy not Build" option for exactly such infrastructure. Who of these "gets" Enterprise requirements best? I urge you to compare their respective offerings against specs outlined in my Blog Post entitled "Why Vendors Don't Get Enterprise2.0". I have. Not surprisingly, when compared against core requirements and the extra credit, as outlined above, competition among the three is not as close as analysts and the laggers of the three would like you to believe...

Wednesday, April 2, 2008

NYBI Meetup #2 Recap

The Recap
The second NYBI Meetup took place in a spacious board room of 1633 Broadway. The meeting kicked off with an exploration of QlikTech's in-memory analytics platform called QlikView with Scott Taylor and Nik Boman. QlikView is seeing success in the financial sector in NY and we wanted to know what made QlikView tick, what made it great, and how the "wisdom" of the NYBI crowd could make it even better. The PPT presentation of QlikView is available from the NYBIMeetup.org's File download section here.

From my recollections and sparse notes
QlikView is a visualization and analysis tool.

QlikView allows a Super User to correlate data sets from various data sources (local or remote) and to store a highly compressed (loaded data is 15% on avg of original data size) snapshot of the relational data map into memory. Coupled with a file structure that permits O(1) access to data and linear, O(n) traversal of data sets, this in-memory storage essentially serves as a Virtual Cube of correlated data for the user to play with. A very rich interface that is available in client, or embedded form, permits almost serendipitous creation of dashboards with parametric drilling, intelligent filtering - the works. Controls adjust themselves and each other instantaneously - the Regular User interacts with the charts through intuitive click and drag motions. The assumption is that the data loaded into QlikView is already of high quality/integrity.When asked "Who is typically the first person among client prospects to go 'Oooh! Aaaah!", response from the QlikTech guys was "Deprived end-users that have to go to IT for cube build-outs that may take months to generate 'ooh and aah'. IT that sanctions ownership supervises, scrutinizes, realizes that QlikView is a boon to them."

Challenges to QlikTech and points of Discussion
  1. Is not the goal of BI, actionable outputs? Hypothetically, with QlikView I found my 3 bad products and I want to commit the pricing strategy - I have to write something out/communicate - If I want to do that, I have to interact with other modules (beyond BI modules) to make actions, how do you make the action happen? Should the products not better integrate into popular desktop productivity apps? For example, how can my interaction with these dashboard be affected if I could key off of an email address to view a Sales Report for that person in a sidebar within Outlook? (reference to Xobni). As a platform, QlikView has potential for such rich integration - the exercise is left for IT or QlikTech's affiliates.
  2. What kind of API should BI platforms expose? What do we really wish to do on the Desktop vs on the Server?
  3. What about community contributions? A community of business partners and affiliates who align to complete a vertical is not necessarily the same things as, let's say, a community of developers writing and contributing extensions to enhance a product's core (i.e. Firefox community and extensions development). Is there value for BI vendors to stretch beyond affiliate networks in order to establish an open participation and innovation culture? With respect to NYBI Meetup, what would something like that look like?

Truths and Trends
Following above rhetoric, in turn, should not NYBI Meetup be about actionable outputs? After all, a Community has to deliver value to those it serves. One baby step forward was to conclude the meetup with "Can we derive any Truths or Trends about BI from topics covered in discussion?" Here is what we came up with:
  1. Truth: People are looking for a better way to solve the same problems. Core Issues of BI are still prevalent. Recent advances in BI tech now provide better ways to deal with these Issues. QlikView is a note-worthy example of such innovation in the space.
  2. In-memory processing is Trend.
  3. RIA (AJAX) adoption and movement to web interfaces is a Trend.
  4. Truth: Data Quality is a Foundation for BI
  5. Truth: BI Tools need to consume WebServices as a datasource
  6. Truth: Killer App = functionality + price
Next NYBI Meetup is at end of April
We are always looking for interesting presentations. To sign up, please visit http://nybimeetup.org.

Tuesday, March 25, 2008

FreeMind Flash Browser Google Gadget

I finally got around to writing a Google Gadget while exploring what a bit of XML and JavaScript can do for Google Sites.

For starters, I decided to bake the FreeMind Flash Browser into a gadget. You can now embed your FreeMind-generated MindMaps into a Google Gadget with my FreeMind Flash Browser Google Gadget. Feedback is welcome - hope you find it useful.

One caveat/limitation. Flash security policy will prevent crossdomain loading of your MindMap. Specifically, the gadget code is hosted on a *.gmodules.com domain. However, most users will wish to upload/embed MindMaps to their Google Sites (sites.google.com) or another Google domain. At the moment, this will not work as Google does not host a Crossdomain Policy file at its root. It is this policy file that allows Flash to load movies across Google's domains. I've posted an inquiry on the Google Sites Help Group to see if there is any traction towards enabling such a policy.

For now, what works for me is:
  1. Upload the MindMap to a hosting account you have control over.
  2. Create a crossdomain.xml policy file:
    <?xml version="1.0"?>
    <!DOCTYPE cross-domain-policy
    SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
    <cross-domain-policy>
    <allow-access-from domain="*.google.com" />
    <allow-access-from domain="*.gmodules.com" />
    <allow-access-from domain="*.googlecode.com" />
    </cross-domain-policy>

    Upload the file to the root location of the domain under which your MindMaps are hosted, i.e. http://mydomain.com/crossdomain.xml
  3. Specify the fully qualified url to the MindMap within the FreeMind Flash Browser Google Gadget initLoadFile setting, i.e. http://mydomain.com/mymaps/mindmap.mm. The gadget should be able to render the map.

Sunday, March 9, 2008

Phun - 2D physics sandbox

Good ol' NYTech meetup. A member just sent out a link to http://www.phun.at - a 2D physics sandbox. Kids, let your imagination run wild!

Phun videos on YouTube: http://www.youtube.com/groups_videos?name=phun

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.

Wednesday, January 23, 2008

Taking Baby Steps (part 4): The Ten-Step Plan

Here it is. Feedback is always welcome. Naturally.
  1. Need a Blog: Pick a Blogging platform but do your best to leverage an in-house platform, if one exists. Please, do not simply download some Blogging software into your work environment. Reach out to your friendly, neighborhood IT dept for guidance.
  2. Put a project into your "lab": Pick a small project that can benefit from improved transparency. By "small" I mean one that is not super critical and one that you have some leeway to experiment with. These tend to be the types of projects where Delivering is your primary responsibility and not Joe's...
  3. Don't overdo it: Establish guidelines about what kind of information is worth "tracking". I've outlined some ideas in my previous posts ("Taking Baby Steps" series). In essence, establish a Knowledge Base of some sort, even if you have to cobble something together. Don't over-engineer your posts. Like a good workout program, keep it lively, keep it real, reduce obstacles so you can sustain.
  4. Dogfood: Spend a few weeks being selfish and commit information to the KB that helps you be successful on your project.
  5. Dangle the carrot: Give the link or RSS Feed to this KB to your manager and your team-mates. Use the content as a basis for your meeting agendas and follow-ups. Work on getting your colleagues to read your post prior to the meetings. One way to do this is to send a plain-old-email with a link to your post an hour ahead of a meeting. Make it clear that the meeting will cover the agenda enumerated there and you'd like to spend less time regurgitating and spend more time decision-making.
  6. Aggressively seek the ever-elusive "manager" comments on your blog. Period. Your efforts will not succeed without a completed Feedback loop. Active participation is key.
  7. Manage expectations: This is not a be all, end all solution. Make sure that your social network is aware of this fact.
  8. Tag it up: Create a taxonomy of "special" Tags and standardize them. That is agree, for example, that all weekly summaries posted to individual product blogs should be Tagged as "weekly" and that all 2-week look-aheads should be Tagged as "iteration".
  9. Roll it up: Construct a Dashboard Tab that may be of value to your manager's manager by rolling up RSS feeds filtered by your "weekly" and "iteration" tags. Get your manager's, manager's manager to acknowledge its usefulness. If you can pull this off, send me an email and I will honor you with the Order of Blogging Ninja.
  10. Road show: Leverage your network to identify like-minded individuals who will benefit from a similar set up. Document the set-up process, best practices and your experiences on a Wiki as you explain the process to your buddy. Encourage your buddy to contribute to this Wiki as (s)he works out the implementation and goes through the process. Rinse and repeat. After a month or two, put together a Forum of sorts and present your findings to your department. Please don't be boring. Then, go to Disney World.
I'll let the 10th point act as the transition into the world of Wikis; coming up next.