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.

Friday, October 12, 2007

Creating backups of Folders with C#.NET

Zipping up folders for backup purposes with C# in .NET2 is painful. Using 3rd party libs is bulky. CodeProject and Geralrd Gibson Jr & Friends to the Rescue. And because regurgitation is useful for later retrieval, here is the "Silent Mode" code snippet that works for me:

internal void Backup()
{
LogHelper.Info(String.Format("Backing up Folder to {0}...", Config.General.ApplicationDataDir));
string backupFolder = String.Concat(Config.General.ApplicationDataDir,Config.General.DirectorySeparatorStr,"Backup");
string backupZip = String.Concat(backupFolder,Config.General.DirectorySeparatorStr,"Backup_",DateTime.Now.ToFileTime(),".zip");
if(!Directory.Exists(backupFolder)) Directory.CreateDirectory(backupFolder);

byte[] emptyzip = new byte[]{80,75,5,6,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

FileStream fs = File.Create(backupZip);
fs.Write(emptyzip, 0, emptyzip.Length);
fs.Flush();
fs.Close();
fs = null;

//Copy a folder and its contents into the newly created zip file
Shell32.ShellClass sc = new Shell32.ShellClass();
Shell32.Folder SrcFlder = sc.NameSpace(Config.General.LocalRootPath);
Shell32.Folder DestFlder = sc.NameSpace(backupZip);
//Shell32.FolderItems items = SrcFlder.Items();
//1564 =
//+ 4 Do not display a progress dialog box.
//+ 16 Click "Yes to All" in any dialog box displayed.
//+ 512 Do not confirm the creation of a new directory if the operation requires one to be created.
//+ 1024 Do not display a user interface if an error occurs.
DestFlder.CopyHere(SrcFlder, 1564);
}

Monday, September 24, 2007

Taking Baby Steps (part 3): Dashboards - A managerial love story

Overview
Part 2 of this series "Taking Baby Steps (part 2): Only one person reads my Blog and I've already won" discussed some ways in which posts to personal, work-related Blogs (aka Knowledge Bases) are relevant to our team-mates and managers. Additionally, techniques on targeting posts through use of Tags were also presented.

In this 3rd post, I will talk about Dashboards, what they're for and how to set them up.

The Basic Unit of Information about which people relate at work
Projects.

Just the "sweet spot", please
There is an inverse relationship between someone's social proximity (in a "Social Degrees of Separation" sense) and their
  1. Tolerance for Irrelevant Information
  2. Amount of time willingly spent extrapolating "important" information
That is, the farther apart two people are in a socio-professional environment, the smaller their overlap of interests. Let's say 70% of my time is dedicated to Project A and 30% to Project B. My colleague Bob also dedicates 30% of his time to Project B. What this means is that out of all the information and knowledge I deal with on a daily basis, only 30% of it is relevant to Bob and I only have 30% of my time available to share this information.

Furthermore, depending on the roles being played, it is important to understand which 30% of Project B Bob focuses on. If Bob is the Project Manager, odds are that the details of your application's algorithm beyond a statement that "it works" is not very relevant to Bob. Including Bob in day-to-day, back-and-forth conversation about algorithm optimizations negatively impacts Bobs productivity given his slice of the pie. This should not be interpreted as "Bob should be left in the dark for the other 70%". Rather:

  • Amount of information sent through the pipe is important.
  • Quality of information is very important
  • Order of information is also important
Some elaboration. For example, most people skim through email until something of importance catches their eye. Then they will read around that which jumped out. This is an act of validation. The reader is Gauging and Confirming Level of Relevancy. As was stated earlier, we all have a threshold. Whether this is acknowledged or not, we all associate Quality of information relayed to us with the originator of the communication. We even have functionality built into our email clients to reward those sources of information that we believe to consistently relay High Quality information. People whose communications I wish to help penetrate through "my" noise get their own Mail Folder and a Rule. The Folder makes it into my Top 5 and so on. Rather than skimming I tend to read these communications "normally". High Quality/Highly Relevant information from the originator creates a level of Trust. Perhaps its a bit too cliche to claim that Trust allows for solid Feedback Loops. Rather, its very clear that a lack of Trust results in a well stocked Trash.

Ideally, communications to Bob are well targeted bringing the 30% of the project Bob is involved in to the forefront. (Bob thanks me for not wasting his time.)

A not so "Side Note". I really dislike emails that say "FYI" followed by a long thread of bracketed who-knows-what. Figuring out the part that is in fact For My Information will require me to skim not just one email, but all the accompanying threads. Cliff Notes at the top, please!

Information Trickle
Why do "executive" summaries exist everywhere but in our routine communications within our Social Networks? The "Cliff Notes" quip above actually deserves some discussion. Clues about content subject matter and how it pertains to "me" are very helpful because they grant me the opportunity to cash out sooner rather than later. Tags are great but not as great as a one-sentence summary that speaks right to my forehead. Descriptive titles/subjects also help a great deal!

Whether we skim, or jump back and forth as we consume written information, it is still done (for must of us) in a linear way. Pardon the nerdy analogy but this is like a Disk Head tracking back and forth over a Hard Drive. Meta information about files, directories etc in all File Systems is also at the "top".
Search Engines construct indexes that pull out relevant information so that from then on the engine can gauge a match - "this data is relevant" - as quickly and efficiently as possible. There are many other examples of almost identical optimizations that happen in technology because we demand it. Somehow, similar optimizations fail to make it into routine meeting, emails, phone conversations, and so on. It would be nice to have that Trickle from High Level but High Quality information to Low Level details in all mediums of communication.

Dashboards are about creating this kind of Information Trickle for other forms of Project-related information.

Dashboards and RSS Feeds go hand in hand

My Dashboard at work contains the following:
  • Tab 1: 20,000 feet -
    Idea here is to create that High-level but informative "At-A-Glance" View of team-wide activity. This High Level but High Quality landing page is relevant to anyone who may be interested in the activities of the team. For a manager 1 or more Social Degrees away, this is a good place to begin "dipping" into our "world" without being overwhelmed by all the details. For my team-mates, this Tab can serve as the Sync-Point for meeting agendas, follow-ups, Iteration refinement and so on. My team-mates will most likely want/need to drill deeper, the manager 1 or more Social Degrees away will not!

    Specifically, our first Tab contains:
    • Changes to Production - Feeds that describe changes to portions of the "production" environment we are responsible for. This is a feed coming out of our Turnover Control System. It's not RSS at this point but hopefully will be soon.
    • Work plan for the entire team spanning 2 weeks time (this is the Two-week Iteration). This is just an aggregate feed that collates Two-week Iterations from individual product blogs based on the "iteration" Tag.
    • Top 5 Bugs or Issues spanning all our products. This is yet another RSS Feed filtered for items tagged or flagged as "important" from our issue tracking system.
    • Links that allow the reader to subscribe to any of the RSS feeds exposed on the page
  • Tab 2-x: Product-specific pages
    Each of these Tabs is dedicated to a specific Product/Project the team works on. These tabs are a natural "drill-target" from the 1st Tab. My Product Tabs contain:
    • Changes to productions affecting this specific product
    • Iteration posts for this product. This is a regular RSS Feed coming form my Product Blog filtered on the "iteration" tag.
    • News Items or Alerts coming from the Blog or the Product Site. (The Product Site tends to target the end-user/customer. It's not very technical but provides enough insight about the Product's stage in it's life cycle, and the standard "what" and "how")
    • Top 5 Bugs or Issues for this specific product/project
    • Links that allow the reader to subscribe to any of the RSS feeds exposed on the page
RSS Feeds are a natural way to "summarize" content that lives elsewhere. As you can see, no content is actually managed by the Dashboard. The Dashboard simply assembles information into easily consumable pieces. The Trickle is enabled by:
  • At-a-Glance views of tightly-correlated information
  • Ability to stay High-Level yet still obtain more detail. The reader can explore Product-specific Tabs without leaving the context of the Dashboard.
  • Link-backs to the source of information. The reader can drill even deeper by clicking on any item in any of the feeds and be taken to the full "article". The reader does this by choice - this is key.

Technologies at Play
  1. Blogs with RSS capabilities -
    Blogs tend to be more appropriate for Weekly Summaries and Iteration posts. My Blog allows me to publish via email so I don't have to change my habits to post. I publish from my Email Client.
  2. RSS Web Part -
    RSS is not just about getting news from slashdot. It can be a very powerful Glue for information that lives everywhere yet needs to be correlated in a single View.
  3. A portal framework with a Tabs Layout -
    The Sharepoints of the world all have this built in. Acquiring RSS Web Parts is also not too difficult. Some Wikis out there (like Twiki or Confluence) allow you to inject all sorts of content into a single page view, including RSS. These are all basic things. As always, your friendly neighborhood IT department will have some options for you.
How to get started
Naturally, I recommend taking "baby" steps. Most teams will fall into two categories:
  1. A lot of unorganized documentation floating around in multiple formats
  2. All project-related know-how is stored in the brain of the guy two weeks away from retirement
For folks that fall into the 1st group, it's a matter of figuring out the kind of information that is most useful to highlight on a dashboard. Some teams I've worked with went straight to:
  • Top Bugs
  • Testing Results
  • Latest activity around portfolio work for a client
Folks falling into the 2nd category should consider starting with Personal, Ad-hoc Knowledge Bases and expanding out from there.

In general, the type of information that is of Highest Value to your social network should be exposed first. The gains will be emergent and will inspire further investment into not just the Dashboards but also into creating and persisting the High Quality data underneath.

Conclusion and what's up next
Dashboards just front the Collaborative Environment. If the information in Product Blogs, Wikis, etc is not up-to-date, Dashboard's highlights are meaningless. It does take some work and consistency to provide this kind of transparency to your team, your manager and your customers.

Typically, gate-keeping Knowledge Bases and their Dashboards is the responsibility of Team Leads and Product or Project Managers. To get started though, all you need is a Champion, someone who will get the team going, while incrementally proving value. Once your manager is convinced (unless of course you are the manager) some top-down "encouragement" never hurts.

In my next post, I will propose a 10-Step Plan to putting these concepts to practice so we can move on to topics such as Wikis, Issue Tracking, and Interruption Management.

Wednesday, September 19, 2007

Firefox EWG - Meeting #3

Summary
Call on Sept 19th was about useful Extensions for the Enterprise. A larger portion of the meeting, however focused on the apparent dwindling interest in Enterprise Firefox within the larger community.

Is the Enterprise simply not ready to bring Firefox in-house? Or, are the majority of institutional adopters simply happy with a consumer product floating about?

Perhaps one of the problems is that we still talk about Firefox as a Web Browser as opposed to a "Productivity Platform" for the Desktop.

Firefox is a Productivity Platform
All the features that make Firefox a consumer favorite deal with productivity. Tabs, keywords, search bars, extensions, dictionaries, etc help us be effective and productive in our work and home lives. When consumer says "better usability", enterprise says "better productivity".

Mike Kaply spoke about adoption at IBM. Developers are finding ways to enhance the end-user experience by writing custom extensions for Firefox. Whether the extensions glue several applications together or simply automate the tedious process of filling out web-forms, end result is a boost to productivity.

If Firefox is the gateway or glue between what's on the Desktop and what's on the network, potential for productivity-boosting application is something to ponder.

Raising awareness
Mike and I are going to take lead on a few initiatives to help raise awareness on the Enterprise Firefox front:
  • 10 Steps to adopting Firefox in the Enterprise -
    Now that we've begun to pool experiences, we should be able to bake out a definitive guide of sorts for things to consider when bringing Firefox into an enterprise environment
  • Good Ol' Conference -
    We will begin looking at opportunities to present at some Conferences
  • Blogging - check...
  • Code Day and Training -
    The learning curve for any kind of Mozilla-related development is rather high. Perhaps training targeting institutions and enterprises (like an Enterprise track) will help developers scale this wall.
  • Enterprise Firefox Incubator -
    Talk has alway been cheap. As we address settings management, security, tools etc we will kick off projects within the Enterprise Working Group.
Enterprise Working Group Incubator
On my end, I'm going to kick off some projects for the Incubator. Specifically there has been interest in:
  • Mission Control - how to get up and running, what the back-end implementation can look like and benefits over GPO
  • Customized Reporter Extension - so that you can redirect Broken Website reports to a repository on your intranet
  • Managed Security Zones for Firefox - how to configure and lock capabilitiy.policy settings in Firefox, how to create a "trusted" zone and fully leverage Web2.0 technologies within your trusted intranet.
As always, if any of these topics or projects are of interest to you, please visit http://enterprisefirefox.org, participate in the calls or simply leave a comment on this here Blog.