Improved search filtering (#940)

* Filter search by collectionId

* Improve spec, remove recursive import

* Add userId filter for documents.search

* 💚

* Search filter UI

* WIP UI

* Date filtering
Prevent dupe menu

* Refactor

* button

* Added year option, improved hover states

* Add new indexes

* Remove manual string interpolation in SQL construction

* Move dateFilter validation to controller

* Fixes: Double query when changing filter
Fixes: Visual jump between filters in dropdown

* Add option to clear filters

* More clearly define dropdowns in dark mode

* Checkbox -> Checkmark
This commit is contained in:
Tom Moor
2019-04-23 07:31:20 -07:00
committed by GitHub
parent a256eba856
commit da7fdfef0a
23 changed files with 679 additions and 76 deletions

View File

@ -215,32 +215,60 @@ Document.searchForUser = async (
const offset = options.offset || 0;
const wildcardQuery = `${sequelize.escape(query)}:*`;
const sql = `
SELECT
id,
ts_rank(documents."searchVector", to_tsquery('english', :query)) as "searchRanking",
ts_headline('english', "text", to_tsquery('english', :query), 'MaxFragments=1, MinWords=20, MaxWords=30') as "searchContext"
FROM documents
WHERE "searchVector" @@ to_tsquery('english', :query) AND
"collectionId" IN(:collectionIds) AND
${options.includeArchived ? '' : '"archivedAt" IS NULL AND'}
"deletedAt" IS NULL AND
("publishedAt" IS NOT NULL OR "createdById" = '${user.id}')
ORDER BY
"searchRanking" DESC,
"updatedAt" DESC
LIMIT :limit
OFFSET :offset;
`;
// Ensure we're filtering by the users accessible collections. If
// collectionId is passed as an option it is assumed that the authorization
// has already been done in the router
let collectionIds;
if (options.collectionId) {
collectionIds = [options.collectionId];
} else {
collectionIds = await user.collectionIds();
}
let dateFilter;
if (options.dateFilter) {
dateFilter = `1 ${options.dateFilter}`;
}
// Build the SQL query to get documentIds, ranking, and search term context
const sql = `
SELECT
id,
ts_rank(documents."searchVector", to_tsquery('english', :query)) as "searchRanking",
ts_headline('english', "text", to_tsquery('english', :query), 'MaxFragments=1, MinWords=20, MaxWords=30') as "searchContext"
FROM documents
WHERE "searchVector" @@ to_tsquery('english', :query) AND
"teamId" = :teamId AND
"collectionId" IN(:collectionIds) AND
${
options.dateFilter ? '"updatedAt" > now() - interval :dateFilter AND' : ''
}
${
options.collaboratorIds
? '"collaboratorIds" @> ARRAY[:collaboratorIds]::uuid[] AND'
: ''
}
${options.includeArchived ? '' : '"archivedAt" IS NULL AND'}
"deletedAt" IS NULL AND
("publishedAt" IS NOT NULL OR "createdById" = :userId)
ORDER BY
"searchRanking" DESC,
"updatedAt" DESC
LIMIT :limit
OFFSET :offset;
`;
const collectionIds = await user.collectionIds();
const results = await sequelize.query(sql, {
type: sequelize.QueryTypes.SELECT,
replacements: {
teamId: user.teamId,
userId: user.id,
collaboratorIds: options.collaboratorIds,
query: wildcardQuery,
limit,
offset,
collectionIds,
dateFilter,
},
});