From 72c4271dd7a47995602559301f37d3737bccbeb6 Mon Sep 17 00:00:00 2001 From: Joel Labes Date: Sat, 10 Oct 2020 22:46:21 +1300 Subject: [PATCH 1/6] Swap from exact substring matches to each individual word matching --- src/app/components/search/search.js | 23 ++++++++++++++++++++--- src/app/services/project_service.js | 17 +++++++++-------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/app/components/search/search.js b/src/app/components/search/search.js index 62053c1fd..3ef0ab843 100644 --- a/src/app/components/search/search.js +++ b/src/app/components/search/search.js @@ -80,7 +80,8 @@ angular scope.shorten = function(text) { if(text != null && text.length > 0){ let modified = text.replace(/\s+/g, ' '); - let indexOfInstance = modified.search(scope.query); + let first_token = splitQuery(scope.query)[0]; //choose the first word in the search as the anchor for shortening + let indexOfInstance = modified.search(first_token); let startIndex = (indexOfInstance - 75) < 0? 0: indexOfInstance - 75; let endIndex = (indexOfInstance + 75) > modified.length? modified.length: indexOfInstance + 75; let shortened = "..." + modified.substring(startIndex, endIndex) + "..."; @@ -93,7 +94,12 @@ angular if (!scope.query || !text) { return $sce.trustAsHtml(text); } - return $sce.trustAsHtml(text.replace(new RegExp(scope.query, 'gi'), '$&')); + //wrap each word in a capturing group with a pipe between them, to allow any of the matches to highlight + //e.g. "hello WORLD" changes to "(hello)|(world)" + let query_segments = splitQuery(scope.query); + let escaped_segments = query_segments.map(segment => escapeRegExp(segment)); + let highlight_words = "(" + escaped_segments.join(")|(") + ")"; + return $sce.trustAsHtml(text.replace(new RegExp(highlight_words, 'gi'), '$&')); } scope.$watch("query", function(nv, ov) { @@ -105,8 +111,10 @@ angular scope.columnFilter = function(columns) { var matches = []; + let query_segments = splitQuery(scope.query); + for (var column in columns) { - if (column.toLowerCase().indexOf(scope.query.toLowerCase()) != -1) { + if (query_segments.every(segment => column.toLowerCase().indexOf(segment) != -1)) { matches.push(column); } } @@ -116,6 +124,15 @@ angular scope.limitColumns = function(id) { return scope.limit_columns[id] !== undefined? scope.limit_columns[id] : 3; } + + //from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + function escapeRegExp(string) { + return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string + } + + function splitQuery(query){ + return query.toLowerCase().split(" ").filter(s => s.length > 0); + } } } }]); diff --git a/src/app/services/project_service.js b/src/app/services/project_service.js index 25872c12b..bfc826841 100644 --- a/src/app/services/project_service.js +++ b/src/app/services/project_service.js @@ -261,7 +261,7 @@ angular }); } - function fuzzySearchObj(val, obj) { + function fuzzySearchObj(query, obj) { var objects = []; var search_keys = { 'name':'string', @@ -271,23 +271,24 @@ angular 'tags': 'array', 'arguments': 'array', }; - var search = new RegExp(val, "i") + + let query_segments = query.toLowerCase().split(" ").filter(s => s.length > 0); for (var i in search_keys) { if (!obj[i]) { continue; - } else if (search_keys[i] === 'string' && obj[i].toLowerCase().indexOf(val.toLowerCase()) != -1) { - objects.push({key: i, value: val}); + } else if (search_keys[i] === 'string' && query_segments.every(segment => obj[i].toLowerCase().indexOf(segment) != -1)) { + objects.push({key: i, value: query}); } else if (search_keys[i] === 'object') { for (var column_name in obj[i]) { - if (obj[i][column_name]["name"].toLowerCase().indexOf(val.toLowerCase()) != -1) { - objects.push({key: i, value: val}); + if (query_segments.every(segment => obj[i][column_name]["name"].toLowerCase().indexOf(segment) != -1)) { + objects.push({key: i, value: query}); } } } else if (search_keys[i] === 'array') { for (var tag of obj[i]) { - if (JSON.stringify(tag).toLowerCase().indexOf(val.toLowerCase()) != -1) { - objects.push({key: i, value: val}); + if (query_segments.every(segment => JSON.stringify(tag).toLowerCase().indexOf(segment) != -1)) { + objects.push({key: i, value: query}); } } } From b2bc0b7b52a1399ff9cf769c26b1d6337962cd7d Mon Sep 17 00:00:00 2001 From: Joel Labes Date: Sat, 10 Oct 2020 23:20:33 +1300 Subject: [PATCH 2/6] Remove redundant keyboard logging --- src/app/main/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/main/index.js b/src/app/main/index.js index fb72d0aaf..01c60250a 100644 --- a/src/app/main/index.js +++ b/src/app/main/index.js @@ -66,7 +66,6 @@ angular }); $scope.onSearchKeypress = function(e) { - console.log(e); if (e.key == 'Escape') { $scope.clearSearch(); e.preventDefault(); From 09a06f2947d7c74b0bec5498d5e97bb4e079481b Mon Sep 17 00:00:00 2001 From: Joel Labes Date: Sat, 10 Oct 2020 23:21:16 +1300 Subject: [PATCH 3/6] Avoid "Invalid regular expression" error when searching for just *, populate changelog --- CHANGELOG.md | 2 ++ src/app/components/search/search.js | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cc7f1d55..54fb4d206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,12 @@ ## dbt 0.19.0 (Release TBD) - Add select/deselect option in DAG view dropups. ([docs#98](https://github.com/fishtown-analytics/dbt-docs/issues/98)) - Fixed issue where sources with tags were not showing up in graph viz ([docs#93](https://github.com/fishtown-analytics/dbt-docs/issues/93)) +- Searches no longer require perfect matches, and instead consider each word individually. `my model` or `model my` will now find `my_model`, without the need for underscores ([docs#143](https://github.com/fishtown-analytics/dbt-docs/issues/143)) Contributors: - [@Mr-Nobody99](https://github.com/Mr-Nobody99) ([docs#138](https://github.com/fishtown-analytics/dbt-docs/pull/138)) - [@jplynch77](https://github.com/jplynch77) ([docs#139](https://github.com/fishtown-analytics/dbt-docs/pull/139)) +- [@joellabes](https://github.com/joellabes) ([docs#145](https://github.com/fishtown-analytics/dbt-docs/pull/145)) ## dbt 0.18.1 (Unreleased) - Add Exposure nodes ([docs#135](https://github.com/fishtown-analytics/dbt-docs/issues/135), [docs#136](https://github.com/fishtown-analytics/dbt-docs/pull/136), [docs#137](https://github.com/fishtown-analytics/dbt-docs/pull/137)) diff --git a/src/app/components/search/search.js b/src/app/components/search/search.js index 3ef0ab843..5df047862 100644 --- a/src/app/components/search/search.js +++ b/src/app/components/search/search.js @@ -80,8 +80,10 @@ angular scope.shorten = function(text) { if(text != null && text.length > 0){ let modified = text.replace(/\s+/g, ' '); - let first_token = splitQuery(scope.query)[0]; //choose the first word in the search as the anchor for shortening - let indexOfInstance = modified.search(first_token); + //choose the first word in the search as the anchor for shortening. + //Escaping in case the first token is "*" or another reserved regex character + let first_token = escapeRegExp(splitQuery(scope.query)[0]); + let indexOfInstance = modified.search(new RegExp(first_token)); let startIndex = (indexOfInstance - 75) < 0? 0: indexOfInstance - 75; let endIndex = (indexOfInstance + 75) > modified.length? modified.length: indexOfInstance + 75; let shortened = "..." + modified.substring(startIndex, endIndex) + "..."; From 3c36ab4ebe47d46501eb00a5d82868d25aca9307 Mon Sep 17 00:00:00 2001 From: Joel Labes Date: Tue, 13 Oct 2020 11:10:41 +1300 Subject: [PATCH 4/6] Trim whitespace to avoid an empty array coming back from splitQuery --- src/app/components/search/search.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/search/search.js b/src/app/components/search/search.js index 5df047862..e111c4ca4 100644 --- a/src/app/components/search/search.js +++ b/src/app/components/search/search.js @@ -78,7 +78,7 @@ angular }); scope.shorten = function(text) { - if(text != null && text.length > 0){ + if(text != null && text.trim().length > 0){ let modified = text.replace(/\s+/g, ' '); //choose the first word in the search as the anchor for shortening. //Escaping in case the first token is "*" or another reserved regex character From 314cfb86ae6e482a4944e99ba485dc1cd5d84e64 Mon Sep 17 00:00:00 2001 From: Joel Labes Date: Tue, 13 Oct 2020 11:18:22 +1300 Subject: [PATCH 5/6] Actually check query, not the text to truncate (doh) --- src/app/components/search/search.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/components/search/search.js b/src/app/components/search/search.js index e111c4ca4..024aa549b 100644 --- a/src/app/components/search/search.js +++ b/src/app/components/search/search.js @@ -78,14 +78,14 @@ angular }); scope.shorten = function(text) { - if(text != null && text.trim().length > 0){ + if(text != null && text.trim().length > 0 && scope.query != null && scope.query.trim().length > 0){ let modified = text.replace(/\s+/g, ' '); //choose the first word in the search as the anchor for shortening. //Escaping in case the first token is "*" or another reserved regex character let first_token = escapeRegExp(splitQuery(scope.query)[0]); let indexOfInstance = modified.search(new RegExp(first_token)); - let startIndex = (indexOfInstance - 75) < 0? 0: indexOfInstance - 75; - let endIndex = (indexOfInstance + 75) > modified.length? modified.length: indexOfInstance + 75; + let startIndex = (indexOfInstance - 75) < 0 ? 0 : indexOfInstance - 75; + let endIndex = (indexOfInstance + 75) > modified.length ? modified.length : indexOfInstance + 75; let shortened = "..." + modified.substring(startIndex, endIndex) + "..."; return shortened; } @@ -124,7 +124,7 @@ angular } scope.limitColumns = function(id) { - return scope.limit_columns[id] !== undefined? scope.limit_columns[id] : 3; + return scope.limit_columns[id] !== undefined ? scope.limit_columns[id] : 3; } //from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions From c9d9244bf413008fc1f8e6213ed647f51fe22d99 Mon Sep 17 00:00:00 2001 From: Joel Labes Date: Mon, 18 Jul 2022 14:44:52 +1200 Subject: [PATCH 6/6] Code review - use lodash's words function --- src/app/components/search/search.js | 10 +++++----- src/app/services/project_service.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/components/search/search.js b/src/app/components/search/search.js index 024aa549b..2cb7e8f67 100644 --- a/src/app/components/search/search.js +++ b/src/app/components/search/search.js @@ -82,7 +82,7 @@ angular let modified = text.replace(/\s+/g, ' '); //choose the first word in the search as the anchor for shortening. //Escaping in case the first token is "*" or another reserved regex character - let first_token = escapeRegExp(splitQuery(scope.query)[0]); + let first_token = escapeRegExp(getQueryTokens(scope.query)[0]); let indexOfInstance = modified.search(new RegExp(first_token)); let startIndex = (indexOfInstance - 75) < 0 ? 0 : indexOfInstance - 75; let endIndex = (indexOfInstance + 75) > modified.length ? modified.length : indexOfInstance + 75; @@ -98,7 +98,7 @@ angular } //wrap each word in a capturing group with a pipe between them, to allow any of the matches to highlight //e.g. "hello WORLD" changes to "(hello)|(world)" - let query_segments = splitQuery(scope.query); + let query_segments = getQueryTokens(scope.query); let escaped_segments = query_segments.map(segment => escapeRegExp(segment)); let highlight_words = "(" + escaped_segments.join(")|(") + ")"; return $sce.trustAsHtml(text.replace(new RegExp(highlight_words, 'gi'), '$&')); @@ -113,7 +113,7 @@ angular scope.columnFilter = function(columns) { var matches = []; - let query_segments = splitQuery(scope.query); + let query_segments = getQueryTokens(scope.query); for (var column in columns) { if (query_segments.every(segment => column.toLowerCase().indexOf(segment) != -1)) { @@ -132,8 +132,8 @@ angular return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } - function splitQuery(query){ - return query.toLowerCase().split(" ").filter(s => s.length > 0); + function getQueryTokens(query){ + return _.words(query.toLowerCase()); } } } diff --git a/src/app/services/project_service.js b/src/app/services/project_service.js index f422534a0..3fb55ad24 100644 --- a/src/app/services/project_service.js +++ b/src/app/services/project_service.js @@ -266,7 +266,7 @@ angular 'arguments': 'array', }; - let query_segments = query.toLowerCase().split(" ").filter(s => s.length > 0); + let query_segments = _.words(query.toLowerCase()); for (var i in search_keys) { if (!obj[i]) {