re = /function\(([^)]*)\) {\s+(?:return )?([^;\v]+);\s+}/
str = 'goog.provide(\'smb.controllers.SearchboxForDashboards\');
goog.require(\'goog.array\');
goog.require(\'goog.functions\');
goog.require(\'goog.iter\');
goog.require(\'goog.object\');
goog.require(\'goog.string.format\');
goog.require(\'smb.model.Dashboard\');
goog.require(\'smb.model.User\');
goog.scope(function() {
var User = smb.model.User;
/**
* @param {!angular.Scope} $scope Injected Angular scope.
* @param {!angular.Route} $route Injected angular route.
* @param {!angular.Location} $location Injected angular location.
* @param {!angular.Resource} $resource Injected angular resource.
* @param {!smb.services.navigation} navigationService The navigationService.
* @constructor
*/
smb.controllers.SearchboxForDashboards = function($scope, $route, $location,
$resource, navigationService) {
/** @private {!angular.Scope} */
this.scope_ = $scope;
/** @private {!smb.services.navigation} */
this.navigationService_ = navigationService;
/** @private {!angular.Route} */
this.route_ = $route;
/** @private {!angular.Location} */
this.location_ = $location;
/** @private {!angular.Resource} */
this.resource_ = $resource;
/** @private */
this.active_ = this.scope_.active;
/**
* Current search string.
* @export {string}
*/
this.search = \'\';
/**
* The list of current search results.
* @export {!Array<!SearchboxForDashboards.SearchResultStruct>}
*/
this.searchResults = [];
};
var SearchboxForDashboards = smb.controllers.SearchboxForDashboards;
/**
* @return {boolean} Whether there are no search results.
* @private
*/
SearchboxForDashboards.prototype.hasNoSearchResults_ = function() {
return this.searchResults.length == 0;
};
/**
* Types of component contained in a search result.
* @enum {string}
*/
SearchboxForDashboards.ComponentType = {
TAG: \'tag\',
TITLE: \'title\',
DESCRIPTION: \'description\'
};
/**
* FIRST_SUBSEARCH is a multiplier applied for matches starting at the beginning
* of the text as a whole. STARTING_MATCH is applies for matches at the
* beginning of any word.
* @typedef {{
* FIRST_SUBSEARCH: number,
* STARTING_MATCH: number
* }}
*/
SearchboxForDashboards.ComponentWeight;
/**
* Weight settings for the various components, can be tweaked to change results.
* @const {!Object<!SearchboxForDashboards.ComponentType,
* !SearchboxForDashboards.ComponentWeight>}
*/
SearchboxForDashboards.WEIGHTS = goog.object.create(
SearchboxForDashboards.ComponentType.TAG, {
FIRST_SUBSEARCH: 1,
STARTING_MATCH: 2
},
SearchboxForDashboards.ComponentType.TITLE, {
FIRST_SUBSEARCH: 10,
STARTING_MATCH: 10
},
SearchboxForDashboards.ComponentType.DESCRIPTION, {
FIRST_SUBSEARCH: 10,
STARTING_MATCH: 10
}
);
/**
* One feature of the search result.
* @typedef {{
* type: !SearchboxForDashboards.ComponentType,
* match: string,
* tag: (string|undefined),
* title: (string|undefined),
* score: number,
* }}
*/
SearchboxForDashboards.SearchResultComponent;
/**
* Search result with scoring information.
* @typedef {{
* dashboard: !smb.model.Dashboard,
* components: !Array<!SearchResultComponent>,
* bestTitleMatch: ?string,
* bestDescriptionMatch: ?string,
* score: number,
* }}
*/
SearchboxForDashboards.SearchResultStruct;
/**
* Gives a match score for a dashboard.
* TODO(b/27602041) Do more in-depth research into whether there are feasible
* alternatives to writing custom search logic that still give us fine
* control over ranking.
* @param {string} search The text to search.
* @param {!smb.model.Dashboard} dashboard The dashboard to which to apply the
* search.
* @param {boolean=} opt_recur Whether to recursively perform subsearches
* for the different words in the search, i.e "word1 word2 word3" would
* do "word1 word2" and "word2 word3", and then the same recursively.
* @param {boolean=} opt_isFirstSearch Whether this is should be treated as
* matching at the beginning of a search.
* @return {!SearchResultStruct} The result of the search.
* @private
*/
SearchboxForDashboards.prototype.doDashboardSearch_ =
function(search, dashboard, opt_recur, opt_isFirstSearch) {
var isFirstSearch = opt_isFirstSearch;
var struct = {dashboard: dashboard, components: []};
var titleSearchComponent =
this.doTitleSearch_(search, dashboard, isFirstSearch);
if (titleSearchComponent != null) {
struct.components.push(titleSearchComponent);
}
var descriptionSearchComponent =
this.doDescriptionSearch_(search, dashboard, isFirstSearch);
if (descriptionSearchComponent != null) {
struct.components.push(descriptionSearchComponent);
}
var tagSearchComponent = this.doTagSearch_(search, dashboard);
if (tagSearchComponent != null) {
struct.components.push(tagSearchComponent);
}
// now we search for all the n-gram substrings
if (opt_recur) {
var re = /\\s+/;
var tokens = search.split(re);
var len = tokens.length;
for (var runLength = len - 1; runLength > 0; runLength--) {
for (var start = 0; start <= len - runLength; start++) {
var subArr = tokens.slice(start, start + runLength);
var newSearch = subArr.join(\' \');
var isFirstSearch = start == 0;
var subStruct =
this.doDashboardSearch_(newSearch, dashboard, false, isFirstSearch);
struct.components = struct.components.concat(subStruct.components);
}
}
}
struct.components = struct.components.sort(function(a, b) {
return b.score - a.score;
});
var bestTitleMatch = goog.array.find(struct.components, function(component) {
return component.type == SearchboxForDashboards.TITLE;
});
if (bestTitleMatch) {
struct.bestTitleMatch = bestTitleMatch.title;
}
var bestDescriptionMatch = goog.array.find(struct.components,
function(component) {
return component.type == SearchboxForDashboards.DESCRIPTION;
});
if (bestDescriptionMatch) {
struct.bestDescriptionMatch = bestDescriptionMatch.description;
}
this.computeAndSetSearchResultScore_(struct);
return struct;
};
/**
* Computes score for the search result and sets score for the search result.
* @param {!SearchResultStruct} searchResult The search result to operate on.
* @private
*/
SearchboxForDashboards.prototype.computeAndSetSearchResultScore_ =
function(searchResult) {
searchResult.score = searchResult.components.map(function(component) {
return component.score;
}).reduce(function(a, b) { return a + b; }, 0);
};
/**
* Performs search on dashboard title.
* @param {string} search The text to search.
* @param {!smb.model.Dashboard} dashboard The dashboard to perform search on.
* @param {boolean} isFirstSearch Whether to treat this as matching at the
* beginning of a search.
* @return {?SearchboxForDashboards.SearchResultComponent} The search match
* component.
* @private
*/
SearchboxForDashboards.prototype.doTitleSearch_ =
function(search, dashboard, isFirstSearch) {
var title = dashboard.titleDisplayText || dashboard.menuDisplayText;
return this.doTextSearch_(search, title, isFirstSearch,
SearchboxForDashboards.ComponentType.TITLE);
};
/**
* Performs search on dashboard description.
* @param {string} search The text to search.
* @param {!smb.model.Dashboard} dashboard The dashboard to perform search on.
* @param {boolean} isFirstSearch Whether to treat this as matching at the
* beginning of a search.
* @return {?SearchboxForDashboards.SearchResultComponent} The search match
* component.
* @private
*/
SearchboxForDashboards.prototype.doDescriptionSearch_ =
function(search, dashboard, isFirstSearch) {
var title = dashboard.titleDisplayText || dashboard.menuDisplayText;
return this.doTextSearch_(search, title, isFirstSearch,
SearchboxForDashboards.ComponentType.DESCRIPTION);
};
/**
* Searches the text.
* @param {string} search
* @param {string} text
* @param {boolean} isFirstSearch
* @param {smb.controllers.SearchboxForDashboards.ComponentType} type
* @return {?SearchboxForDashboards.SearchResultComponent} The search match
* component.
* @private
*/
SearchboxForDashboards.prototype.doTextSearch_ = function(search, text,
isFirstSearch, type) {
var score = 0;
var searchRegex = new RegExp(search, \'i\');
var match = text.match(searchRegex);
if (!match) {
return null;
}
score = match[0].length;
var weight = SearchboxForDashboards.WEIGHTS[type];
if (text.toLowerCase().startsWith(match[0].toLowerCase())) {
score *= weight.STARTING_MATCH;
if (isFirstSearch) {
score *= weight.FIRST_SUBSEARCH;
}
}
return goog.object.create(
type, match[0],
\'score\', score,
\'type\', type,
\'match\', search
);
};
/**
* Perform search on dashboard tags.
* @param {string} search The text to search.
* @param {!smb.model.Dashboard} dashboard The dashboard to perform search on.
* @param {boolean} isFirstSearch
* @return {?SearchboxForDashboards.SearchResultComponent} The search match
* component.
* @private
*/
SearchboxForDashboards.prototype.doTagSearch_ = function(search, dashboard,
isFirstSearch) {
if (!(\'tags\' in dashboard)) {
return null;
}
var tagMatches = dashboard.tags.map(function(tag) {
return this.doTextSearch_(search, tag, isFirstSearch,
SearchboxForDashboards.ComponentType.TAG);
}, this);
var tagMatches = [];
var groups = goog.iter.groupBy(tagMatches,
function(tagMatch) { return tagMatch.match; });
var scores = [];
var score = 0;
var iterLeft = true;
while (iterLeft) {
try {
var matchTup = groups.next();
var key = matchTup[0];
var matches = matchTup[1];
var numMatches = matches.length;
var scores = matches.map(function(match) { return match.score; })
.reduce(function(a, b) { return a + b; }, 0);
var average = scores / numMatches;
// numMatches + 1 because the score for one should not be zero
// have to do log because too many dups for sqrt to make sense
score += (average * Math.log(numMatches + 1));
} catch (e) {
// goog.iter throws exception at the end rather than having a flag
// to check
iterLeft = false;
}
}
return {
score: score,
type: SearchboxForDashboards.ComponentType.TAG,
match: search
};
};
/**
* @param {string} search The text to search for.
* @param {!Array<!smb.model.Dashboard>} dashboards The dashboards to search
* over.
* @return {!Array<!SearchResultStruct>} The results, sorted.
* @private
*/
SearchboxForDashboards.prototype.doSearch_ = function(search, dashboards) {
var scoreStructs = dashboards.map(function(dashboard) {
return this.doDashboardSearch_(search, dashboard, true, true);
}, this);
return scoreStructs
.filter(function(struct) { return struct.score > 0; }, this)
.sort(function(aStruct, bStruct) {
return bStruct.score - aStruct.score;
});
};
/**
* Updates the autocomplete results, showing the appropriate amount for the
* current search text.
*/
SearchboxForDashboards.prototype.updateAutoComplete = function() {
var dashboards = this.navigationService_.getSearchableDashboards();
if (!dashboards) return;
var searchResults = this.doSearch_(this.search, dashboards);
var searchResultsShow = searchResults.length > 0;
this.searchResults = searchResults;
this.saveAutoCompleteEvent_();
};
/**
* Logs autocomplete search.
* @private
*/
SearchboxForDashboards.prototype.saveAutoCompleteEvent_ = function() {
var urlQuery = this.location_.url();
// @see onlinesales.revenueprograms.sas.discovery.server.services.UserEventProvider
var endpoint = this.getUserEventResource_(\'persist\');
var evnt = {
type: User.Event.SEARCH,
urlQuery: urlQuery,
search: this.search,
session: smbuser.session
};
endpoint.save(evnt);
};
/**
* Logs search result click.
* @param {number} dashboardId the id of the relavant dashboard
* @private
*/
SearchboxForDashboards.prototype.saveSearchResultClick_ = function(dashboardId) {
var urlQuery = this.location_.url();
// @see onlinesales.revenueprograms.sas.discovery.server.services.UserEventProvider
var endpoint = this.getUserEventResource_(\'persist\');
var evnt = {
dashboardId: dashboardId,
type: User.Event.SEARCH_CLICK,
urlQuery: urlQuery,
search: this.search,
session: smbuser.session
};
endpoint.save(evnt);
};
/**
* @param {string} action The action path for which to get endpoint.
* @return {!ngResource} Angular resource for the action on user events.
* @private
*/
SearchboxForDashboards.prototype.getUserEventResource_ = function(action) {
return this.resource_(
goog.string.format(\'%s/%s\', User.EVENT_ENDPOINT, action));
};
/**
* Currently returns the arguments, here because of past and potentially
* future formatting.
* @param {string} tag The tag string.
* @return {string} the tag display text
*/
SearchboxForDashboards.prototype.getTagDisplayText = goog.functions.identity;
/**
* Gets the class for the given tag.
* @param {string} tag The tag.
* @return {string} The class name.
*/
SearchboxForDashboards.prototype.getTagDisplayClass = function(tag) {
if (tag) {
var strArr = tag.split(\':\');
if (strArr.length == 2) {
return goog.string.format(\'tags-style-%s\', strArr[0]);
} else {
return \'\';
}
}
return \'\';
};
/**
* Goes to the search results page, if that feature is enabled.
*
* @param {!jQuery.Event|!JQLite.Event} evt The fired event on keypress.
*
* @export
*/
SearchboxForDashboards.prototype.searchBoxKeypressed = function(evt) {
};
/**
* Handler for clicking search result item. Navigates to the dashboard.
* @param {!smb.model.Dashboard} dashboard The dashboard selection clicked.
* @param {!jQuery.Event|!JQLite.Event} $event The event for the click.
*/
SearchboxForDashboards.prototype.resultItemClicked = function(dashboard, $event) {
$event.stopPropagation();
this.saveSearchResultClick_(dashboard.id);
this.navigateToDashboard_(dashboard);
};
/**
* Navigates to the given dashboard.
* @param {!smb.model.Dashboard} dashboard The dashboard to navigate to.
* @private
*/
SearchboxForDashboards.prototype.navigateToDashboard_ = function(dashboard) {
this.reset_();
this.location_.search({});
this.location_.path(dashboard.navigationURLPath);
this.route_.reload();
};
/**
* @param {!SearchboxForDashboards.SearchResultStruct} resultObj Representation of the
* search result.
* @return {?string} The title text for the search result, if there is any.
*/
SearchboxForDashboards.prototype.getTitleText = function(resultObj) {
var dash = resultObj.dashboard;
return dash.titleDisplayText || dash.menuDisplayText;
};
/**
* @param {!SearchboxForDashboards.SearchResultStruct} resultObj Representation of the
* search result.
* @return {string} The description text for the search result.
*/
SearchboxForDashboards.prototype.getDescriptionText = function(resultObj) {
return resultObj.dashboard.flatDescription;
};
/**
* @return {boolean} Whether to show the search box on this page.
*/
SearchboxForDashboards.prototype.shouldShowResultBox = function() {
// Shows when it is active, there are results, and there is a search
return this.scope_.active && !this.hasNoSearchResults_() &&
this.search.length > 0;
};
/**
* Sets the search text to the empty string.
* @private
*/
SearchboxForDashboards.prototype.clearSearchBoxText_ = function() {
this.search = \'\';
};
/**
* Sets the search results to an empty array.
* @private
*/
SearchboxForDashboards.prototype.clearSearchResults_ = function() {
this.searchResults = [];
};
/**
* Reverts to initial state.
* @private
*/
SearchboxForDashboards.prototype.reset_ = function() {
this.clearSearchBoxText_();
this.clearSearchResults_();
};
}); // goog.scope
'
subst = '($1) => $2'
result = str.gsub(re, subst)
# Print the result of the substitution
puts result
Please keep in mind that these code samples are automatically generated and are not guaranteed to work. If you find any syntax errors, feel free to submit a bug report. For a full regex reference for Ruby, please visit: http://ruby-doc.org/core-2.2.0/Regexp.html