Include saved views in global search

This commit is contained in:
shamoon 2024-04-19 11:02:13 -07:00
parent 05495794e8
commit 314e34c3b7
9 changed files with 721 additions and 551 deletions

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,9 @@
@if (type === DataType.Document) { @if (type === DataType.Document) {
<i-bs width="1em" height="1em" name="pencil"></i-bs> <i-bs width="1em" height="1em" name="pencil"></i-bs>
<span>&nbsp;<ng-container i18n>Open</ng-container></span> <span>&nbsp;<ng-container i18n>Open</ng-container></span>
} @else if (type === DataType.SavedView) {
<i-bs width="1em" height="1em" name="eye"></i-bs>
<span>&nbsp;<ng-container i18n>Open</ng-container></span>
} @else if (type === DataType.Workflow || type === DataType.CustomField || type === DataType.Group || type === DataType.User || type === DataType.MailAccount || type === DataType.MailRule) { } @else if (type === DataType.Workflow || type === DataType.CustomField || type === DataType.Group || type === DataType.User || type === DataType.MailAccount || type === DataType.MailRule) {
<i-bs width="1em" height="1em" name="pencil"></i-bs> <i-bs width="1em" height="1em" name="pencil"></i-bs>
<span>&nbsp;<ng-container i18n>Edit</ng-container></span> <span>&nbsp;<ng-container i18n>Edit</ng-container></span>
@ -41,7 +44,7 @@
<span>&nbsp;<ng-container i18n>Filter documents</ng-container></span> <span>&nbsp;<ng-container i18n>Filter documents</ng-container></span>
} }
</button> </button>
@if (type !== DataType.Workflow && type !== DataType.CustomField && type !== DataType.Group && type !== DataType.User && type !== DataType.MailAccount && type !== DataType.MailRule) { @if (type !== DataType.SavedView && type !== DataType.Workflow && type !== DataType.CustomField && type !== DataType.Group && type !== DataType.User && type !== DataType.MailAccount && type !== DataType.MailRule) {
<button #secondaryButton type="button" class="btn btn-sm btn-outline-primary d-flex" <button #secondaryButton type="button" class="btn btn-sm btn-outline-primary d-flex"
(click)="secondaryAction(type, item); $event.stopPropagation()" (click)="secondaryAction(type, item); $event.stopPropagation()"
[disabled]="disableSecondaryButton(type, item)" [disabled]="disableSecondaryButton(type, item)"
@ -69,6 +72,12 @@
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: document, nameProp: 'title', type: DataType.Document, icon: 'file-text', date: document.added}"></ng-container> <ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: document, nameProp: 'title', type: DataType.Document, icon: 'file-text', date: document.added}"></ng-container>
} }
} }
@if (searchResults?.saved_views.length) {
<h6 class="dropdown-header" i18n="@@searchResults.saved_views">Saved Views</h6>
@for (saved_view of searchResults.saved_views; track saved_view.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: saved_view, nameProp: 'name', type: DataType.SavedView, icon: 'funnel'}"></ng-container>
}
}
@if (searchResults?.tags.length) { @if (searchResults?.tags.length) {
<h6 class="dropdown-header" i18n="@@searchResults.tags">Tags</h6> <h6 class="dropdown-header" i18n="@@searchResults.tags">Tags</h6>

View File

@ -51,6 +51,12 @@ const searchResults = {
custom_fields: [], custom_fields: [],
}, },
], ],
saved_views: [
{
id: 1,
name: 'TestSavedView',
},
],
correspondents: [ correspondents: [
{ {
id: 1, id: 1,

View File

@ -112,6 +112,9 @@ export class GlobalSearchComponent implements OnInit {
case DataType.Document: case DataType.Document:
this.router.navigate(['/documents', object.id]) this.router.navigate(['/documents', object.id])
return return
case DataType.SavedView:
this.router.navigate(['/view', object.id])
return
case DataType.Correspondent: case DataType.Correspondent:
filterRuleType = FILTER_HAS_CORRESPONDENT_ANY filterRuleType = FILTER_HAS_CORRESPONDENT_ANY
break break

View File

@ -1,5 +1,6 @@
export enum DataType { export enum DataType {
Document = 'document', Document = 'document',
SavedView = 'saved_view',
Correspondent = 'correspondent', Correspondent = 'correspondent',
DocumentType = 'document_type', DocumentType = 'document_type',
StoragePath = 'storage_path', StoragePath = 'storage_path',

View File

@ -15,10 +15,12 @@ import { User } from 'src/app/data/user'
import { Workflow } from 'src/app/data/workflow' import { Workflow } from 'src/app/data/workflow'
import { SettingsService } from '../settings.service' import { SettingsService } from '../settings.service'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings' import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { SavedView } from 'src/app/data/saved-view'
export interface GlobalSearchResult { export interface GlobalSearchResult {
total: number total: number
documents: Document[] documents: Document[]
saved_views: SavedView[]
correspondents: Correspondent[] correspondents: Correspondent[]
document_types: DocumentType[] document_types: DocumentType[]
storage_paths: StoragePath[] storage_paths: StoragePath[]

View File

@ -21,6 +21,7 @@ from documents.models import CustomFieldInstance
from documents.models import Document from documents.models import Document
from documents.models import DocumentType from documents.models import DocumentType
from documents.models import Note from documents.models import Note
from documents.models import SavedView
from documents.models import StoragePath from documents.models import StoragePath
from documents.models import Tag from documents.models import Tag
from documents.models import Workflow from documents.models import Workflow
@ -1171,10 +1172,17 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
StoragePath.objects.create(name="path 2", path="path2") StoragePath.objects.create(name="path 2", path="path2")
tag1 = Tag.objects.create(name="bank tag1") tag1 = Tag.objects.create(name="bank tag1")
Tag.objects.create(name="tag2") Tag.objects.create(name="tag2")
user1 = User.objects.create_user("bank user1") user1 = User.objects.create_superuser("bank user1")
User.objects.create_user("user2") User.objects.create_user("user2")
group1 = Group.objects.create(name="bank group1") group1 = Group.objects.create(name="bank group1")
Group.objects.create(name="group2") Group.objects.create(name="group2")
SavedView.objects.create(
name="bank view",
show_on_dashboard=True,
show_in_sidebar=True,
sort_field="",
owner=user1,
)
mail_account1 = MailAccount.objects.create(name="bank mail account 1") mail_account1 = MailAccount.objects.create(name="bank mail account 1")
mail_account2 = MailAccount.objects.create(name="mail account 2") mail_account2 = MailAccount.objects.create(name="mail account 2")
mail_rule1 = MailRule.objects.create( mail_rule1 = MailRule.objects.create(
@ -1198,10 +1206,13 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
workflow1 = Workflow.objects.create(name="bank workflow 1") workflow1 = Workflow.objects.create(name="bank workflow 1")
Workflow.objects.create(name="workflow 2") Workflow.objects.create(name="workflow 2")
self.client.force_authenticate(user1)
response = self.client.get("/api/search/?query=bank") response = self.client.get("/api/search/?query=bank")
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
results = response.data results = response.data
self.assertEqual(len(results["documents"]), 2) self.assertEqual(len(results["documents"]), 2)
self.assertEqual(len(results["saved_views"]), 1)
self.assertNotEqual(results["documents"][0]["id"], d3.id) self.assertNotEqual(results["documents"][0]["id"], d3.id)
self.assertNotEqual(results["documents"][1]["id"], d3.id) self.assertNotEqual(results["documents"][1]["id"], d3.id)
self.assertEqual(results["correspondents"][0]["id"], correspondent1.id) self.assertEqual(results["correspondents"][0]["id"], correspondent1.id)

View File

@ -1135,7 +1135,13 @@ class GlobalSearchView(PassUserMixin):
)._get_query() )._get_query()
results = s.search(q, limit=OBJECT_LIMIT) results = s.search(q, limit=OBJECT_LIMIT)
docs = docs | all_docs.filter(id__in=[r["id"] for r in results]) docs = docs | all_docs.filter(id__in=[r["id"] for r in results])
saved_views = (
SavedView.objects.filter(owner=request.user, name__icontains=query)[
:OBJECT_LIMIT
]
if request.user.has_perm("documents.view_savedview")
else []
)
tags = ( tags = (
get_objects_for_user_owner_aware(request.user, "view_tag", Tag).filter( get_objects_for_user_owner_aware(request.user, "view_tag", Tag).filter(
name__icontains=query, name__icontains=query,
@ -1206,6 +1212,11 @@ class GlobalSearchView(PassUserMixin):
} }
docs_serializer = DocumentSerializer(docs, many=True, context=context) docs_serializer = DocumentSerializer(docs, many=True, context=context)
saved_views_serializer = SavedViewSerializer(
saved_views,
many=True,
context=context,
)
tags_serializer = TagSerializer(tags, many=True, context=context) tags_serializer = TagSerializer(tags, many=True, context=context)
correspondents_serializer = CorrespondentSerializer( correspondents_serializer = CorrespondentSerializer(
correspondents, correspondents,
@ -1244,6 +1255,7 @@ class GlobalSearchView(PassUserMixin):
return Response( return Response(
{ {
"total": len(docs) "total": len(docs)
+ len(saved_views)
+ len(tags) + len(tags)
+ len(correspondents) + len(correspondents)
+ len(document_types) + len(document_types)
@ -1255,6 +1267,7 @@ class GlobalSearchView(PassUserMixin):
+ len(workflows) + len(workflows)
+ len(custom_fields), + len(custom_fields),
"documents": docs_serializer.data, "documents": docs_serializer.data,
"saved_views": saved_views_serializer.data,
"tags": tags_serializer.data, "tags": tags_serializer.data,
"correspondents": correspondents_serializer.data, "correspondents": correspondents_serializer.data,
"document_types": document_types_serializer.data, "document_types": document_types_serializer.data,

View File

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: paperless-ngx\n" "Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 01:15-0700\n" "POT-Creation-Date: 2024-04-19 11:01-0700\n"
"PO-Revision-Date: 2022-02-17 04:17\n" "PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: English\n" "Language-Team: English\n"
@ -25,27 +25,27 @@ msgstr ""
msgid "owner" msgid "owner"
msgstr "" msgstr ""
#: documents/models.py:53 documents/models.py:902 #: documents/models.py:53 documents/models.py:897
msgid "None" msgid "None"
msgstr "" msgstr ""
#: documents/models.py:54 documents/models.py:903 #: documents/models.py:54 documents/models.py:898
msgid "Any word" msgid "Any word"
msgstr "" msgstr ""
#: documents/models.py:55 documents/models.py:904 #: documents/models.py:55 documents/models.py:899
msgid "All words" msgid "All words"
msgstr "" msgstr ""
#: documents/models.py:56 documents/models.py:905 #: documents/models.py:56 documents/models.py:900
msgid "Exact match" msgid "Exact match"
msgstr "" msgstr ""
#: documents/models.py:57 documents/models.py:906 #: documents/models.py:57 documents/models.py:901
msgid "Regular expression" msgid "Regular expression"
msgstr "" msgstr ""
#: documents/models.py:58 documents/models.py:907 #: documents/models.py:58 documents/models.py:902
msgid "Fuzzy word" msgid "Fuzzy word"
msgstr "" msgstr ""
@ -53,20 +53,20 @@ msgstr ""
msgid "Automatic" msgid "Automatic"
msgstr "" msgstr ""
#: documents/models.py:62 documents/models.py:397 documents/models.py:1223 #: documents/models.py:62 documents/models.py:397 documents/models.py:1218
#: paperless_mail/models.py:18 paperless_mail/models.py:93 #: paperless_mail/models.py:18 paperless_mail/models.py:93
msgid "name" msgid "name"
msgstr "" msgstr ""
#: documents/models.py:64 documents/models.py:963 #: documents/models.py:64 documents/models.py:958
msgid "match" msgid "match"
msgstr "" msgstr ""
#: documents/models.py:67 documents/models.py:966 #: documents/models.py:67 documents/models.py:961
msgid "matching algorithm" msgid "matching algorithm"
msgstr "" msgstr ""
#: documents/models.py:72 documents/models.py:971 #: documents/models.py:72 documents/models.py:966
msgid "is insensitive" msgid "is insensitive"
msgstr "" msgstr ""
@ -615,246 +615,246 @@ msgstr ""
msgid "custom field instances" msgid "custom field instances"
msgstr "" msgstr ""
#: documents/models.py:910 #: documents/models.py:905
msgid "Consumption Started" msgid "Consumption Started"
msgstr "" msgstr ""
#: documents/models.py:911 #: documents/models.py:906
msgid "Document Added" msgid "Document Added"
msgstr "" msgstr ""
#: documents/models.py:912 #: documents/models.py:907
msgid "Document Updated" msgid "Document Updated"
msgstr "" msgstr ""
#: documents/models.py:915 #: documents/models.py:910
msgid "Consume Folder" msgid "Consume Folder"
msgstr "" msgstr ""
#: documents/models.py:916 #: documents/models.py:911
msgid "Api Upload" msgid "Api Upload"
msgstr "" msgstr ""
#: documents/models.py:917 #: documents/models.py:912
msgid "Mail Fetch" msgid "Mail Fetch"
msgstr "" msgstr ""
#: documents/models.py:920 #: documents/models.py:915
msgid "Workflow Trigger Type" msgid "Workflow Trigger Type"
msgstr "" msgstr ""
#: documents/models.py:932 #: documents/models.py:927
msgid "filter path" msgid "filter path"
msgstr "" msgstr ""
#: documents/models.py:937 #: documents/models.py:932
msgid "" msgid ""
"Only consume documents with a path that matches this if specified. Wildcards " "Only consume documents with a path that matches this if specified. Wildcards "
"specified as * are allowed. Case insensitive." "specified as * are allowed. Case insensitive."
msgstr "" msgstr ""
#: documents/models.py:944 #: documents/models.py:939
msgid "filter filename" msgid "filter filename"
msgstr "" msgstr ""
#: documents/models.py:949 paperless_mail/models.py:148 #: documents/models.py:944 paperless_mail/models.py:148
msgid "" msgid ""
"Only consume documents which entirely match this filename if specified. " "Only consume documents which entirely match this filename if specified. "
"Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." "Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
msgstr "" msgstr ""
#: documents/models.py:960 #: documents/models.py:955
msgid "filter documents from this mail rule" msgid "filter documents from this mail rule"
msgstr "" msgstr ""
#: documents/models.py:976 #: documents/models.py:971
msgid "has these tag(s)" msgid "has these tag(s)"
msgstr "" msgstr ""
#: documents/models.py:984 #: documents/models.py:979
msgid "has this document type" msgid "has this document type"
msgstr "" msgstr ""
#: documents/models.py:992 #: documents/models.py:987
msgid "has this correspondent" msgid "has this correspondent"
msgstr "" msgstr ""
#: documents/models.py:996 #: documents/models.py:991
msgid "workflow trigger" msgid "workflow trigger"
msgstr "" msgstr ""
#: documents/models.py:997 #: documents/models.py:992
msgid "workflow triggers" msgid "workflow triggers"
msgstr "" msgstr ""
#: documents/models.py:1007 #: documents/models.py:1002
msgid "Assignment" msgid "Assignment"
msgstr "" msgstr ""
#: documents/models.py:1011 #: documents/models.py:1006
msgid "Removal" msgid "Removal"
msgstr "" msgstr ""
#: documents/models.py:1015 #: documents/models.py:1010
msgid "Workflow Action Type" msgid "Workflow Action Type"
msgstr "" msgstr ""
#: documents/models.py:1021 #: documents/models.py:1016
msgid "assign title" msgid "assign title"
msgstr "" msgstr ""
#: documents/models.py:1026 #: documents/models.py:1021
msgid "" msgid ""
"Assign a document title, can include some placeholders, see documentation." "Assign a document title, can include some placeholders, see documentation."
msgstr "" msgstr ""
#: documents/models.py:1035 paperless_mail/models.py:216 #: documents/models.py:1030 paperless_mail/models.py:216
msgid "assign this tag" msgid "assign this tag"
msgstr "" msgstr ""
#: documents/models.py:1044 paperless_mail/models.py:224 #: documents/models.py:1039 paperless_mail/models.py:224
msgid "assign this document type" msgid "assign this document type"
msgstr "" msgstr ""
#: documents/models.py:1053 paperless_mail/models.py:238 #: documents/models.py:1048 paperless_mail/models.py:238
msgid "assign this correspondent" msgid "assign this correspondent"
msgstr "" msgstr ""
#: documents/models.py:1062 #: documents/models.py:1057
msgid "assign this storage path" msgid "assign this storage path"
msgstr "" msgstr ""
#: documents/models.py:1071 #: documents/models.py:1066
msgid "assign this owner" msgid "assign this owner"
msgstr "" msgstr ""
#: documents/models.py:1078 #: documents/models.py:1073
msgid "grant view permissions to these users" msgid "grant view permissions to these users"
msgstr "" msgstr ""
#: documents/models.py:1085 #: documents/models.py:1080
msgid "grant view permissions to these groups" msgid "grant view permissions to these groups"
msgstr "" msgstr ""
#: documents/models.py:1092 #: documents/models.py:1087
msgid "grant change permissions to these users" msgid "grant change permissions to these users"
msgstr "" msgstr ""
#: documents/models.py:1099 #: documents/models.py:1094
msgid "grant change permissions to these groups" msgid "grant change permissions to these groups"
msgstr "" msgstr ""
#: documents/models.py:1106 #: documents/models.py:1101
msgid "assign these custom fields" msgid "assign these custom fields"
msgstr "" msgstr ""
#: documents/models.py:1113 #: documents/models.py:1108
msgid "remove these tag(s)" msgid "remove these tag(s)"
msgstr "" msgstr ""
#: documents/models.py:1118 #: documents/models.py:1113
msgid "remove all tags" msgid "remove all tags"
msgstr "" msgstr ""
#: documents/models.py:1125 #: documents/models.py:1120
msgid "remove these document type(s)" msgid "remove these document type(s)"
msgstr "" msgstr ""
#: documents/models.py:1130 #: documents/models.py:1125
msgid "remove all document types" msgid "remove all document types"
msgstr "" msgstr ""
#: documents/models.py:1137 #: documents/models.py:1132
msgid "remove these correspondent(s)" msgid "remove these correspondent(s)"
msgstr "" msgstr ""
#: documents/models.py:1142 #: documents/models.py:1137
msgid "remove all correspondents" msgid "remove all correspondents"
msgstr "" msgstr ""
#: documents/models.py:1149 #: documents/models.py:1144
msgid "remove these storage path(s)" msgid "remove these storage path(s)"
msgstr "" msgstr ""
#: documents/models.py:1154 #: documents/models.py:1149
msgid "remove all storage paths" msgid "remove all storage paths"
msgstr "" msgstr ""
#: documents/models.py:1161 #: documents/models.py:1156
msgid "remove these owner(s)" msgid "remove these owner(s)"
msgstr "" msgstr ""
#: documents/models.py:1166 #: documents/models.py:1161
msgid "remove all owners" msgid "remove all owners"
msgstr "" msgstr ""
#: documents/models.py:1173 #: documents/models.py:1168
msgid "remove view permissions for these users" msgid "remove view permissions for these users"
msgstr "" msgstr ""
#: documents/models.py:1180 #: documents/models.py:1175
msgid "remove view permissions for these groups" msgid "remove view permissions for these groups"
msgstr "" msgstr ""
#: documents/models.py:1187 #: documents/models.py:1182
msgid "remove change permissions for these users" msgid "remove change permissions for these users"
msgstr "" msgstr ""
#: documents/models.py:1194 #: documents/models.py:1189
msgid "remove change permissions for these groups" msgid "remove change permissions for these groups"
msgstr "" msgstr ""
#: documents/models.py:1199 #: documents/models.py:1194
msgid "remove all permissions" msgid "remove all permissions"
msgstr "" msgstr ""
#: documents/models.py:1206 #: documents/models.py:1201
msgid "remove these custom fields" msgid "remove these custom fields"
msgstr "" msgstr ""
#: documents/models.py:1211 #: documents/models.py:1206
msgid "remove all custom fields" msgid "remove all custom fields"
msgstr "" msgstr ""
#: documents/models.py:1215 #: documents/models.py:1210
msgid "workflow action" msgid "workflow action"
msgstr "" msgstr ""
#: documents/models.py:1216 #: documents/models.py:1211
msgid "workflow actions" msgid "workflow actions"
msgstr "" msgstr ""
#: documents/models.py:1225 paperless_mail/models.py:95 #: documents/models.py:1220 paperless_mail/models.py:95
msgid "order" msgid "order"
msgstr "" msgstr ""
#: documents/models.py:1231 #: documents/models.py:1226
msgid "triggers" msgid "triggers"
msgstr "" msgstr ""
#: documents/models.py:1238 #: documents/models.py:1233
msgid "actions" msgid "actions"
msgstr "" msgstr ""
#: documents/models.py:1241 #: documents/models.py:1236
msgid "enabled" msgid "enabled"
msgstr "" msgstr ""
#: documents/serialisers.py:115 #: documents/serialisers.py:114
#, python-format #, python-format
msgid "Invalid regular expression: %(error)s" msgid "Invalid regular expression: %(error)s"
msgstr "" msgstr ""
#: documents/serialisers.py:418 #: documents/serialisers.py:417
msgid "Invalid color." msgid "Invalid color."
msgstr "" msgstr ""
#: documents/serialisers.py:1148 #: documents/serialisers.py:1171
#, python-format #, python-format
msgid "File type %(type)s not supported" msgid "File type %(type)s not supported"
msgstr "" msgstr ""
#: documents/serialisers.py:1257 #: documents/serialisers.py:1280
msgid "Invalid variable detected." msgid "Invalid variable detected."
msgstr "" msgstr ""
@ -1350,7 +1350,7 @@ msgstr ""
msgid "Chinese Simplified" msgid "Chinese Simplified"
msgstr "" msgstr ""
#: paperless/urls.py:230 #: paperless/urls.py:236
msgid "Paperless-ngx administration" msgid "Paperless-ngx administration"
msgstr "" msgstr ""