improv: use statusFilter instead of includeArchive,includeDrafts for document search (#6537)
* improv: use statusFilter instead of includeArchive,includeDrafts for document search * improv: update FilterComponent to add support for multiple selected items * feat: update document type search ui * fix test * Restore support for old parameters to avoid breaking change --------- Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { DocumentPermission } from "@shared/types";
|
||||
import { DocumentPermission, StatusFilter } from "@shared/types";
|
||||
import SearchHelper from "@server/models/helpers/SearchHelper";
|
||||
import {
|
||||
buildDocument,
|
||||
@@ -208,28 +208,30 @@ describe("SearchHelper", () => {
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
archivedAt: new Date(),
|
||||
});
|
||||
const { results } = await SearchHelper.searchForUser(user, "test", {
|
||||
includeDrafts: true,
|
||||
statusFilter: [StatusFilter.Draft],
|
||||
});
|
||||
expect(results.length).toBe(1);
|
||||
});
|
||||
|
||||
test("should not include drafts", async () => {
|
||||
test("should not include drafts with user read permission", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDraftDocument({
|
||||
teamId: user.teamId,
|
||||
userId: user.id,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
const { results } = await SearchHelper.searchForUser(user, "test", {
|
||||
includeDrafts: false,
|
||||
});
|
||||
expect(results.length).toBe(0);
|
||||
});
|
||||
|
||||
test("should not include drafts with user permission", async () => {
|
||||
const user = await buildUser();
|
||||
const draft = await buildDraftDocument({
|
||||
teamId: user.teamId,
|
||||
userId: user.id,
|
||||
@@ -244,32 +246,105 @@ describe("SearchHelper", () => {
|
||||
});
|
||||
|
||||
const { results } = await SearchHelper.searchForUser(user, "test", {
|
||||
includeDrafts: false,
|
||||
statusFilter: [StatusFilter.Published, StatusFilter.Archived],
|
||||
});
|
||||
expect(results.length).toBe(0);
|
||||
});
|
||||
|
||||
test("should include results from drafts as well", async () => {
|
||||
test("should search only published created by user", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "not draft",
|
||||
title: "test",
|
||||
});
|
||||
await buildDraftDocument({
|
||||
teamId: user.teamId,
|
||||
userId: user.id,
|
||||
createdById: user.id,
|
||||
title: "draft",
|
||||
title: "test",
|
||||
});
|
||||
const { results } = await SearchHelper.searchForUser(user, "draft", {
|
||||
includeDrafts: true,
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
archivedAt: new Date(),
|
||||
});
|
||||
const { results } = await SearchHelper.searchForUser(user, "test", {
|
||||
statusFilter: [StatusFilter.Published],
|
||||
});
|
||||
expect(results.length).toBe(1);
|
||||
});
|
||||
|
||||
test("should search only archived documents created by user", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDocument({
|
||||
title: "test",
|
||||
});
|
||||
await buildDraftDocument({
|
||||
teamId: user.teamId,
|
||||
userId: user.id,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
archivedAt: new Date(),
|
||||
});
|
||||
const { results } = await SearchHelper.searchForUser(user, "test", {
|
||||
statusFilter: [StatusFilter.Archived],
|
||||
});
|
||||
expect(results.length).toBe(1);
|
||||
});
|
||||
|
||||
test("should return results from archived and published", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDraftDocument({
|
||||
teamId: user.teamId,
|
||||
userId: user.id,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
archivedAt: new Date(),
|
||||
});
|
||||
const { results } = await SearchHelper.searchForUser(user, "test", {
|
||||
statusFilter: [StatusFilter.Archived, StatusFilter.Published],
|
||||
});
|
||||
expect(results.length).toBe(2);
|
||||
});
|
||||
|
||||
test("should not include results from drafts", async () => {
|
||||
test("should return results from drafts and published", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
@@ -283,10 +358,44 @@ describe("SearchHelper", () => {
|
||||
createdById: user.id,
|
||||
title: "draft",
|
||||
});
|
||||
const { results } = await SearchHelper.searchForUser(user, "draft", {
|
||||
includeDrafts: false,
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "archived not draft",
|
||||
archivedAt: new Date(),
|
||||
});
|
||||
expect(results.length).toBe(1);
|
||||
const { results } = await SearchHelper.searchForUser(user, "draft", {
|
||||
statusFilter: [StatusFilter.Published, StatusFilter.Draft],
|
||||
});
|
||||
expect(results.length).toBe(2);
|
||||
});
|
||||
|
||||
test("should include results from drafts and archived", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "not draft",
|
||||
});
|
||||
await buildDraftDocument({
|
||||
teamId: user.teamId,
|
||||
userId: user.id,
|
||||
createdById: user.id,
|
||||
title: "draft",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "archived not draft",
|
||||
archivedAt: new Date(),
|
||||
});
|
||||
const { results } = await SearchHelper.searchForUser(user, "draft", {
|
||||
statusFilter: [StatusFilter.Draft, StatusFilter.Archived],
|
||||
});
|
||||
expect(results.length).toBe(2);
|
||||
});
|
||||
|
||||
test("should return the total count of search results", async () => {
|
||||
@@ -421,38 +530,37 @@ describe("SearchHelper", () => {
|
||||
test("should search only drafts created by user", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDraftDocument({
|
||||
teamId: user.teamId,
|
||||
userId: user.id,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
const documents = await SearchHelper.searchTitlesForUser(user, "test", {
|
||||
includeDrafts: true,
|
||||
});
|
||||
expect(documents.length).toBe(1);
|
||||
});
|
||||
|
||||
test("should not include drafts", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDraftDocument({
|
||||
teamId: user.teamId,
|
||||
userId: user.id,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
const documents = await SearchHelper.searchTitlesForUser(user, "test", {
|
||||
includeDrafts: false,
|
||||
});
|
||||
expect(documents.length).toBe(0);
|
||||
});
|
||||
|
||||
test("should include results from drafts as well", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "not test",
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
archivedAt: new Date(),
|
||||
});
|
||||
const documents = await SearchHelper.searchTitlesForUser(user, "test", {
|
||||
statusFilter: [StatusFilter.Draft],
|
||||
});
|
||||
expect(documents.length).toBe(1);
|
||||
});
|
||||
|
||||
test("should search only published created by user", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDocument({
|
||||
title: "test",
|
||||
});
|
||||
await buildDraftDocument({
|
||||
teamId: user.teamId,
|
||||
@@ -460,30 +568,140 @@ describe("SearchHelper", () => {
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
archivedAt: new Date(),
|
||||
});
|
||||
const documents = await SearchHelper.searchTitlesForUser(user, "test", {
|
||||
includeDrafts: true,
|
||||
statusFilter: [StatusFilter.Published],
|
||||
});
|
||||
expect(documents.length).toBe(1);
|
||||
});
|
||||
|
||||
test("should search only archived documents created by user", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDocument({
|
||||
title: "test",
|
||||
});
|
||||
await buildDraftDocument({
|
||||
teamId: user.teamId,
|
||||
userId: user.id,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
archivedAt: new Date(),
|
||||
});
|
||||
const documents = await SearchHelper.searchTitlesForUser(user, "test", {
|
||||
statusFilter: [StatusFilter.Archived],
|
||||
});
|
||||
expect(documents.length).toBe(1);
|
||||
});
|
||||
|
||||
test("should return results from archived and published", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDraftDocument({
|
||||
teamId: user.teamId,
|
||||
userId: user.id,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
archivedAt: new Date(),
|
||||
});
|
||||
const documents = await SearchHelper.searchTitlesForUser(user, "test", {
|
||||
statusFilter: [StatusFilter.Archived, StatusFilter.Published],
|
||||
});
|
||||
expect(documents.length).toBe(2);
|
||||
});
|
||||
|
||||
test("should not include results from drafts", async () => {
|
||||
test("should return results from drafts and published", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "not test",
|
||||
title: "not draft",
|
||||
});
|
||||
await buildDraftDocument({
|
||||
teamId: user.teamId,
|
||||
userId: user.id,
|
||||
createdById: user.id,
|
||||
title: "test",
|
||||
title: "draft",
|
||||
});
|
||||
const documents = await SearchHelper.searchTitlesForUser(user, "test", {
|
||||
includeDrafts: false,
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "archived not draft",
|
||||
archivedAt: new Date(),
|
||||
});
|
||||
expect(documents.length).toBe(1);
|
||||
const documents = await SearchHelper.searchTitlesForUser(user, "draft", {
|
||||
statusFilter: [StatusFilter.Published, StatusFilter.Draft],
|
||||
});
|
||||
expect(documents.length).toBe(2);
|
||||
});
|
||||
|
||||
test("should include results from drafts and archived", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "not draft",
|
||||
});
|
||||
await buildDraftDocument({
|
||||
teamId: user.teamId,
|
||||
userId: user.id,
|
||||
createdById: user.id,
|
||||
title: "draft",
|
||||
});
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
title: "archived not draft",
|
||||
archivedAt: new Date(),
|
||||
});
|
||||
const documents = await SearchHelper.searchTitlesForUser(user, "draft", {
|
||||
statusFilter: [StatusFilter.Draft, StatusFilter.Archived],
|
||||
});
|
||||
expect(documents.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import find from "lodash/find";
|
||||
import map from "lodash/map";
|
||||
import queryParser from "pg-tsquery";
|
||||
import { Op, Sequelize, WhereOptions } from "sequelize";
|
||||
import { DateFilter } from "@shared/types";
|
||||
import { DateFilter, StatusFilter } from "@shared/types";
|
||||
import Collection from "@server/models/Collection";
|
||||
import Document from "@server/models/Document";
|
||||
import Share from "@server/models/Share";
|
||||
@@ -36,12 +36,10 @@ type SearchOptions = {
|
||||
share?: Share;
|
||||
/** Limit results to a date range. */
|
||||
dateFilter?: DateFilter;
|
||||
/** Status of the documents to return */
|
||||
statusFilter?: StatusFilter[];
|
||||
/** Limit results to a list of users that collaborated on the document. */
|
||||
collaboratorIds?: string[];
|
||||
/** Include archived documents in the results */
|
||||
includeArchived?: boolean;
|
||||
/** Include draft documents in the results (will only ever return your own) */
|
||||
includeDrafts?: boolean;
|
||||
/** The minimum number of words to be returned in the contextual snippet */
|
||||
snippetMinWords?: number;
|
||||
/** The maximum number of words to be returned in the contextual snippet */
|
||||
@@ -356,36 +354,59 @@ export default class SearchHelper {
|
||||
});
|
||||
}
|
||||
|
||||
if (!options.includeArchived) {
|
||||
where[Op.and].push({
|
||||
archivedAt: {
|
||||
[Op.eq]: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.includeDrafts && model instanceof User) {
|
||||
where[Op.and].push({
|
||||
[Op.or]: [
|
||||
const statusQuery = [];
|
||||
if (options.statusFilter?.includes(StatusFilter.Published)) {
|
||||
statusQuery.push({
|
||||
[Op.and]: [
|
||||
{
|
||||
publishedAt: {
|
||||
[Op.ne]: null,
|
||||
},
|
||||
archivedAt: {
|
||||
[Op.eq]: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
createdById: model.id,
|
||||
},
|
||||
{ "$memberships.id$": { [Op.ne]: null } },
|
||||
],
|
||||
});
|
||||
} else {
|
||||
where[Op.and].push({
|
||||
publishedAt: {
|
||||
}
|
||||
|
||||
if (
|
||||
options.statusFilter?.includes(StatusFilter.Draft) &&
|
||||
// Only ever include draft results for the user's own documents
|
||||
model instanceof User
|
||||
) {
|
||||
statusQuery.push({
|
||||
[Op.and]: [
|
||||
{
|
||||
publishedAt: {
|
||||
[Op.eq]: null,
|
||||
},
|
||||
archivedAt: {
|
||||
[Op.eq]: null,
|
||||
},
|
||||
[Op.or]: [
|
||||
{ createdById: model.id },
|
||||
{ "$memberships.id$": { [Op.ne]: null } },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (options.statusFilter?.includes(StatusFilter.Archived)) {
|
||||
statusQuery.push({
|
||||
archivedAt: {
|
||||
[Op.ne]: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.statusFilter?.length) {
|
||||
where[Op.and].push({
|
||||
[Op.or]: statusQuery,
|
||||
});
|
||||
}
|
||||
|
||||
return where;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user