From 91cdf9171eafb87e1449e7c5175552052dda2c2c Mon Sep 17 00:00:00 2001 From: Vadim Kurland Date: Tue, 25 May 2010 05:20:25 +0000 Subject: [PATCH] 2010-05-24 vadim * UserWorkflow.cpp (UserWorkflow::report): see #1466 Implemented instrumentation that should help us improve user experience. Will track few things that new users do (or dont) and report as a combination of bit flags at the end of the GUI session. Reporting things such as if user looked at "Getting Started" tutorial, if they created their first firewall object, modified any rules, tried to compile or install. Information passed in the report is strictly a set of boolean flags, is not identifiable and does not reveal what firewall platform they are using or anything about their objects and rules. --- build_num | 2 +- doc/ChangeLog | 13 ++ src/gui/DiscoveryDruid.cpp | 4 + src/gui/FWBApplication.cpp | 41 ++++++ src/gui/FWBApplication.h | 46 +++++++ src/gui/FWBSettings.cpp | 4 + src/gui/FWBSettings.h | 5 +- src/gui/FWCmdRule.cpp | 14 ++ src/gui/FWWindow.cpp | 7 +- src/gui/PrefsDialog.cpp | 28 ++++ src/gui/ProjectPanel.cpp | 11 ++ src/gui/RCS.cpp | 1 + src/gui/RuleSetViewDelegate.cpp | 9 -- src/gui/StartTipDialog.cpp | 1 + src/gui/TutorialDialog.cpp | 3 + src/gui/UserWorkflow.cpp | 140 ++++++++++++++++++++ src/gui/UserWorkflow.h | 108 +++++++++++++++ src/gui/global.h | 6 +- src/gui/gui.pro | 8 +- src/gui/main.cpp | 9 +- src/gui/newFirewallDialog.cpp | 3 + src/gui/newFirewallDialog_from_template.cpp | 3 + src/gui/newGroupDialog.cpp | 1 + 23 files changed, 449 insertions(+), 18 deletions(-) create mode 100644 src/gui/FWBApplication.cpp create mode 100644 src/gui/FWBApplication.h create mode 100644 src/gui/UserWorkflow.cpp create mode 100644 src/gui/UserWorkflow.h diff --git a/build_num b/build_num index 09abba783..d7b1ce0dc 100644 --- a/build_num +++ b/build_num @@ -1 +1 @@ -#define BUILD_NUM 2925 +#define BUILD_NUM 2929 diff --git a/doc/ChangeLog b/doc/ChangeLog index a0e8b36e5..117413d3a 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,16 @@ +2010-05-24 vadim + + * UserWorkflow.cpp (UserWorkflow::report): see #1466 Implemented + instrumentation that should help us improve user experience. Will + track few things that new users do (or don't) and report as a + combination of bit flags at the end of the GUI session. Reporting + things such as if user looked at "Getting Started" tutorial, if + they created their first firewall object, modified any rules, + tried to compile or install. Information passed in the report is + strictly a set of boolean flags, is not identifiable and does not + reveal what firewall platform they are using or anything about + their objects and rules. + 2010-05-23 vadim * FWCmdAddObject.cpp (FWCmdAddObject::redo): fixed #1468 Open new diff --git a/src/gui/DiscoveryDruid.cpp b/src/gui/DiscoveryDruid.cpp index a326f0e7d..a39f2eb96 100644 --- a/src/gui/DiscoveryDruid.cpp +++ b/src/gui/DiscoveryDruid.cpp @@ -82,6 +82,7 @@ #include "fwbuilder/Logger.h" #include "FWBSettings.h" +#include "UserWorkflow.h" #include "ObjectManipulator.h" #include "FWWindow.h" @@ -776,6 +777,9 @@ void DiscoveryDruid::startConfigImport() thread = new ConfigImport(buffer, platform); thread->setTargetWidget(this); thread->start(); + + wfl->registerEvent(UserWorkflow::IMPORT); + } else { QMessageBox::critical(this, tr("Discovery error"), diff --git a/src/gui/FWBApplication.cpp b/src/gui/FWBApplication.cpp new file mode 100644 index 000000000..ad353aaed --- /dev/null +++ b/src/gui/FWBApplication.cpp @@ -0,0 +1,41 @@ +/* + + Firewall Builder + + Copyright (C) 2010 NetCitadel, LLC + + Author: Vadim Kurland vadim@fwbuilder.org + + $Id$ + + This program is free software which we release under the GNU General Public + License. You may redistribute and/or modify this program under the terms + of that license as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + To get a copy of the GNU General Public License, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + + +#include "global.h" +#include "UserWorkflow.h" +#include "FWBSettings.h" +#include "FWBApplication.h" + +#include + + +void FWBApplication::quit() +{ + if (st->getCheckUpdates()) wfl->report(); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 10000); + QApplication::quit(); +} + diff --git a/src/gui/FWBApplication.h b/src/gui/FWBApplication.h new file mode 100644 index 000000000..969ff224e --- /dev/null +++ b/src/gui/FWBApplication.h @@ -0,0 +1,46 @@ +/* + + Firewall Builder + + Copyright (C) 2010 NetCitadel, LLC + + Author: Vadim Kurland vadim@fwbuilder.org + + $Id$ + + This program is free software which we release under the GNU General Public + License. You may redistribute and/or modify this program under the terms + of that license as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + To get a copy of the GNU General Public License, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +#ifndef __FWBAPPLICATION_H_ +#define __FWBAPPLICATION_H_ + +#include + + +class FWBApplication : public QApplication { + + Q_OBJECT; + +public: + FWBApplication(int &argc, char **argv) : QApplication(argc, argv) {} + +public slots: + + void quit(); + +}; + +#endif + diff --git a/src/gui/FWBSettings.cpp b/src/gui/FWBSettings.cpp index 1bfde07dc..10ff8b6da 100644 --- a/src/gui/FWBSettings.cpp +++ b/src/gui/FWBSettings.cpp @@ -80,6 +80,7 @@ const char* groupColsSetpath = SETTINGS_PATH_PREFIX "/UI/GroupViewColumns"; const char* objTooltips = SETTINGS_PATH_PREFIX "/UI/objTooltips"; const char* tooltipDelay = SETTINGS_PATH_PREFIX "/UI/tooltipDelay"; const char* showUndoPanel = SETTINGS_PATH_PREFIX "/UI/showUndoPanel"; +const char* userWorkflowFlags = SETTINGS_PATH_PREFIX "/UI/userWorkFlowFlags"; const char* emptyRCSLog = SETTINGS_PATH_PREFIX "/RCS/emptyLog"; const char* rcsFilePreviewStyle = SETTINGS_PATH_PREFIX "/RCS/FilePreviewStyle"; @@ -396,6 +397,9 @@ void FWBSettings::setDontSaveStdLib( bool f) { setValue(dontSaveStdLib,f);} bool FWBSettings::getShowUndoPanel() {return value(showUndoPanel).toBool();} void FWBSettings::setShowUndoPanel(bool f) {setValue(showUndoPanel, f);} +int FWBSettings::getUserWorkflowFlags() {return value(userWorkflowFlags).toInt();} +void FWBSettings::setUserWorkflowFlags(int f) {setValue(userWorkflowFlags, f);} + bool FWBSettings::haveScreenPosition(const QString &wname) { QString val = value(QString(screenPositionSetpath)+wname ).toString(); diff --git a/src/gui/FWBSettings.h b/src/gui/FWBSettings.h index 61579bc8c..30c1c6cdc 100644 --- a/src/gui/FWBSettings.h +++ b/src/gui/FWBSettings.h @@ -241,7 +241,10 @@ class FWBSettings : public QSettings { bool getShowUndoPanel(); void setShowUndoPanel(bool); - + int getUserWorkflowFlags(); + void setUserWorkflowFlags(int); + + private: QFont getFontByType(const char*type); }; diff --git a/src/gui/FWCmdRule.cpp b/src/gui/FWCmdRule.cpp index 5a187b2e0..e206a2cb5 100644 --- a/src/gui/FWCmdRule.cpp +++ b/src/gui/FWCmdRule.cpp @@ -28,6 +28,7 @@ #include "FWWindow.h" #include "FWCmdRule.h" #include "FindObjectWidget.h" +#include "UserWorkflow.h" #include "events.h" @@ -73,6 +74,7 @@ void FWCmdRule::notify() void FWCmdRule::redo() { + if (!wfl->checkEvent(UserWorkflow::RULE_MOD)) wfl->registerEvent(UserWorkflow::RULE_MOD); RuleSetModel* md = getRuleSetModel(); redoOnModel(md); notify(); @@ -112,6 +114,7 @@ FWCmdRuleInsert::~FWCmdRuleInsert() void FWCmdRuleInsert::redoOnModel(RuleSetModel *md) { + if (!wfl->checkEvent(UserWorkflow::RULE_MOD)) wfl->registerEvent(UserWorkflow::RULE_MOD); if (insertedRule == 0) { if (ruleToInsert == 0) { @@ -212,6 +215,7 @@ FWCmdRuleDelete::~FWCmdRuleDelete() void FWCmdRuleDelete::redoOnModel(RuleSetModel *md) { + if (!wfl->checkEvent(UserWorkflow::RULE_MOD)) wfl->registerEvent(UserWorkflow::RULE_MOD); if (fwbdebug) qDebug() << "FWCmdRuleDelete::redoOnModel(RuleSetModel *md)"; foreach(Rule* rule, rulesToDelete) @@ -269,6 +273,8 @@ void FWCmdRuleColor::redoOnModel(RuleSetModel *md) { QModelIndexList indexes; + if (!wfl->checkEvent(UserWorkflow::RULE_MOD)) wfl->registerEvent(UserWorkflow::RULE_MOD); + foreach(int ruleId, oldColors.keys()) { Rule* rule = Rule::cast(getObject(ruleId)); @@ -309,6 +315,7 @@ FWCmdRuleMove::FWCmdRuleMove(ProjectPanel *project, libfwbuilder::RuleSet* rules void FWCmdRuleMove::redoOnModel(RuleSetModel *md) { + if (!wfl->checkEvent(UserWorkflow::RULE_MOD)) wfl->registerEvent(UserWorkflow::RULE_MOD); move(md, direction); } @@ -356,6 +363,7 @@ FWCmdRuleRenameGroup::FWCmdRuleRenameGroup(ProjectPanel *project, RuleSet* rules void FWCmdRuleRenameGroup::redoOnModel(RuleSetModel *md) { + if (!wfl->checkEvent(UserWorkflow::RULE_MOD)) wfl->registerEvent(UserWorkflow::RULE_MOD); QModelIndex grp = md->index(oldName); md->renameGroup(grp, newName); } @@ -378,6 +386,7 @@ FWCmdRuleRemoveFromGroup::FWCmdRuleRemoveFromGroup(ProjectPanel* project, RuleSe void FWCmdRuleRemoveFromGroup::redoOnModel(RuleSetModel *md) { + if (!wfl->checkEvent(UserWorkflow::RULE_MOD)) wfl->registerEvent(UserWorkflow::RULE_MOD); QModelIndex group = md->index(groupName); QModelIndex first = md->index(firstRule, 0); QModelIndex last = md->index(lastRule, 0); @@ -421,6 +430,7 @@ FWCmdRuleNewGroup::FWCmdRuleNewGroup(ProjectPanel* project, RuleSet* ruleset, Ru void FWCmdRuleNewGroup::redoOnModel(RuleSetModel *md) { + if (!wfl->checkEvent(UserWorkflow::RULE_MOD)) wfl->registerEvent(UserWorkflow::RULE_MOD); QModelIndex first = md->index(firstRule, 0); QModelIndex last = md->index(lastRule, 0); QModelIndex index = md->createNewGroup(groupName, first.row(), last.row()); @@ -449,6 +459,7 @@ FWCmdRuleAddToGroup::FWCmdRuleAddToGroup(ProjectPanel* project, RuleSet* ruleset void FWCmdRuleAddToGroup::redoOnModel(RuleSetModel *md) { + if (!wfl->checkEvent(UserWorkflow::RULE_MOD)) wfl->registerEvent(UserWorkflow::RULE_MOD); QModelIndex first = md->index(firstRule, 0); QModelIndex last = md->index(lastRule, 0); @@ -481,6 +492,7 @@ void FWCmdRuleChange::selectAffectedRule() void FWCmdRuleChange::redo() { + if (!wfl->checkEvent(UserWorkflow::RULE_MOD)) wfl->registerEvent(UserWorkflow::RULE_MOD); prepareRuleSetView(); FWCmdChange::redo(); selectAffectedRule(); @@ -618,6 +630,8 @@ FWCmdRuleNegateRE::FWCmdRuleNegateRE(ProjectPanel *project, void FWCmdRuleNegateRE::redo() { + if (!wfl->checkEvent(UserWorkflow::RULE_MOD)) wfl->registerEvent(UserWorkflow::RULE_MOD); + prepareRuleSetView(); RuleElement* ruleElement = RuleElement::cast(getObject()); ruleElement->toggleNeg(); diff --git a/src/gui/FWWindow.cpp b/src/gui/FWWindow.cpp index 173f45564..cf9028d90 100644 --- a/src/gui/FWWindow.cpp +++ b/src/gui/FWWindow.cpp @@ -34,6 +34,7 @@ #include #include +#include "FWBApplication.h" #include "FWWindow.h" #include "ProjectPanel.h" #include "ObjectTreeView.h" @@ -41,6 +42,7 @@ #include "FWObjectClipboard.h" #include "FWBTree.h" #include "FWBSettings.h" +#include "UserWorkflow.h" #include "FWObjectPropertiesFactory.h" #include "upgradePredicate.h" #include "ObjConflictResolutionDialog.h" @@ -751,7 +753,7 @@ void FWWindow::fileExit() st->setInt("Window/maximized", window_maximized_state); - qApp->quit(); + app->quit(); } void FWWindow::toolsDiscoveryDruid() @@ -1356,6 +1358,9 @@ void FWWindow::showSummary() */ void FWWindow::showTutorial(QString tutorial) { + if (fwbdebug) + qDebug() << "FWWindow::showTutorial:" << tutorial; + TutorialDialog *dialog; if (tutorial.isEmpty()) dialog= new TutorialDialog( diff --git a/src/gui/PrefsDialog.cpp b/src/gui/PrefsDialog.cpp index 3fa82dce4..7eb5311b1 100644 --- a/src/gui/PrefsDialog.cpp +++ b/src/gui/PrefsDialog.cpp @@ -38,6 +38,7 @@ #include "ProjectPanel.h" #include "HttpGet.h" #include "RuleSetView.h" +#include "UserWorkflow.h" #include "fwbuilder/Resources.h" @@ -466,6 +467,25 @@ void PrefsDialog::accept() st->setCompilerOutputFont(compilerOutputFont); st->setClipComment(m_dialog->chClipComment->isChecked()); + + + if (m_dialog->checkUpdates->isChecked()) + { + wfl->clearEvent(UserWorkflow::UPDATE_CHECKS_DISABLED); + } else + { + wfl->registerEvent(UserWorkflow::UPDATE_CHECKS_DISABLED); + + if (st->getCheckUpdates()) + { + // update checking was enabled but the user disabled it in + // this preferences dialog session. Run last closing + // report before disabling both update check and closing + // report. + wfl->report(); + } + } + st->setCheckUpdates(m_dialog->checkUpdates->isChecked()); st->setCheckUpdatesProxy(m_dialog->checkUpdatesProxy->text()); @@ -507,7 +527,15 @@ void PrefsDialog::accept() mw->showDeletedObjects(st->getBool("UI/ShowDeletedObjects")); mw->updateTreeFont(); // app->setFont(st->getTreeFont()); + + if (m_dialog->showTips->isChecked()) + wfl->clearEvent(UserWorkflow::TIP_OF_THE_DAY_DISABLED); + else + wfl->registerEvent(UserWorkflow::TIP_OF_THE_DAY_DISABLED); + st->setBool("UI/NoStartTip", m_dialog->showTips->isChecked()); + + QDialog::accept(); } diff --git a/src/gui/ProjectPanel.cpp b/src/gui/ProjectPanel.cpp index 93921a881..9322e90bc 100644 --- a/src/gui/ProjectPanel.cpp +++ b/src/gui/ProjectPanel.cpp @@ -39,6 +39,7 @@ #include "fwbuilder/RuleElement.h" #include "FWBSettings.h" +#include "UserWorkflow.h" #include "FWBTree.h" #include "FWObjectPropertiesFactory.h" #include "FWWindow.h" @@ -735,6 +736,9 @@ void ProjectPanel::addRule() void ProjectPanel::compileThis() { if (visibleRuleSet==NULL) return ; + + wfl->registerEvent(UserWorkflow::COMPILE); + set fw; Firewall *f = Firewall::cast(visibleRuleSet->getParent()); if (f) @@ -747,6 +751,9 @@ void ProjectPanel::compileThis() void ProjectPanel::installThis() { if (visibleRuleSet==NULL) return ; + + wfl->registerEvent(UserWorkflow::INSTALL); + set fw; Firewall *f = Firewall::cast(visibleRuleSet->getParent()); if (f) @@ -763,6 +770,7 @@ void ProjectPanel::compile() return; fileSave(); + wfl->registerEvent(UserWorkflow::COMPILE); mainW->compile(); } @@ -773,16 +781,19 @@ void ProjectPanel::compile(set vf) return; fileSave(); + wfl->registerEvent(UserWorkflow::COMPILE); mainW->compile(vf); } void ProjectPanel::install(set vf) { + wfl->registerEvent(UserWorkflow::INSTALL); mainW->install(vf); } void ProjectPanel::install() { + wfl->registerEvent(UserWorkflow::INSTALL); mainW->install(); } diff --git a/src/gui/RCS.cpp b/src/gui/RCS.cpp index 248b36acd..50844c430 100644 --- a/src/gui/RCS.cpp +++ b/src/gui/RCS.cpp @@ -28,6 +28,7 @@ #include "utils.h" #include "utils_no_qt.h" +#include "FWBApplication.h" #include "RCS.h" // need this for FS_SEPARATOR diff --git a/src/gui/RuleSetViewDelegate.cpp b/src/gui/RuleSetViewDelegate.cpp index 352c503fb..c37a97faa 100644 --- a/src/gui/RuleSetViewDelegate.cpp +++ b/src/gui/RuleSetViewDelegate.cpp @@ -117,10 +117,6 @@ void RuleSetViewDelegate::paintGroup(QPainter *painter, const QStyleOptionViewIt void RuleSetViewDelegate::drawIcons(QPainter *painter, QRect rect, const QStringList &icons) const { - if (fwbdebug) - qDebug() << "RuleSetViewDelegate::drawIcons" - << "icons.size()=" << icons.size(); - int x = rect.left(); int y = rect.top(); int iconWidth = 0; @@ -129,11 +125,6 @@ void RuleSetViewDelegate::drawIcons(QPainter *painter, QRect rect, { QPixmap pm; QString icon = calculateIconName(icons[i], false); - if (fwbdebug) - qDebug() << "i=" << i - << "icons[i]=" << icons[i] - << "icon=" << icon; - LoadPixmap(icon, pm); painter->drawPixmap(x, y, pm); iconWidth = pm.width(); diff --git a/src/gui/StartTipDialog.cpp b/src/gui/StartTipDialog.cpp index 65d8047c0..ac514f53d 100644 --- a/src/gui/StartTipDialog.cpp +++ b/src/gui/StartTipDialog.cpp @@ -32,6 +32,7 @@ #include "FWBSettings.h" #include "FWWindow.h" #include "Help.h" +#include "UserWorkflow.h" #include #include diff --git a/src/gui/TutorialDialog.cpp b/src/gui/TutorialDialog.cpp index 953c60db1..ee362bd1b 100644 --- a/src/gui/TutorialDialog.cpp +++ b/src/gui/TutorialDialog.cpp @@ -27,6 +27,7 @@ #include "TutorialDialog.h" #include "ui_TutorialDialog.h" +#include "UserWorkflow.h" #include #include @@ -55,6 +56,8 @@ TutorialDialog::TutorialDialog(QString tutorial, QWidget *parent) : this->setWindowFlags(this->windowFlags() | Qt::WindowStaysOnTopHint); //this->setWindowModality(Qt::ApplicationModal); showPage(currentPage); + + wfl->registerTutorialViewing(tutorial); } void TutorialDialog::resizeEvent(QResizeEvent *) diff --git a/src/gui/UserWorkflow.cpp b/src/gui/UserWorkflow.cpp new file mode 100644 index 000000000..e0184afcb --- /dev/null +++ b/src/gui/UserWorkflow.cpp @@ -0,0 +1,140 @@ +/* + + Firewall Builder + + Copyright (C) 2010 NetCitadel, LLC + + Author: Vadim Kurland vadim@fwbuilder.org + + $Id$ + + This program is free software which we release under the GNU General Public + License. You may redistribute and/or modify this program under the terms + of that license as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + To get a copy of the GNU General Public License, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +#include "global.h" + +#include "UserWorkflow.h" +#include "FWBSettings.h" +#include "HttpGet.h" + +#include + +/* + * Create object UserWorkflow only after FWBSettings object has been + * created and initialized. + */ +UserWorkflow::UserWorkflow() +{ + assert(st != NULL); + start_timestamp = QDateTime::currentDateTime(); + report_query = NULL; + int flags = st->getUserWorkflowFlags(); + int f = 1; + for (int i=0; i<32; ++i) + { + if (flags & f) events.insert((enum workflowEvents)(f)); + f = f << 1; + } +} + +UserWorkflow::~UserWorkflow() +{ + if (report_query != NULL) delete report_query; +} + +int UserWorkflow::eventsToInt() +{ + int flags = 0; + foreach(int f, events) flags |= f; + return flags; +} + +bool UserWorkflow::checkEvent(enum workflowEvents e) +{ + return events.contains(e); +} + +void UserWorkflow::registerEvent(enum workflowEvents e) +{ + if (fwbdebug) + qDebug() << "UserWorkflow::registerEvent():" << e; + events.insert(e); + st->setUserWorkflowFlags(eventsToInt()); +} + +void UserWorkflow::clearEvent(enum workflowEvents e) +{ + if (fwbdebug) + qDebug() << "UserWorkflow::clearEvent():" << e; + events.remove(e); + st->setUserWorkflowFlags(eventsToInt()); +} + +void UserWorkflow::registerTutorialViewing(const QString &tutorial_name) +{ + if (tutorial_name == "getting_started") + registerEvent(UserWorkflow::GETTING_STARTED_TUTOTIAL); +} + +void UserWorkflow::report() +{ + uint elapsed_time = QDateTime::currentDateTime().toTime_t() - + start_timestamp.toTime_t(); + + // Note that QTime::elapsed() wraps to zero after ~24hr. If + // program stayed open for over 24 hr, it would return incorrect + // session duration. + + + if (fwbdebug) + { + QString s("%1"); + qDebug() << "UserWorkflow::report():" << s.arg(eventsToInt(), 0, 16); + qDebug() << "Session:" << elapsed_time << "sec"; + } + + report_query = new HttpGet(); + connect(report_query, SIGNAL(done(const QString&)), + this, SLOT(reportDone(const QString&))); + + QString report_url = CLOSING_REPORT_URL; + + // Use env variable FWBUILDER_CLOSING_REPORT_URL to override url to test + // e.g. export FWBUILDER_CLOSING_REPORT_URL="file://$(pwd)/report_%1" + // + char* report_override_url = getenv("FWBUILDER_CLOSING_REPORT_URL"); + if (report_override_url != NULL) + report_url = QString(report_override_url); + + // start http query to get latest version from the web site + QString url = QString(report_url) + .arg(VERSION).arg(st->getAppGUID()).arg(eventsToInt()); + if (!report_query->get(url) && fwbdebug) + { + qDebug() << "HttpGet error: " << report_query->getLastError(); + qDebug() << "Url: " << url; + } +} + +void UserWorkflow::reportDone(const QString& resp) +{ + if (fwbdebug) qDebug() << "UserWorkflow::reportDone" << resp; + + disconnect(report_query, SIGNAL(done(const QString&)), + this, SLOT(reportDone(const QString&))); + // we ignore server response for the closing reports. + if (report_query != NULL) delete report_query; + report_query = NULL; +} diff --git a/src/gui/UserWorkflow.h b/src/gui/UserWorkflow.h new file mode 100644 index 000000000..e82b1806f --- /dev/null +++ b/src/gui/UserWorkflow.h @@ -0,0 +1,108 @@ +/* + + Firewall Builder + + Copyright (C) 2010 NetCitadel, LLC + + Author: Vadim Kurland vadim@fwbuilder.org + + $Id$ + + This program is free software which we release under the GNU General Public + License. You may redistribute and/or modify this program under the terms + of that license as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + To get a copy of the GNU General Public License, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +#ifndef __USERWORKFLOW_H_ +#define __USERWORKFLOW_H_ + +#include +#include +#include + +#include "HttpGet.h" + +/* + * We track certain events in the new user workflow to optimize user + * experience. This helps us understand things such as if "Getting + * Started" tutorial helps convert users that just experiment with the + * program into active users. We would like to know at what point + * users abandon the program after they launch it for the first + * time. We keep a boolean flag for each event in the settings (to + * make it presistent) and report combination of the flags when the + * GUI is closed. + * + * To report workflow statistics, the program connects to our web + * server when user closes the program. If update checking is disabled + * in the global preferences, this closing report is disabled as + * well. + * + * Connection sends the following data to the server: + * + * version, uuid and a combination of bit flags of the following (each + * flag is set once when corresponding even happens for the first + * time; flag is not updated ever since) + * + * - user disabled version update and closing reports checks (sent once when checks and reports are disabled) + * - user looked at the "Getting Started" tutorial at least once + * - user created their first firewall object using template + * - user created their first firewall object without template + * - user did their first rule modification of any kind + * - user ran their first policy compile + * - user ran their first policy install + * - user ran their first policy import + * + */ + +#define CLOSING_REPORT_URL \ + "http://update.fwbuilder.org/reports/closing.cgi?v=%1&uuid=%2&flags=%3" + +class UserWorkflow : public QObject { + + Q_OBJECT; + +public: + enum workflowEvents { UPDATE_CHECKS_DISABLED = 0, + GETTING_STARTED_TUTOTIAL = 1, + NEW_FW_WITH_TEMPLATE = 2, + NEW_FW_NO_TEMPLATE = 4, + RULE_MOD = 8, + COMPILE = 16, + INSTALL = 32, + IMPORT = 64, + TIP_OF_THE_DAY_DISABLED = 128, + }; + +private: + QDateTime start_timestamp; + QSet events; + HttpGet *report_query; + + int eventsToInt(); + +public: + UserWorkflow(); + virtual ~UserWorkflow(); + bool checkEvent(enum workflowEvents e); + void registerEvent(enum workflowEvents e); + void clearEvent(enum workflowEvents e); + void registerTutorialViewing(const QString &tutorial_name); + void report(); + +public slots: + void reportDone(const QString&); + +}; + +#endif + diff --git a/src/gui/global.h b/src/gui/global.h index 4426a05dc..4f6cacf5a 100644 --- a/src/gui/global.h +++ b/src/gui/global.h @@ -32,13 +32,15 @@ #include class QString; -class QApplication; +class FWBApplication; class FWWindow; class FWBSettings; +class UserWorkflow; -extern QApplication *app; +extern FWBApplication *app; extern FWWindow *mw; extern FWBSettings *st; +extern UserWorkflow *wfl; extern std::string appRootDir; extern std::string userDataDir; diff --git a/src/gui/gui.pro b/src/gui/gui.pro index e741f1d0b..163b2c403 100644 --- a/src/gui/gui.pro +++ b/src/gui/gui.pro @@ -177,7 +177,9 @@ HEADERS += ../../config.h \ UsageResolver.h \ IconSetter.h \ TutorialDialog.h \ - MDIEventFilter.h + MDIEventFilter.h \ + UserWorkflow.h \ + FWBApplication.h SOURCES += ProjectPanel.cpp \ ProjectPanel_events.cpp \ @@ -354,7 +356,9 @@ SOURCES += ProjectPanel.cpp \ IconSetter.cpp \ UsageResolver.cpp \ TutorialDialog.cpp \ - MDIEventFilter.cpp + MDIEventFilter.cpp \ + UserWorkflow.cpp \ + FWBApplication.cpp FORMS = FWBMainWindow_q.ui \ compileroutputpanel_q.ui \ diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 151537666..4cdbb1891 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -51,6 +51,7 @@ #include #include +#include "FWBApplication.h" #include "FWBSettings.h" #include "RCS.h" #include "FWWindow.h" @@ -61,6 +62,7 @@ #include "findDialog.h" #include "ProjectPanel.h" #include "RCS.h" +#include "UserWorkflow.h" #include "fwbuilder/FWObject.h" #include "fwbuilder/Tools.h" @@ -87,9 +89,10 @@ using namespace std; static QString filename; static QString print_output_file_name; bool auto_load_from_rcs_head_revision = false; -QApplication *app = NULL; +FWBApplication *app = NULL; FWWindow *mw = NULL; FWBSettings *st = NULL; +UserWorkflow *wfl = NULL; int fwbdebug = 0; bool safemode = false; bool cli_print = false; @@ -154,7 +157,7 @@ int main( int argc, char *argv[] ) Q_INIT_RESOURCE(MainRes); - app = new QApplication( argc, argv ); + app = new FWBApplication( argc, argv ); app->setOrganizationName(QLatin1String("NetCitadel")); app->setApplicationName(QLatin1String("Firewall Builder")); @@ -170,6 +173,8 @@ int main( int argc, char *argv[] ) st->init(); if (fwbdebug) qDebug("done"); + wfl = new UserWorkflow(); + QPixmapCache::setCacheLimit(4096); INIT2; diff --git a/src/gui/newFirewallDialog.cpp b/src/gui/newFirewallDialog.cpp index a27758502..ba22245a9 100644 --- a/src/gui/newFirewallDialog.cpp +++ b/src/gui/newFirewallDialog.cpp @@ -35,6 +35,7 @@ #include "FWBSettings.h" #include "FWBTree.h" #include "events.h" +#include "UserWorkflow.h" #include "fwbuilder/Library.h" #include "fwbuilder/Firewall.h" @@ -852,6 +853,8 @@ void newFirewallDialog::finishClicked() } else { + wfl->registerEvent(UserWorkflow::NEW_FW_NO_TEMPLATE); + // Create from interface list (obtained either manually or via snmp) if ( !this->m_dialog->interfaceEditor1->isValid() ) return; diff --git a/src/gui/newFirewallDialog_from_template.cpp b/src/gui/newFirewallDialog_from_template.cpp index e4ac68b4b..09d5775c2 100644 --- a/src/gui/newFirewallDialog_from_template.cpp +++ b/src/gui/newFirewallDialog_from_template.cpp @@ -29,6 +29,7 @@ #include "newFirewallDialog.h" #include "FWBSettings.h" +#include "UserWorkflow.h" #include "FWBTree.h" #include "InterfaceEditorWidget.h" #include "InterfacesTabWidget.h" @@ -72,6 +73,8 @@ class FindNetwork : public FWObjectFindPredicate void newFirewallDialog::createFirewallFromTemplate() { + wfl->registerEvent(UserWorkflow::NEW_FW_WITH_TEMPLATE); + QListWidgetItem *itm = m_dialog->templateList->currentItem(); FWObject *template_fw=templates[itm]; assert (template_fw!=NULL); diff --git a/src/gui/newGroupDialog.cpp b/src/gui/newGroupDialog.cpp index dc7cebd6b..98ebd1ea7 100644 --- a/src/gui/newGroupDialog.cpp +++ b/src/gui/newGroupDialog.cpp @@ -28,6 +28,7 @@ #include "global.h" #include "utils.h" +#include "FWBApplication.h" #include "newGroupDialog.h" #include