Unverified Commit 9abeb9c4 by Sam Padgett Committed by GitHub

Merge pull request #317 from spadgett/weighted-search

Bug 1449038 - Add a simple weighted search to KeywordService
parents 0e323e76 43fa21b8
...@@ -2932,7 +2932,9 @@ angular.module('openshiftCommonServices') ...@@ -2932,7 +2932,9 @@ angular.module('openshiftCommonServices')
;'use strict'; ;'use strict';
angular.module("openshiftCommonServices") angular.module("openshiftCommonServices")
.service("KeywordService", function(){ .service("KeywordService", function($filter) {
var displayName = $filter('displayName');
var generateKeywords = function(filterText) { var generateKeywords = function(filterText) {
if (!filterText) { if (!filterText) {
...@@ -2977,8 +2979,57 @@ angular.module("openshiftCommonServices") ...@@ -2977,8 +2979,57 @@ angular.module("openshiftCommonServices")
return filteredObjects; return filteredObjects;
}; };
// Perform a simple weighted search.
// weightedSearch is like filterForKeywords, except each field has a weight
// and the result is a sorted array of matches. filterFields is an array of
// objects with keys `path` and `weight`.
var weightedSearch = function(objects, filterFields, keywords) {
if (_.isEmpty(keywords)) {
return [];
}
var results = [];
_.each(objects, function(object) {
// Keep a score for matches, weighted by field.
var score = 0;
_.each(keywords, function(regex) {
var matchesKeyword = false;
_.each(filterFields, function(field) {
var value = _.get(object, field.path);
if (!value) {
return;
}
if (regex.test(value)) {
// For each matching keyword, add the field weight to the score.
score += field.weight;
matchesKeyword = true;
}
});
if (!matchesKeyword) {
// We've missed a keyword. Set score to 0 and short circuit the loop.
score = 0;
return false;
}
});
if (score > 0) {
results.push({
object: object,
score: score
});
}
});
// Sort first by score, then by display name for items that have the same score.
var orderedResult = _.orderBy(results, ['score', displayName], ['desc', 'asc']);
return _.map(orderedResult, 'object');
};
return { return {
filterForKeywords: filterForKeywords, filterForKeywords: filterForKeywords,
weightedSearch: weightedSearch,
generateKeywords: generateKeywords generateKeywords: generateKeywords
}; };
}); });
......
...@@ -5000,7 +5000,9 @@ angular.module('openshiftCommonServices') ...@@ -5000,7 +5000,9 @@ angular.module('openshiftCommonServices')
;'use strict'; ;'use strict';
angular.module("openshiftCommonServices") angular.module("openshiftCommonServices")
.service("KeywordService", function(){ .service("KeywordService", ["$filter", function($filter) {
var displayName = $filter('displayName');
var generateKeywords = function(filterText) { var generateKeywords = function(filterText) {
if (!filterText) { if (!filterText) {
...@@ -5045,11 +5047,60 @@ angular.module("openshiftCommonServices") ...@@ -5045,11 +5047,60 @@ angular.module("openshiftCommonServices")
return filteredObjects; return filteredObjects;
}; };
// Perform a simple weighted search.
// weightedSearch is like filterForKeywords, except each field has a weight
// and the result is a sorted array of matches. filterFields is an array of
// objects with keys `path` and `weight`.
var weightedSearch = function(objects, filterFields, keywords) {
if (_.isEmpty(keywords)) {
return [];
}
var results = [];
_.each(objects, function(object) {
// Keep a score for matches, weighted by field.
var score = 0;
_.each(keywords, function(regex) {
var matchesKeyword = false;
_.each(filterFields, function(field) {
var value = _.get(object, field.path);
if (!value) {
return;
}
if (regex.test(value)) {
// For each matching keyword, add the field weight to the score.
score += field.weight;
matchesKeyword = true;
}
});
if (!matchesKeyword) {
// We've missed a keyword. Set score to 0 and short circuit the loop.
score = 0;
return false;
}
});
if (score > 0) {
results.push({
object: object,
score: score
});
}
});
// Sort first by score, then by display name for items that have the same score.
var orderedResult = _.orderBy(results, ['score', displayName], ['desc', 'asc']);
return _.map(orderedResult, 'object');
};
return { return {
filterForKeywords: filterForKeywords, filterForKeywords: filterForKeywords,
weightedSearch: weightedSearch,
generateKeywords: generateKeywords generateKeywords: generateKeywords
}; };
}); }]);
;'use strict'; ;'use strict';
angular.module('openshiftCommonServices') angular.module('openshiftCommonServices')
......
...@@ -1085,7 +1085,7 @@ extensions: "v1beta1" ...@@ -1085,7 +1085,7 @@ extensions: "v1beta1"
if (e instanceof ResourceGroupVersion) return e; if (e instanceof ResourceGroupVersion) return e;
var n, r, o; var n, r, o;
return angular.isString(e) ? (n = d(e), o = f[r = ""]) : e && e.resource && (n = d(e.resource), r = e.group || "", o = e.version || f[r] || _.get(t, [ "groups", r, "preferredVersion" ])), new ResourceGroupVersion(n, r, o); return angular.isString(e) ? (n = d(e), o = f[r = ""]) : e && e.resource && (n = d(e.resource), r = e.group || "", o = e.version || f[r] || _.get(t, [ "groups", r, "preferredVersion" ])), new ResourceGroupVersion(n, r, o);
}, m = function(e) { }, h = function(e) {
if (e) { if (e) {
var t = e.split("/"); var t = e.split("/");
return 1 === t.length ? "v1" === t[0] ? { return 1 === t.length ? "v1" === t[0] ? {
...@@ -1099,7 +1099,7 @@ group: t[0], ...@@ -1099,7 +1099,7 @@ group: t[0],
version: t[1] version: t[1]
} : void a.warn('Invalid apiVersion "' + e + '"'); } : void a.warn('Invalid apiVersion "' + e + '"');
} }
}, h = function(e, t) { }, m = function(e, t) {
return !(!_.find(r.kinds, { return !(!_.find(r.kinds, {
group: e, group: e,
kind: t kind: t
...@@ -1134,7 +1134,7 @@ _.each(e.versions[t].resources, function(t) { ...@@ -1134,7 +1134,7 @@ _.each(e.versions[t].resources, function(t) {
_.includes(t.name, "/") || _.find(o, { _.includes(t.name, "/") || _.find(o, {
kind: t.kind, kind: t.kind,
group: e.name group: e.name
}) || h(e.name, t.kind) || (t.namespaced || n) && r.push({ }) || m(e.name, t.kind) || (t.namespaced || n) && r.push({
kind: t.kind, kind: t.kind,
group: e.name group: e.name
}); });
...@@ -1148,19 +1148,19 @@ toAPIVersion: function(e) { ...@@ -1148,19 +1148,19 @@ toAPIVersion: function(e) {
return e.group ? e.group + "/" + e.version : e.version; return e.group ? e.group + "/" + e.version : e.version;
}, },
toResourceGroupVersion: g, toResourceGroupVersion: g,
parseGroupVersion: m, parseGroupVersion: h,
objectToResourceGroupVersion: function(e) { objectToResourceGroupVersion: function(e) {
if (e && e.kind && e.apiVersion) { if (e && e.kind && e.apiVersion) {
var t = p(e.kind); var t = p(e.kind);
if (t) { if (t) {
var n = m(e.apiVersion); var n = h(e.apiVersion);
if (n) return new ResourceGroupVersion(t, n.group, n.version); if (n) return new ResourceGroupVersion(t, n.group, n.version);
} }
} }
}, },
deriveTargetResource: function(e, t) { deriveTargetResource: function(e, t) {
if (e && t) { if (e && t) {
var n = p(t.kind), r = m(t.apiVersion), o = g(e); var n = p(t.kind), r = h(t.apiVersion), o = g(e);
if (n && r && o) return angular.isString(e) ? (o.equals(n) && (o.group = r.group, o.version = r.version), o) : (o.equals(n, r.group) && (o.version = r.version), o); if (n && r && o) return angular.isString(e) ? (o.equals(n) && (o.group = r.group, o.version = r.version), o) : (o.equals(n, r.group) && (o.version = r.version), o);
} }
}, },
...@@ -1277,24 +1277,24 @@ throw n + " not set"; ...@@ -1277,24 +1277,24 @@ throw n + " not set";
this.$get = [ "$q", "$injector", "$log", "$rootScope", "Logger", "base64", function(o, i, a, s, c, l) { this.$get = [ "$q", "$injector", "$log", "$rootScope", "Logger", "base64", function(o, i, a, s, c, l) {
var u = c.get("auth"); var u = c.get("auth");
u.log("AuthServiceProvider.$get", arguments); u.log("AuthServiceProvider.$get", arguments);
var d = $.Callbacks(), p = $.Callbacks(), f = $.Callbacks(), g = null, m = null, h = r(i, e, "AuthServiceProvider.UserStore()"); var d = $.Callbacks(), p = $.Callbacks(), f = $.Callbacks(), g = null, h = null, m = r(i, e, "AuthServiceProvider.UserStore()");
h.available() || c.error("AuthServiceProvider.$get user store " + e + " not available"); m.available() || c.error("AuthServiceProvider.$get user store " + e + " not available");
var v = r(i, t, "AuthServiceProvider.LoginService()"), b = r(i, n, "AuthServiceProvider.LogoutService()"); var v = r(i, t, "AuthServiceProvider.LoginService()"), b = r(i, n, "AuthServiceProvider.LogoutService()");
return { return {
UserStore: function() { UserStore: function() {
return h; return m;
}, },
isLoggedIn: function() { isLoggedIn: function() {
return !!h.getUser(); return !!m.getUser();
}, },
withUser: function() { withUser: function() {
var e = h.getUser(); var e = m.getUser();
return e ? (s.user = e, u.log("AuthService.withUser()", e), o.when(e)) : (u.log("AuthService.withUser(), calling startLogin()"), this.startLogin()); return e ? (s.user = e, u.log("AuthService.withUser()", e), o.when(e)) : (u.log("AuthService.withUser(), calling startLogin()"), this.startLogin());
}, },
setUser: function(e, t, n) { setUser: function(e, t, n) {
u.log("AuthService.setUser()", e, t, n); u.log("AuthService.setUser()", e, t, n);
var r = h.getUser(); var r = m.getUser();
h.setUser(e, n), h.setToken(t, n), s.user = e, (r && r.metadata && r.metadata.name) !== (e && e.metadata && e.metadata.name) && (u.log("AuthService.setUser(), user changed", r, e), f.fire(e)); m.setUser(e, n), m.setToken(t, n), s.user = e, (r && r.metadata && r.metadata.name) !== (e && e.metadata && e.metadata.name) && (u.log("AuthService.setUser(), user changed", r, e), f.fire(e));
}, },
requestRequiresAuth: function(e) { requestRequiresAuth: function(e) {
var t = !!e.auth; var t = !!e.auth;
...@@ -1302,7 +1302,7 @@ return u.log("AuthService.requestRequiresAuth()", e.url.toString(), t), t; ...@@ -1302,7 +1302,7 @@ return u.log("AuthService.requestRequiresAuth()", e.url.toString(), t), t;
}, },
addAuthToRequest: function(e) { addAuthToRequest: function(e) {
var t = ""; var t = "";
return e && e.auth && e.auth.token ? (t = e.auth.token, u.log("AuthService.addAuthToRequest(), using token from request config", t)) : (t = h.getToken(), u.log("AuthService.addAuthToRequest(), using token from user store", t)), t ? ("WATCH" === e.method ? (e.protocols = e.protocols || [], _.isArray(e.protocols) || (e.protocols = [ e.protocols ]), 0 == e.protocols.length && e.protocols.unshift("undefined"), e.protocols.unshift("base64url.bearer.authorization.k8s.io." + l.urlencode(t)), u.log("AuthService.addAuthToRequest(), added token protocol", e.protocols)) : (e.headers.Authorization = "Bearer " + t, u.log("AuthService.addAuthToRequest(), added token header", e.headers.Authorization)), !0) : (u.log("AuthService.addAuthToRequest(), no token available"), !1); return e && e.auth && e.auth.token ? (t = e.auth.token, u.log("AuthService.addAuthToRequest(), using token from request config", t)) : (t = m.getToken(), u.log("AuthService.addAuthToRequest(), using token from user store", t)), t ? ("WATCH" === e.method ? (e.protocols = e.protocols || [], _.isArray(e.protocols) || (e.protocols = [ e.protocols ]), 0 == e.protocols.length && e.protocols.unshift("undefined"), e.protocols.unshift("base64url.bearer.authorization.k8s.io." + l.urlencode(t)), u.log("AuthService.addAuthToRequest(), added token protocol", e.protocols)) : (e.headers.Authorization = "Bearer " + t, u.log("AuthService.addAuthToRequest(), added token header", e.headers.Authorization)), !0) : (u.log("AuthService.addAuthToRequest(), no token available"), !1);
}, },
startLogin: function() { startLogin: function() {
if (g) return u.log("Login already in progress"), g; if (g) return u.log("Login already in progress"), g;
...@@ -1316,16 +1316,16 @@ g = null; ...@@ -1316,16 +1316,16 @@ g = null;
}); });
}, },
startLogout: function() { startLogout: function() {
if (m) return u.log("Logout already in progress"), m; if (h) return u.log("Logout already in progress"), h;
var e = this, t = h.getUser(), n = h.getToken(), r = this.isLoggedIn(); var e = this, t = m.getUser(), n = m.getToken(), r = this.isLoggedIn();
return m = b.logout(t, n).then(function() { return h = b.logout(t, n).then(function() {
u.log("Logout service success"); u.log("Logout service success");
}).catch(function(e) { }).catch(function(e) {
u.error("Logout service error", e); u.error("Logout service error", e);
}).finally(function() { }).finally(function() {
e.setUser(null, null); e.setUser(null, null);
var t = e.isLoggedIn(); var t = e.isLoggedIn();
r && !t && p.fire(), m = null; r && !t && p.fire(), h = null;
}); });
}, },
onLogin: function(e) { onLogin: function(e) {
...@@ -1385,7 +1385,7 @@ return d(t) && !_.isEmpty(_.intersection(e.verbs, [ "*", "create", "update" ])); ...@@ -1385,7 +1385,7 @@ return d(t) && !_.isEmpty(_.intersection(e.verbs, [ "*", "create", "update" ]));
}); });
}, f = {}, g = function(e) { }, f = {}, g = function(e) {
return _.get(s.get(e || a), [ "rules" ]); return _.get(s.get(e || a), [ "rules" ]);
}, m = function(e, t, n, r) { }, h = function(e, t, n, r) {
var o = e[n]; var o = e[n];
if (!o) return !1; if (!o) return !1;
var i = o[r]; var i = o[r];
...@@ -1426,7 +1426,7 @@ canI: function(e, t, n) { ...@@ -1426,7 +1426,7 @@ canI: function(e, t, n) {
if (c) return !0; if (c) return !0;
if (!e) return !1; if (!e) return !1;
var r = o.toResourceGroupVersion(e), i = g(n || a); var r = o.toResourceGroupVersion(e), i = g(n || a);
return !!i && (m(i, t, r.group, r.resource) || m(i, t, "*", "*") || m(i, t, r.group, "*") || m(i, t, "*", r.resource)); return !!i && (h(i, t, r.group, r.resource) || h(i, t, "*", "*") || h(i, t, r.group, "*") || h(i, t, "*", r.resource));
}, },
canIAddToProject: function(e) { canIAddToProject: function(e) {
return !!c || !!_.get(s.get(e || a), [ "canAddToProject" ]); return !!c || !!_.get(s.get(e || a), [ "canAddToProject" ]);
...@@ -1590,7 +1590,7 @@ t._websocketEventsMap = {}; ...@@ -1590,7 +1590,7 @@ t._websocketEventsMap = {};
function g(e) { function g(e) {
return e.length >= S && Date.now() - e[0].time < 3e4; return e.length >= S && Date.now() - e[0].time < 3e4;
} }
function m(e) { function h(e) {
if (e.length < 5) return !1; if (e.length < 5) return !1;
for (var t = e.length - 5; t < e.length; t++) if ("close" !== e[t].type) return !1; for (var t = e.length - 5; t < e.length; t++) if ("close" !== e[t].type) return !1;
return !0; return !0;
...@@ -1607,12 +1607,12 @@ angular.forEach(e, function(e, o) { ...@@ -1607,12 +1607,12 @@ angular.forEach(e, function(e, o) {
p(e, t, n, r ? r[o] : null); p(e, t, n, r ? r[o] : null);
}); });
}; };
var h = [], v = _.debounce(function() { var m = [], v = _.debounce(function() {
if (h.length) { if (m.length) {
var e = { var e = {
type: "error", type: "error",
message: "An error occurred connecting to the server.", message: "An error occurred connecting to the server.",
details: h.join("\n"), details: m.join("\n"),
links: [ { links: [ {
label: "Refresh", label: "Refresh",
onClick: function() { onClick: function() {
...@@ -1620,12 +1620,12 @@ window.location.reload(); ...@@ -1620,12 +1620,12 @@ window.location.reload();
} }
} ] } ]
}; };
r.$emit("NotificationsService.addNotification", e), h = []; r.$emit("NotificationsService.addNotification", e), m = [];
} }
}, 300, { }, 300, {
maxWait: 1e3 maxWait: 1e3
}), b = function(e, t) { }), b = function(e, t) {
t && (e += " (status " + t + ")"), h.push(e), v(); t && (e += " (status " + t + ")"), m.push(e), v();
}, y = function(e, t) { }, y = function(e, t) {
var n; var n;
return n = e ? "Unknown resource: " + e.toString() : "Internal error: API resource not specified.", _.get(t, "errorNotification", !0) && b(n), o.reject({ return n = e ? "Unknown resource: " + e.toString() : "Internal error: API resource not specified.", _.get(t, "errorNotification", !0) && b(n), o.reject({
...@@ -1793,7 +1793,7 @@ return l.promise; ...@@ -1793,7 +1793,7 @@ return l.promise;
}, f.prototype.createStream = function(e, t, r, o, i) { }, f.prototype.createStream = function(e, t, r, o, i) {
var c = this; var c = this;
e = a.toResourceGroupVersion(e); e = a.toResourceGroupVersion(e);
var d, p = i ? "binary.k8s.io" : "base64.binary.k8s.io", f = {}, g = {}, m = {}, h = {}, v = function() { var d, p = i ? "binary.k8s.io" : "base64.binary.k8s.io", f = {}, g = {}, h = {}, m = {}, v = function() {
return c._getNamespace(e, r, {}).then(function(a) { return c._getNamespace(e, r, {}).then(function(a) {
var d = c._urlForResource(e, t, r, !0, _.extend(a, o)); var d = c._urlForResource(e, t, r, !0, _.extend(a, o));
if (!d) return y(e, o); if (!d) return y(e, o);
...@@ -1815,12 +1815,12 @@ i ? n(e.data) : n(t, e.data, v); ...@@ -1815,12 +1815,12 @@ i ? n(e.data) : n(t, e.data, v);
} else s.log("log stream response is not a string", e.data); } else s.log("log stream response is not a string", e.data);
}, },
onclose: function(e) { onclose: function(e) {
_.each(m, function(t) { _.each(h, function(t) {
t(e); t(e);
}); });
}, },
onerror: function(e) { onerror: function(e) {
_.each(h, function(t) { _.each(m, function(t) {
t(e); t(e);
}); });
}, },
...@@ -1846,17 +1846,17 @@ return g[t] = e, t; ...@@ -1846,17 +1846,17 @@ return g[t] = e, t;
onClose: function(e) { onClose: function(e) {
if (_.isFunction(e)) { if (_.isFunction(e)) {
var t = _.uniqueId("stream_"); var t = _.uniqueId("stream_");
return m[t] = e, t; return h[t] = e, t;
} }
}, },
onError: function(e) { onError: function(e) {
if (_.isFunction(e)) { if (_.isFunction(e)) {
var t = _.uniqueId("stream_"); var t = _.uniqueId("stream_");
return h[t] = e, t; return m[t] = e, t;
} }
}, },
remove: function(e) { remove: function(e) {
f[e] && delete f[e], g[e] && delete g[e], m[e] && delete m[e], h[e] && delete h[e]; f[e] && delete f[e], g[e] && delete g[e], h[e] && delete h[e], m[e] && delete m[e];
}, },
start: function() { start: function() {
return d = v(); return d = v();
...@@ -1964,7 +1964,7 @@ time: Date.now() ...@@ -1964,7 +1964,7 @@ time: Date.now()
}); n.length > S; ) n.shift(); }); n.length > S; ) n.shift();
}, f.prototype._isTooManyWebsocketRetries = function(e) { }, f.prototype._isTooManyWebsocketRetries = function(e) {
var t = this._websocketEventsMap[e]; var t = this._websocketEventsMap[e];
return !!t && (g(t) ? (s.log("Too many websocket open or close events for resource/context in a short period", e, t), !0) : !!m(t) && (s.log("Too many consecutive websocket close events for resource/context", e, t), !0)); return !!t && (g(t) ? (s.log("Too many websocket open or close events for resource/context in a short period", e, t), !0) : !!h(t) && (s.log("Too many consecutive websocket close events for resource/context", e, t), !0));
}; };
var w = function(e) { var w = function(e) {
var t = _.keysIn(_.pick(e, [ "fieldSelector", "labelSelector" ])).sort(); var t = _.keysIn(_.pick(e, [ "fieldSelector", "labelSelector" ])).sort();
...@@ -2194,7 +2194,8 @@ return a.delete(s, o, {}, c); ...@@ -2194,7 +2194,8 @@ return a.delete(s, o, {}, c);
} }
}; };
} ]; } ];
}), angular.module("openshiftCommonServices").service("KeywordService", function() { }), angular.module("openshiftCommonServices").service("KeywordService", [ "$filter", function(e) {
var t = e("displayName");
return { return {
filterForKeywords: function(e, t, n) { filterForKeywords: function(e, t, n) {
var r = e; var r = e;
...@@ -2209,6 +2210,25 @@ return !1; ...@@ -2209,6 +2210,25 @@ return !1;
}); });
}), r); }), r);
}, },
weightedSearch: function(e, n, r) {
if (_.isEmpty(r)) return [];
var o = [];
_.each(e, function(e) {
var t = 0;
_.each(r, function(r) {
var o = !1;
if (_.each(n, function(n) {
var i = _.get(e, n.path);
i && r.test(i) && (t += n.weight, o = !0);
}), !o) return t = 0, !1;
}), t > 0 && o.push({
object: e,
score: t
});
});
var i = _.orderBy(o, [ "score", t ], [ "desc", "asc" ]);
return _.map(i, "object");
},
generateKeywords: function(e) { generateKeywords: function(e) {
if (!e) return []; if (!e) return [];
var t = _.uniq(e.match(/\S+/g)); var t = _.uniq(e.match(/\S+/g));
...@@ -2219,7 +2239,7 @@ return new RegExp(_.escapeRegExp(e), "i"); ...@@ -2219,7 +2239,7 @@ return new RegExp(_.escapeRegExp(e), "i");
}); });
} }
}; };
}), angular.module("openshiftCommonServices").provider("Logger", function() { } ]), angular.module("openshiftCommonServices").provider("Logger", function() {
this.$get = function() { this.$get = function() {
var e = Logger.get("OpenShift"), t = { var e = Logger.get("OpenShift"), t = {
get: function(e) { get: function(e) {
...@@ -2360,7 +2380,7 @@ var u, d = !1, p = r.getPreferredVersion("projects"), f = r.getPreferredVersion( ...@@ -2360,7 +2380,7 @@ var u, d = !1, p = r.getPreferredVersion("projects"), f = r.getPreferredVersion(
s.debug("ProjectsService: clearing project cache"), u = null, d = !1; s.debug("ProjectsService: clearing project cache"), u = null, d = !1;
}; };
o.onUserChanged(g), o.onLogout(g); o.onUserChanged(g), o.onLogout(g);
var m = function(e) { var h = function(e) {
var t = [ l("description"), l("displayName") ]; var t = [ l("description"), l("displayName") ];
return _.each(t, function(t) { return _.each(t, function(t) {
e.metadata.annotations[t] || delete e.metadata.annotations[t]; e.metadata.annotations[t] || delete e.metadata.annotations[t];
...@@ -2408,7 +2428,7 @@ u = e, t(e); ...@@ -2408,7 +2428,7 @@ u = e, t(e);
}); });
}, },
update: function(e, t) { update: function(e, t) {
return a.update(p, e, m(t), { return a.update(p, e, h(t), {
projectName: e projectName: e
}, { }, {
errorNotification: !1 errorNotification: !1
...@@ -2595,15 +2615,15 @@ if (!f.verified) return s.reject({ ...@@ -2595,15 +2615,15 @@ if (!f.verified) return s.reject({
error: "invalid_request", error: "invalid_request",
error_description: "Client state could not be verified" error_description: "Client state could not be verified"
}); });
var m = [ "grant_type=authorization_code", "code=" + encodeURIComponent(d.code), "redirect_uri=" + encodeURIComponent(r), "client_id=" + encodeURIComponent(e) ].join("&"); var h = [ "grant_type=authorization_code", "code=" + encodeURIComponent(d.code), "redirect_uri=" + encodeURIComponent(r), "client_id=" + encodeURIComponent(e) ].join("&");
return o && (m += "&scope=" + encodeURIComponent(o)), t({ return o && (h += "&scope=" + encodeURIComponent(o)), t({
method: "POST", method: "POST",
url: n, url: n,
headers: { headers: {
Authorization: "Basic " + window.btoa(e + ":"), Authorization: "Basic " + window.btoa(e + ":"),
"Content-Type": "application/x-www-form-urlencoded" "Content-Type": "application/x-www-form-urlencoded"
}, },
data: m data: h
}).then(function(e) { }).then(function(e) {
return c(e.data, f); return c(e.data, f);
}, function(e) { }, function(e) {
......
'use strict'; 'use strict';
angular.module("openshiftCommonServices") angular.module("openshiftCommonServices")
.service("KeywordService", function(){ .service("KeywordService", function($filter) {
var displayName = $filter('displayName');
var generateKeywords = function(filterText) { var generateKeywords = function(filterText) {
if (!filterText) { if (!filterText) {
...@@ -46,8 +48,57 @@ angular.module("openshiftCommonServices") ...@@ -46,8 +48,57 @@ angular.module("openshiftCommonServices")
return filteredObjects; return filteredObjects;
}; };
// Perform a simple weighted search.
// weightedSearch is like filterForKeywords, except each field has a weight
// and the result is a sorted array of matches. filterFields is an array of
// objects with keys `path` and `weight`.
var weightedSearch = function(objects, filterFields, keywords) {
if (_.isEmpty(keywords)) {
return [];
}
var results = [];
_.each(objects, function(object) {
// Keep a score for matches, weighted by field.
var score = 0;
_.each(keywords, function(regex) {
var matchesKeyword = false;
_.each(filterFields, function(field) {
var value = _.get(object, field.path);
if (!value) {
return;
}
if (regex.test(value)) {
// For each matching keyword, add the field weight to the score.
score += field.weight;
matchesKeyword = true;
}
});
if (!matchesKeyword) {
// We've missed a keyword. Set score to 0 and short circuit the loop.
score = 0;
return false;
}
});
if (score > 0) {
results.push({
object: object,
score: score
});
}
});
// Sort first by score, then by display name for items that have the same score.
var orderedResult = _.orderBy(results, ['score', displayName], ['desc', 'asc']);
return _.map(orderedResult, 'object');
};
return { return {
filterForKeywords: filterForKeywords, filterForKeywords: filterForKeywords,
weightedSearch: weightedSearch,
generateKeywords: generateKeywords generateKeywords: generateKeywords
}; };
}); });
...@@ -107,4 +107,94 @@ describe('KeywordService', function() { ...@@ -107,4 +107,94 @@ describe('KeywordService', function() {
expect(result).toEqual(mockData); expect(result).toEqual(mockData);
}); });
}); });
describe('#weightedSearch', function() {
var mockData = {
'sample-app': {
metadata: {
name: 'sample-app',
annotations: {
'openshift.io/display-name': 'Sample App',
'openshift.io/description': 'Sample app that uses Maria'
}
}
},
'maria-db': {
metadata: {
name: 'maria-db',
annotations: {
'openshift.io/display-name': 'Maria DB'
}
}
},
'mongo-db': {
metadata: {
name: 'mongo-db',
annotations: {
'openshift.io/display-name': 'Mongo DB',
tags: 'db'
}
}
}
};
it('should honor field weights', function() {
var keywords = KeywordService.generateKeywords('maria');
var result = KeywordService.weightedSearch(mockData, [{
path: 'metadata.annotations["openshift.io/display-name"]',
weight: 10
}, {
path: 'metadata.annotations["openshift.io/description"]',
weight: 2
}], keywords);
expect(result).toEqual([ mockData['maria-db'], mockData['sample-app'] ]);
});
it('should weight multiple matches higher', function() {
var keywords = KeywordService.generateKeywords('db');
var result = KeywordService.weightedSearch(mockData, [{
path: 'metadata.annotations["openshift.io/display-name"]',
weight: 10
}, {
path: 'metadata.annotations.tags',
weight: 2
}], keywords);
expect(result).toEqual([ mockData['mongo-db'], mockData['maria-db'] ]);
});
it('should match all keywords', function() {
var keywords = KeywordService.generateKeywords('maria db');
var result = KeywordService.weightedSearch(mockData, [{
path: 'metadata.annotations["openshift.io/display-name"]',
weight: 10
}, {
path: 'metadata.annotations["openshift.io/description"]',
weight: 2
}], keywords);
expect(result).toEqual([ mockData['maria-db'] ]);
});
it('should return an empty array when no keywords', function() {
var result = KeywordService.weightedSearch(mockData, [{
path: 'metadata.annotations["openshift.io/display-name"]',
weight: 10
}, {
path: 'metadata.annotations["openshift.io/description"]',
weight: 2
}], []);
expect(result.length).toEqual(0);
});
it('should return an empty array when no matches', function() {
var keywords = KeywordService.generateKeywords('MISSING');
var result = KeywordService.weightedSearch(mockData, [{
path: 'metadata.annotations["openshift.io/display-name"]',
weight: 10
}, {
path: 'metadata.annotations["openshift.io/description"]',
weight: 2
}], keywords);
expect(result.length).toEqual(0);
});
});
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment