adobe/brackets

View on GitHub
src/utils/HealthLogger.js

Summary

Maintainability
C
7 hrs
Test Coverage
/*
 * Copyright (c) 2015 - present Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/**
 *  Utilities functions related to Health Data logging
 */
/*global Map*/
define(function (require, exports, module) {
    "use strict";

    var PreferencesManager          = require("preferences/PreferencesManager"),
        LanguageManager             = require("language/LanguageManager"),
        FileUtils                   = require("file/FileUtils"),
        PerfUtils                   = require("utils/PerfUtils"),
        FindUtils                   = require("search/FindUtils"),
        StringUtils                 = require("utils/StringUtils"),
        EventDispatcher             = require("utils/EventDispatcher"),

        HEALTH_DATA_STATE_KEY       = "HealthData.Logs",
        logHealthData               = true,
        analyticsEventMap           = new Map();

    var commonStrings = { USAGE: "usage",
        FILE_OPEN: "fileOpen",
        FILE_NEW: "newfile",
        FILE_SAVE: "fileSave",
        FILE_CLOSE: "fileClose",
        LANGUAGE_CHANGE: "languageChange",
        LANGUAGE_SERVER_PROTOCOL: "languageServerProtocol",
        CODE_HINTS: "codeHints",
        PARAM_HINTS: "parameterHints",
        JUMP_TO_DEF: "jumpToDefinition"
    };

    EventDispatcher.makeEventDispatcher(exports);

    /**
     * Init: creates the health log preference keys in the state.json file
     */
    function init() {
        PreferencesManager.stateManager.definePreference(HEALTH_DATA_STATE_KEY, "object", {});
    }

    /**
     * All the logging functions should be disabled if this returns false
     * @return {boolean} true if health data can be logged
     */
    function shouldLogHealthData() {
        return logHealthData;
    }

    /**
     * Return all health data logged till now stored in the state prefs
     * @return {Object} Health Data aggregated till now
     */
    function getStoredHealthData() {
        var storedData = PreferencesManager.getViewState(HEALTH_DATA_STATE_KEY) || {};
        return storedData;
    }

    /**
     * Return the aggregate of all health data logged till now from all sources
     * @return {Object} Health Data aggregated till now
     */
    function getAggregatedHealthData() {
        var healthData = getStoredHealthData();
        $.extend(healthData, PerfUtils.getHealthReport());
        $.extend(healthData, FindUtils.getHealthReport());
        return healthData;
    }

    /**
     * Sets the health data
     * @param {Object} dataObject The object to be stored as health data
     */
    function setHealthData(dataObject) {
        if (!shouldLogHealthData()) {
            return;
        }
        PreferencesManager.setViewState(HEALTH_DATA_STATE_KEY, dataObject);
    }

    /**
     * Returns health data logged for the given key
     * @return {Object} Health Data object for the key or undefined if no health data stored
     */
    function getHealthDataLog(key) {
        var healthData = getStoredHealthData();
        return healthData[key];
    }

    /**
     * Sets the health data for the given key
     * @param {Object} dataObject The object to be stored as health data for the key
     */
    function setHealthDataLog(key, dataObject) {
        var healthData = getStoredHealthData();
        healthData[key] = dataObject;
        setHealthData(healthData);
    }

    /**
     * Clears all the health data recorded till now
     */
    function clearHealthData() {
        PreferencesManager.setViewState(HEALTH_DATA_STATE_KEY, {});
        //clear the performance related health data also
        PerfUtils.clear();
    }

    /**
     * Enable or disable health data logs
     * @param {boolean} enabled true to enable health logs
     */
    function setHealthLogsEnabled(enabled) {
        logHealthData = enabled;
        if (!enabled) {
            clearHealthData();
        }
    }

    /**
     * Whenever a file is opened call this function. The function will record the number of times
     * the standard file types have been opened. We only log the standard filetypes
     * @param {String} filePath          The path of the file to be registered
     * @param {boolean} addedToWorkingSet set to true if extensions of files added to the
     *                                    working set needs to be logged
     */
    function fileOpened(filePath, addedToWorkingSet, encoding) {
        if (!shouldLogHealthData()) {
            return;
        }
        var fileExtension = FileUtils.getFileExtension(filePath),
            language = LanguageManager.getLanguageForPath(filePath),
            healthData = getStoredHealthData(),
            fileExtCountMap = [];
        healthData.fileStats = healthData.fileStats || {
            openedFileExt     : {},
            workingSetFileExt : {},
            openedFileEncoding: {}
        };
        if (language.getId() !== "unknown") {
            fileExtCountMap = addedToWorkingSet ? healthData.fileStats.workingSetFileExt : healthData.fileStats.openedFileExt;
            if (!fileExtCountMap[fileExtension]) {
                fileExtCountMap[fileExtension] = 0;
            }
            fileExtCountMap[fileExtension]++;
            setHealthData(healthData);
        }
        if (encoding) {
            var fileEncCountMap = healthData.fileStats.openedFileEncoding;
            if (!fileEncCountMap) {
                healthData.fileStats.openedFileEncoding = {};
                fileEncCountMap = healthData.fileStats.openedFileEncoding;
            }
            if (!fileEncCountMap[encoding]) {
                fileEncCountMap[encoding] = 0;
            }
            fileEncCountMap[encoding]++;
            setHealthData(healthData);
        }


        sendAnalyticsData(commonStrings.USAGE + commonStrings.FILE_OPEN + language._name,
                            commonStrings.USAGE,
                            commonStrings.FILE_OPEN,
                            language._name.toLowerCase()
                         );

    }

    /**
     * Whenever a file is saved call this function.
     * The function will send the analytics Data
     * We only log the standard filetypes and fileSize
     * @param {String} filePath The path of the file to be registered
     */
    function fileSaved(docToSave) {
        if (!docToSave) {
            return;
        }
        var fileType = docToSave.language ? docToSave.language._name : "";
        sendAnalyticsData(commonStrings.USAGE + commonStrings.FILE_SAVE + fileType,
                            commonStrings.USAGE,
                            commonStrings.FILE_SAVE,
                            fileType.toLowerCase()
                         );
    }

    /**
     * Whenever a file is closed call this function.
     * The function will send the analytics Data.
     * We only log the standard filetypes and fileSize
     * @param {String} filePath The path of the file to be registered
     */
    function fileClosed(file) {
        if (!file) {
            return;
        }
        var language = LanguageManager.getLanguageForPath(file._path),
            size = -1;

        function _sendData(fileSize) {
            var subType = "";

            if(fileSize/1024 <= 1) {

                if(fileSize < 0) {
                    subType = "";
                }
                if(fileSize <= 10) {
                    subType = "Size_0_10KB";
                } else if (fileSize <= 50) {
                    subType = "Size_10_50KB";
                } else if (fileSize <= 100) {
                    subType = "Size_50_100KB";
                } else if (fileSize <= 500) {
                    subType = "Size_100_500KB";
                } else {
                    subType = "Size_500KB_1MB";
                }

            } else {
                fileSize = fileSize/1024;
                if(fileSize <= 2) {
                    subType = "Size_1_2MB";
                } else if(fileSize <= 5) {
                    subType = "Size_2_5MB";
                } else {
                    subType = "Size_Above_5MB";
                }
            }

            sendAnalyticsData(commonStrings.USAGE + commonStrings.FILE_CLOSE + language._name + subType,
                                commonStrings.USAGE,
                                commonStrings.FILE_CLOSE,
                                language._name.toLowerCase(),
                                subType
                             );
        }

        file.stat(function(err, fileStat) {
            if(!err) {
                size = fileStat.size.valueOf()/1024;
            }
            _sendData(size);
        });
    }

    /**
     * Sets the project details(a probably unique prjID, number of files in the project and the node cache size) in the health log
     * The name of the project is never saved into the health data log, only the hash(name) is for privacy requirements.
     * @param {string} projectName The name of the project
     * @param {number} numFiles    The number of file in the project
     * @param {number} cacheSize   The node file cache memory consumed by the project
     */
    function setProjectDetail(projectName, numFiles, cacheSize) {
        var projectNameHash = StringUtils.hashCode(projectName),
            FIFLog = getHealthDataLog("ProjectDetails");
        if (!FIFLog) {
            FIFLog = {};
        }
        FIFLog["prj" + projectNameHash] = {
            numFiles : numFiles,
            cacheSize : cacheSize
        };
        setHealthDataLog("ProjectDetails", FIFLog);
    }

    /**
     * Increments health log count for a particular kind of search done
     * @param {string} searchType The kind of search type that needs to be logged- should be a js var compatible string
     */
    function searchDone(searchType) {
        var searchDetails = getHealthDataLog("searchDetails");
        if (!searchDetails) {
            searchDetails = {};
        }
        if (!searchDetails[searchType]) {
            searchDetails[searchType] = 0;
        }
        searchDetails[searchType]++;
        setHealthDataLog("searchDetails", searchDetails);
    }

     /**
     * Notifies the HealthData extension to send Analytics Data to server
     * @param{Object} eventParams Event Data to be sent to Analytics Server
     */
    function notifyHealthManagerToSendData(eventParams) {
        exports.trigger("SendAnalyticsData", eventParams);
    }

    /**
     * Send Analytics Data
     * @param {string} eventCategory The kind of Event Category that
     * needs to be logged- should be a js var compatible string
     * @param {string} eventSubCategory The kind of Event Sub Category that
     * needs to be logged- should be a js var compatible string
     * @param {string} eventType The kind of Event Type that needs to be logged- should be a js var compatible string
     * @param {string} eventSubType The kind of Event Sub Type that
     * needs to be logged- should be a js var compatible string
     */
    function sendAnalyticsData(eventName, eventCategory, eventSubCategory, eventType, eventSubType) {
        var isEventDataAlreadySent = analyticsEventMap.get(eventName),
            isHDTracking   = PreferencesManager.getExtensionPrefs("healthData").get("healthDataTracking"),
            eventParams = {};

        if (isHDTracking && !isEventDataAlreadySent && eventName && eventCategory) {
            eventParams =  {
                eventName: eventName,
                eventCategory: eventCategory,
                eventSubCategory: eventSubCategory || "",
                eventType: eventType || "",
                eventSubType: eventSubType || ""
            };
            notifyHealthManagerToSendData(eventParams);
        }
    }

    // Define public API
    exports.getHealthDataLog          = getHealthDataLog;
    exports.setHealthDataLog          = setHealthDataLog;
    exports.getAggregatedHealthData   = getAggregatedHealthData;
    exports.clearHealthData           = clearHealthData;
    exports.fileOpened                = fileOpened;
    exports.fileSaved                 = fileSaved;
    exports.fileClosed                = fileClosed;
    exports.setProjectDetail          = setProjectDetail;
    exports.searchDone                = searchDone;
    exports.setHealthLogsEnabled      = setHealthLogsEnabled;
    exports.shouldLogHealthData       = shouldLogHealthData;
    exports.init                      = init;
    exports.sendAnalyticsData         = sendAnalyticsData;

    // constants
    // searchType for searchDone()
    exports.SEARCH_INSTANT            = "searchInstant";
    exports.SEARCH_ON_RETURN_KEY      = "searchOnReturnKey";
    exports.SEARCH_REPLACE_ALL        = "searchReplaceAll";
    exports.SEARCH_NEXT_PAGE          = "searchNextPage";
    exports.SEARCH_PREV_PAGE          = "searchPrevPage";
    exports.SEARCH_LAST_PAGE          = "searchLastPage";
    exports.SEARCH_FIRST_PAGE         = "searchFirstPage";
    exports.SEARCH_REGEXP             = "searchRegExp";
    exports.SEARCH_CASE_SENSITIVE     = "searchCaseSensitive";
    // A new search context on search bar up-Gives an idea of number of times user did a discrete search
    exports.SEARCH_NEW                = "searchNew";
    exports.commonStrings = commonStrings;
    exports.analyticsEventMap = analyticsEventMap;
});