Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swap from exact substring matches to each individual word matching #145

Merged
merged 8 commits into from
Jul 21, 2022
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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))
Expand Down
33 changes: 26 additions & 7 deletions src/app/components/search/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,14 @@ angular
});

scope.shorten = function(text) {
if(text != null && text.length > 0){
if(text != null && text.trim().length > 0 && scope.query != null && scope.query.trim().length > 0){
let modified = text.replace(/\s+/g, ' ');
let indexOfInstance = modified.search(scope.query);
let startIndex = (indexOfInstance - 75) < 0? 0: indexOfInstance - 75;
let endIndex = (indexOfInstance + 75) > modified.length? modified.length: indexOfInstance + 75;
//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) + "...";
return shortened;
}
Expand All @@ -93,7 +96,12 @@ angular
if (!scope.query || !text) {
return $sce.trustAsHtml(text);
}
return $sce.trustAsHtml(text.replace(new RegExp(scope.query, 'gi'), '<span class="search-result-match">$&</span>'));
//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'), '<span class="search-result-match">$&</span>'));
}

scope.$watch("query", function(nv, ov) {
Expand All @@ -105,16 +113,27 @@ 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);
}
}
return matches;
}

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
function escapeRegExp(string) {
return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}

function splitQuery(query){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: a better name for this might be getQueryTokens, since it is unclear what the query is being split on without reading this function. Also, if you use lodash's words method, I think this function becomes unnecessary _.words(text).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep that's a better name! Have kept the function for lowercasing purposes but have swapped out all the icky split stuff for _.words

return query.toLowerCase().split(" ").filter(s => s.length > 0);
}
}
}
Expand Down
1 change: 0 additions & 1 deletion src/app/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ angular
});

$scope.onSearchKeypress = function(e) {
console.log(e);
if (e.key == 'Escape') {
$scope.clearSearch();
e.preventDefault();
Expand Down
17 changes: 9 additions & 8 deletions src/app/services/project_service.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ angular
});
}

function fuzzySearchObj(val, obj) {
function fuzzySearchObj(query, obj) {
var objects = [];
var search_keys = {
'name':'string',
Expand All @@ -271,23 +271,24 @@ angular
'tags': 'array',
'arguments': 'array',
};
var search = new RegExp(val, "i")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was never used


let query_segments = query.toLowerCase().split(" ").filter(s => s.length > 0);
joellabes marked this conversation as resolved.
Show resolved Hide resolved

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});
}
}
}
Expand Down