mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-02-22 00:49:35 -06:00
Compare commits
4 Commits
feature-82
...
tweak-rese
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f474014dbe | ||
|
|
57c5939d7b | ||
|
|
43fe932c57 | ||
|
|
83f68d6063 |
@@ -344,9 +344,6 @@ src/documents/migrations/0001_initial.py:0: error: Skipping analyzing "multisele
|
|||||||
src/documents/migrations/0001_initial.py:0: error: Skipping analyzing "multiselectfield.db.fields": module is installed, but missing library stubs or py.typed marker [import-untyped]
|
src/documents/migrations/0001_initial.py:0: error: Skipping analyzing "multiselectfield.db.fields": module is installed, but missing library stubs or py.typed marker [import-untyped]
|
||||||
src/documents/migrations/0008_sharelinkbundle.py:0: error: Function is missing a type annotation [no-untyped-def]
|
src/documents/migrations/0008_sharelinkbundle.py:0: error: Function is missing a type annotation [no-untyped-def]
|
||||||
src/documents/migrations/0008_sharelinkbundle.py:0: error: Function is missing a type annotation [no-untyped-def]
|
src/documents/migrations/0008_sharelinkbundle.py:0: error: Function is missing a type annotation [no-untyped-def]
|
||||||
src/documents/migrations/0012_savedview_visibility_to_ui_settings.py:0: error: Function is missing a type annotation [no-untyped-def]
|
|
||||||
src/documents/migrations/0012_savedview_visibility_to_ui_settings.py:0: error: Function is missing a type annotation [no-untyped-def]
|
|
||||||
src/documents/migrations/0012_savedview_visibility_to_ui_settings.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
|
||||||
src/documents/models.py:0: error: Argument 1 to "Path" has incompatible type "Path | None"; expected "str | PathLike[str]" [arg-type]
|
src/documents/models.py:0: error: Argument 1 to "Path" has incompatible type "Path | None"; expected "str | PathLike[str]" [arg-type]
|
||||||
src/documents/models.py:0: error: Could not resolve manager type for "documents.models.Document.deleted_objects" [django-manager-missing]
|
src/documents/models.py:0: error: Could not resolve manager type for "documents.models.Document.deleted_objects" [django-manager-missing]
|
||||||
src/documents/models.py:0: error: Could not resolve manager type for "documents.models.Document.global_objects" [django-manager-missing]
|
src/documents/models.py:0: error: Could not resolve manager type for "documents.models.Document.global_objects" [django-manager-missing]
|
||||||
@@ -1196,14 +1193,6 @@ src/documents/tests/test_management_exporter.py:0: error: Skipping analyzing "al
|
|||||||
src/documents/tests/test_management_fuzzy.py:0: error: Function is missing a type annotation [no-untyped-def]
|
src/documents/tests/test_management_fuzzy.py:0: error: Function is missing a type annotation [no-untyped-def]
|
||||||
src/documents/tests/test_management_retagger.py:0: error: Function is missing a return type annotation [no-untyped-def]
|
src/documents/tests/test_management_retagger.py:0: error: Function is missing a return type annotation [no-untyped-def]
|
||||||
src/documents/tests/test_management_superuser.py:0: error: Function is missing a type annotation [no-untyped-def]
|
src/documents/tests/test_management_superuser.py:0: error: Function is missing a type annotation [no-untyped-def]
|
||||||
src/documents/tests/test_migration_saved_view_visibility.py:0: error: Cannot determine type of "apps" [has-type]
|
|
||||||
src/documents/tests/test_migration_saved_view_visibility.py:0: error: Cannot determine type of "apps" [has-type]
|
|
||||||
src/documents/tests/test_migration_saved_view_visibility.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
|
||||||
src/documents/tests/test_migration_saved_view_visibility.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
|
||||||
src/documents/tests/test_migration_saved_view_visibility.py:0: error: Incompatible types in assignment (expression has type "str", base class "TestMigrations" defined the type as "None") [assignment]
|
|
||||||
src/documents/tests/test_migration_saved_view_visibility.py:0: error: Incompatible types in assignment (expression has type "str", base class "TestMigrations" defined the type as "None") [assignment]
|
|
||||||
src/documents/tests/test_migration_saved_view_visibility.py:0: error: Incompatible types in assignment (expression has type "str", base class "TestMigrations" defined the type as "None") [assignment]
|
|
||||||
src/documents/tests/test_migration_saved_view_visibility.py:0: error: Incompatible types in assignment (expression has type "str", base class "TestMigrations" defined the type as "None") [assignment]
|
|
||||||
src/documents/tests/test_migration_share_link_bundle.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
src/documents/tests/test_migration_share_link_bundle.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
||||||
src/documents/tests/test_migration_share_link_bundle.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
src/documents/tests/test_migration_share_link_bundle.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
||||||
src/documents/tests/test_migration_share_link_bundle.py:0: error: Incompatible types in assignment (expression has type "str", base class "TestMigrations" defined the type as "None") [assignment]
|
src/documents/tests/test_migration_share_link_bundle.py:0: error: Incompatible types in assignment (expression has type "str", base class "TestMigrations" defined the type as "None") [assignment]
|
||||||
@@ -1580,6 +1569,7 @@ src/documents/views.py:0: error: Function is missing a return type annotation [
|
|||||||
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def]
|
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def]
|
||||||
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def]
|
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def]
|
||||||
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def]
|
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def]
|
||||||
|
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def]
|
||||||
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def]
|
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def]
|
||||||
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def]
|
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def]
|
||||||
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def]
|
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def]
|
||||||
@@ -1635,6 +1625,8 @@ src/documents/views.py:0: error: Function is missing a type annotation [no-unty
|
|||||||
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def]
|
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def]
|
||||||
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def]
|
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def]
|
||||||
src/documents/views.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
src/documents/views.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
||||||
|
src/documents/views.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
||||||
|
src/documents/views.py:0: error: Incompatible type for lookup 'owner': (got "User | AnonymousUser", expected "User | int | None") [misc]
|
||||||
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "Any | None", variable has type "dict[Any, Any]") [assignment]
|
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "Any | None", variable has type "dict[Any, Any]") [assignment]
|
||||||
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "QuerySet[Any, Any]", variable has type "list[Any]") [assignment]
|
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "QuerySet[Any, Any]", variable has type "list[Any]") [assignment]
|
||||||
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "QuerySet[Any, Any]", variable has type "list[Document]") [assignment]
|
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "QuerySet[Any, Any]", variable has type "list[Document]") [assignment]
|
||||||
@@ -1700,11 +1692,11 @@ src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable
|
|||||||
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
|
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
|
||||||
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
|
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
|
||||||
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
|
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
|
||||||
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
|
|
||||||
src/documents/views.py:0: error: Value of type "Iterable[CustomField]" is not indexable [index]
|
src/documents/views.py:0: error: Value of type "Iterable[CustomField]" is not indexable [index]
|
||||||
src/documents/views.py:0: error: Value of type "Iterable[Group]" is not indexable [index]
|
src/documents/views.py:0: error: Value of type "Iterable[Group]" is not indexable [index]
|
||||||
src/documents/views.py:0: error: Value of type "Iterable[MailAccount]" is not indexable [index]
|
src/documents/views.py:0: error: Value of type "Iterable[MailAccount]" is not indexable [index]
|
||||||
src/documents/views.py:0: error: Value of type "Iterable[MailRule]" is not indexable [index]
|
src/documents/views.py:0: error: Value of type "Iterable[MailRule]" is not indexable [index]
|
||||||
|
src/documents/views.py:0: error: Value of type "Iterable[SavedView]" is not indexable [index]
|
||||||
src/documents/views.py:0: error: Value of type "Iterable[User]" is not indexable [index]
|
src/documents/views.py:0: error: Value of type "Iterable[User]" is not indexable [index]
|
||||||
src/documents/views.py:0: error: Value of type "Iterable[Workflow]" is not indexable [index]
|
src/documents/views.py:0: error: Value of type "Iterable[Workflow]" is not indexable [index]
|
||||||
src/documents/views.py:0: error: Value of type "dict[str, _PingReply] | None" is not indexable [index]
|
src/documents/views.py:0: error: Value of type "dict[str, _PingReply] | None" is not indexable [index]
|
||||||
|
|||||||
@@ -451,8 +451,3 @@ Initial API version.
|
|||||||
- The document `created` field is now a date, not a datetime. The
|
- The document `created` field is now a date, not a datetime. The
|
||||||
`created_date` field is considered deprecated and will be removed in a
|
`created_date` field is considered deprecated and will be removed in a
|
||||||
future version.
|
future version.
|
||||||
|
|
||||||
#### Version 10
|
|
||||||
|
|
||||||
- The `show_on_dashboard` and `show_in_sidebar` fields of saved views have been
|
|
||||||
removed. Relevant settings are now stored in the UISettings model.
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"size": -1,
|
"size": -1,
|
||||||
"mimeType": "application/json",
|
"mimeType": "application/json",
|
||||||
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true,\"dashboard_views_visible_ids\":[7,4],\"sidebar_views_visible_ids\":[7,4,11]},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
||||||
},
|
},
|
||||||
"headersSize": -1,
|
"headersSize": -1,
|
||||||
"bodySize": -1,
|
"bodySize": -1,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"size": -1,
|
"size": -1,
|
||||||
"mimeType": "application/json",
|
"mimeType": "application/json",
|
||||||
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true,\"dashboard_views_visible_ids\":[7,4],\"sidebar_views_visible_ids\":[7,4,11]},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
||||||
},
|
},
|
||||||
"headersSize": -1,
|
"headersSize": -1,
|
||||||
"bodySize": -1,
|
"bodySize": -1,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"size": -1,
|
"size": -1,
|
||||||
"mimeType": "application/json",
|
"mimeType": "application/json",
|
||||||
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true,\"dashboard_views_visible_ids\":[7,4],\"sidebar_views_visible_ids\":[7,4,11]},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
||||||
},
|
},
|
||||||
"headersSize": -1,
|
"headersSize": -1,
|
||||||
"bodySize": -1,
|
"bodySize": -1,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"size": -1,
|
"size": -1,
|
||||||
"mimeType": "application/json",
|
"mimeType": "application/json",
|
||||||
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true,\"dashboard_views_visible_ids\":[7,4],\"sidebar_views_visible_ids\":[7,4,11]},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
||||||
},
|
},
|
||||||
"headersSize": -1,
|
"headersSize": -1,
|
||||||
"bodySize": -1,
|
"bodySize": -1,
|
||||||
|
|||||||
@@ -1781,11 +1781,15 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
|
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
|
||||||
<context context-type="linenumber">216</context>
|
<context context-type="linenumber">156</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
|
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
|
||||||
<context context-type="linenumber">241</context>
|
<context context-type="linenumber">230</context>
|
||||||
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
|
||||||
|
<context context-type="linenumber">255</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2991443309752293110" datatype="html">
|
<trans-unit id="2991443309752293110" datatype="html">
|
||||||
@@ -3113,21 +3117,21 @@
|
|||||||
<source>Sidebar views updated</source>
|
<source>Sidebar views updated</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
|
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
|
||||||
<context context-type="linenumber">329</context>
|
<context context-type="linenumber">343</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3547923076537026828" datatype="html">
|
<trans-unit id="3547923076537026828" datatype="html">
|
||||||
<source>Error updating sidebar views</source>
|
<source>Error updating sidebar views</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
|
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
|
||||||
<context context-type="linenumber">332</context>
|
<context context-type="linenumber">346</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2526035785704676448" datatype="html">
|
<trans-unit id="2526035785704676448" datatype="html">
|
||||||
<source>An error occurred while saving update checking settings.</source>
|
<source>An error occurred while saving update checking settings.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
|
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
|
||||||
<context context-type="linenumber">353</context>
|
<context context-type="linenumber">367</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4580988005648117665" datatype="html">
|
<trans-unit id="4580988005648117665" datatype="html">
|
||||||
|
|||||||
@@ -99,7 +99,12 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="nav-group mt-3 mb-1" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
|
<div class="nav-group mt-3 mb-1" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
|
||||||
@if (savedViewService.sidebarViews?.length > 0) {
|
@if (savedViewService.loading) {
|
||||||
|
<h6 class="sidebar-heading px-3 text-muted">
|
||||||
|
<span i18n>Saved views</span>
|
||||||
|
<div class="spinner-border spinner-border-sm fw-normal ms-2" role="status"></div>
|
||||||
|
</h6>
|
||||||
|
} @else if (savedViewService.sidebarViews?.length > 0) {
|
||||||
<h6 class="sidebar-heading px-3 text-muted">
|
<h6 class="sidebar-heading px-3 text-muted">
|
||||||
<span i18n>Saved views</span>
|
<span i18n>Saved views</span>
|
||||||
</h6>
|
</h6>
|
||||||
@@ -129,11 +134,6 @@
|
|||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
} @else if (savedViewService.loading) {
|
|
||||||
<h6 class="sidebar-heading px-3 text-muted">
|
|
||||||
<span i18n>Saved views</span>
|
|
||||||
<div class="spinner-border spinner-border-sm fw-normal ms-2" role="status"></div>
|
|
||||||
</h6>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -243,9 +243,19 @@ describe('AppFrameComponent', () => {
|
|||||||
|
|
||||||
it('should support toggling slim sidebar and saving', fakeAsync(() => {
|
it('should support toggling slim sidebar and saving', fakeAsync(() => {
|
||||||
const saveSettingSpy = jest.spyOn(settingsService, 'set')
|
const saveSettingSpy = jest.spyOn(settingsService, 'set')
|
||||||
|
settingsService.set(SETTINGS_KEYS.ATTRIBUTES_SECTIONS_COLLAPSED, [])
|
||||||
expect(component.slimSidebarEnabled).toBeFalsy()
|
expect(component.slimSidebarEnabled).toBeFalsy()
|
||||||
expect(component.slimSidebarAnimating).toBeFalsy()
|
expect(component.slimSidebarAnimating).toBeFalsy()
|
||||||
component.toggleSlimSidebar()
|
component.toggleSlimSidebar()
|
||||||
|
const requests = httpTestingController.match(
|
||||||
|
`${environment.apiBaseUrl}ui_settings/`
|
||||||
|
)
|
||||||
|
expect(requests).toHaveLength(1)
|
||||||
|
expect(requests[0].request.body.settings.slim_sidebar).toBe(true)
|
||||||
|
expect(
|
||||||
|
requests[0].request.body.settings.attributes_sections_collapsed
|
||||||
|
).toEqual(['attributes'])
|
||||||
|
requests[0].flush({ success: true })
|
||||||
expect(component.slimSidebarAnimating).toBeTruthy()
|
expect(component.slimSidebarAnimating).toBeTruthy()
|
||||||
tick(200)
|
tick(200)
|
||||||
expect(component.slimSidebarAnimating).toBeFalsy()
|
expect(component.slimSidebarAnimating).toBeFalsy()
|
||||||
@@ -254,6 +264,10 @@ describe('AppFrameComponent', () => {
|
|||||||
SETTINGS_KEYS.SLIM_SIDEBAR,
|
SETTINGS_KEYS.SLIM_SIDEBAR,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
expect(saveSettingSpy).toHaveBeenCalledWith(
|
||||||
|
SETTINGS_KEYS.ATTRIBUTES_SECTIONS_COLLAPSED,
|
||||||
|
['attributes']
|
||||||
|
)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
it('should show error on toggle slim sidebar if store settings fails', () => {
|
it('should show error on toggle slim sidebar if store settings fails', () => {
|
||||||
|
|||||||
@@ -140,10 +140,24 @@ export class AppFrameComponent
|
|||||||
|
|
||||||
toggleSlimSidebar(): void {
|
toggleSlimSidebar(): void {
|
||||||
this.slimSidebarAnimating = true
|
this.slimSidebarAnimating = true
|
||||||
this.slimSidebarEnabled = !this.slimSidebarEnabled
|
const slimSidebarEnabled = !this.slimSidebarEnabled
|
||||||
if (this.slimSidebarEnabled) {
|
this.settingsService.set(SETTINGS_KEYS.SLIM_SIDEBAR, slimSidebarEnabled)
|
||||||
this.attributesSectionsCollapsed = true
|
if (slimSidebarEnabled) {
|
||||||
|
this.settingsService.set(SETTINGS_KEYS.ATTRIBUTES_SECTIONS_COLLAPSED, [
|
||||||
|
CollapsibleSection.ATTRIBUTES,
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
this.settingsService
|
||||||
|
.storeSettings()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe({
|
||||||
|
error: (error) => {
|
||||||
|
this.toastService.showError(
|
||||||
|
$localize`An error occurred while saving settings.`
|
||||||
|
)
|
||||||
|
console.warn(error)
|
||||||
|
},
|
||||||
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.slimSidebarAnimating = false
|
this.slimSidebarAnimating = false
|
||||||
}, 200) // slightly longer than css animation for slim sidebar
|
}, 200) // slightly longer than css animation for slim sidebar
|
||||||
|
|||||||
@@ -104,9 +104,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (list.activeSavedViewId && activeSavedViewCanChange) {
|
<div *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.SavedView }">
|
||||||
<button ngbDropdownItem (click)="saveViewConfig()" [disabled]="!savedViewIsModified" i18n>Save "{{list.activeSavedViewTitle}}"</button>
|
@if (list.activeSavedViewId) {
|
||||||
}
|
<button ngbDropdownItem (click)="saveViewConfig()" [disabled]="!savedViewIsModified" i18n>Save "{{list.activeSavedViewTitle}}"</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<button ngbDropdownItem (click)="saveViewConfigAs()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.SavedView }" i18n>Save as...</button>
|
<button ngbDropdownItem (click)="saveViewConfigAs()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.SavedView }" i18n>Save as...</button>
|
||||||
<a ngbDropdownItem routerLink="/savedviews" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }" i18n>All saved views</a>
|
<a ngbDropdownItem routerLink="/savedviews" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }" i18n>All saved views</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -452,7 +452,7 @@ describe('DocumentListComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle error on view saving', () => {
|
it('should handle error on view saving', () => {
|
||||||
const view: SavedView = {
|
component.list.activateSavedView({
|
||||||
id: 10,
|
id: 10,
|
||||||
name: 'Saved View 10',
|
name: 'Saved View 10',
|
||||||
sort_field: 'added',
|
sort_field: 'added',
|
||||||
@@ -463,16 +463,7 @@ describe('DocumentListComponent', () => {
|
|||||||
value: '20',
|
value: '20',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
})
|
||||||
jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
|
|
||||||
const queryParams = { view: view.id.toString() }
|
|
||||||
jest
|
|
||||||
.spyOn(activatedRoute, 'queryParamMap', 'get')
|
|
||||||
.mockReturnValue(of(convertToParamMap(queryParams)))
|
|
||||||
activatedRoute.snapshot.queryParams = queryParams
|
|
||||||
router.routerState.snapshot.url = '/view/10/'
|
|
||||||
fixture.detectChanges()
|
|
||||||
|
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
jest
|
jest
|
||||||
.spyOn(savedViewService, 'patch')
|
.spyOn(savedViewService, 'patch')
|
||||||
@@ -484,40 +475,6 @@ describe('DocumentListComponent', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not save a view without object change permissions', () => {
|
|
||||||
const view: SavedView = {
|
|
||||||
id: 10,
|
|
||||||
name: 'Saved View 10',
|
|
||||||
sort_field: 'added',
|
|
||||||
sort_reverse: true,
|
|
||||||
filter_rules: [
|
|
||||||
{
|
|
||||||
rule_type: FILTER_HAS_TAGS_ANY,
|
|
||||||
value: '20',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
owner: 999,
|
|
||||||
user_can_change: false,
|
|
||||||
}
|
|
||||||
jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
|
|
||||||
jest
|
|
||||||
.spyOn(permissionService, 'currentUserHasObjectPermissions')
|
|
||||||
.mockReturnValue(false)
|
|
||||||
const queryParams = { view: view.id.toString() }
|
|
||||||
jest
|
|
||||||
.spyOn(activatedRoute, 'queryParamMap', 'get')
|
|
||||||
.mockReturnValue(of(convertToParamMap(queryParams)))
|
|
||||||
activatedRoute.snapshot.queryParams = queryParams
|
|
||||||
router.routerState.snapshot.url = '/view/10/'
|
|
||||||
fixture.detectChanges()
|
|
||||||
|
|
||||||
const patchSpy = jest.spyOn(savedViewService, 'patch')
|
|
||||||
|
|
||||||
component.saveViewConfig()
|
|
||||||
|
|
||||||
expect(patchSpy).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support edited view saving as', () => {
|
it('should support edited view saving as', () => {
|
||||||
const view: SavedView = {
|
const view: SavedView = {
|
||||||
id: 10,
|
id: 10,
|
||||||
@@ -549,44 +506,16 @@ describe('DocumentListComponent', () => {
|
|||||||
const modalSpy = jest.spyOn(modalService, 'open')
|
const modalSpy = jest.spyOn(modalService, 'open')
|
||||||
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
||||||
const savedViewServiceCreate = jest.spyOn(savedViewService, 'create')
|
const savedViewServiceCreate = jest.spyOn(savedViewService, 'create')
|
||||||
const updateVisibilitySpy = jest
|
|
||||||
.spyOn(settingsService, 'updateSavedViewsVisibility')
|
|
||||||
.mockReturnValue(of({ success: true }))
|
|
||||||
savedViewServiceCreate.mockReturnValueOnce(of(modifiedView))
|
savedViewServiceCreate.mockReturnValueOnce(of(modifiedView))
|
||||||
component.saveViewConfigAs()
|
component.saveViewConfigAs()
|
||||||
|
|
||||||
const modalCloseSpy = jest.spyOn(openModal, 'close')
|
const modalCloseSpy = jest.spyOn(openModal, 'close')
|
||||||
const permissions = {
|
|
||||||
owner: 5,
|
|
||||||
set_permissions: {
|
|
||||||
view: {
|
|
||||||
users: [4],
|
|
||||||
groups: [3],
|
|
||||||
},
|
|
||||||
change: {
|
|
||||||
users: [2],
|
|
||||||
groups: [1],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
openModal.componentInstance.saveClicked.next({
|
openModal.componentInstance.saveClicked.next({
|
||||||
name: 'Foo Bar',
|
name: 'Foo Bar',
|
||||||
showOnDashboard: true,
|
show_on_dashboard: true,
|
||||||
showInSideBar: true,
|
show_in_sidebar: true,
|
||||||
permissions_form: permissions,
|
|
||||||
})
|
})
|
||||||
expect(savedViewServiceCreate).toHaveBeenCalled()
|
expect(savedViewServiceCreate).toHaveBeenCalled()
|
||||||
expect(savedViewServiceCreate).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
name: 'Foo Bar',
|
|
||||||
owner: permissions.owner,
|
|
||||||
set_permissions: permissions.set_permissions,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
expect(updateVisibilitySpy).toHaveBeenCalledWith(
|
|
||||||
expect.arrayContaining([modifiedView.id]),
|
|
||||||
expect.arrayContaining([modifiedView.id])
|
|
||||||
)
|
|
||||||
expect(modalSpy).toHaveBeenCalled()
|
expect(modalSpy).toHaveBeenCalled()
|
||||||
expect(toastSpy).toHaveBeenCalled()
|
expect(toastSpy).toHaveBeenCalled()
|
||||||
expect(modalCloseSpy).toHaveBeenCalled()
|
expect(modalCloseSpy).toHaveBeenCalled()
|
||||||
@@ -620,10 +549,6 @@ describe('DocumentListComponent', () => {
|
|||||||
|
|
||||||
let openModal: NgbModalRef
|
let openModal: NgbModalRef
|
||||||
modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
|
modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
|
||||||
const updateVisibilitySpy = jest.spyOn(
|
|
||||||
settingsService,
|
|
||||||
'updateSavedViewsVisibility'
|
|
||||||
)
|
|
||||||
jest.spyOn(savedViewService, 'create').mockReturnValueOnce(
|
jest.spyOn(savedViewService, 'create').mockReturnValueOnce(
|
||||||
throwError(
|
throwError(
|
||||||
() =>
|
() =>
|
||||||
@@ -636,10 +561,9 @@ describe('DocumentListComponent', () => {
|
|||||||
|
|
||||||
openModal.componentInstance.saveClicked.next({
|
openModal.componentInstance.saveClicked.next({
|
||||||
name: 'Foo Bar',
|
name: 'Foo Bar',
|
||||||
showOnDashboard: true,
|
show_on_dashboard: true,
|
||||||
showInSideBar: true,
|
show_in_sidebar: true,
|
||||||
})
|
})
|
||||||
expect(updateVisibilitySpy).not.toHaveBeenCalled()
|
|
||||||
expect(openModal.componentInstance.error).toEqual({ filter_rules: ['11'] })
|
expect(openModal.componentInstance.error).toEqual({ filter_rules: ['11'] })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
} from '@ng-bootstrap/ng-bootstrap'
|
} from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { TourNgBootstrap } from 'ngx-ui-tour-ng-bootstrap'
|
import { TourNgBootstrap } from 'ngx-ui-tour-ng-bootstrap'
|
||||||
import { filter, first, map, of, Subject, switchMap, takeUntil } from 'rxjs'
|
import { filter, first, map, Subject, switchMap, takeUntil } from 'rxjs'
|
||||||
import {
|
import {
|
||||||
DEFAULT_DISPLAY_FIELDS,
|
DEFAULT_DISPLAY_FIELDS,
|
||||||
DisplayField,
|
DisplayField,
|
||||||
@@ -47,10 +47,7 @@ import { UsernamePipe } from 'src/app/pipes/username.pipe'
|
|||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
import { HotKeyService } from 'src/app/services/hot-key.service'
|
import { HotKeyService } from 'src/app/services/hot-key.service'
|
||||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
||||||
import {
|
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||||
PermissionAction,
|
|
||||||
PermissionsService,
|
|
||||||
} from 'src/app/services/permissions.service'
|
|
||||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
@@ -151,18 +148,12 @@ export class DocumentListComponent
|
|||||||
|
|
||||||
unmodifiedFilterRules: FilterRule[] = []
|
unmodifiedFilterRules: FilterRule[] = []
|
||||||
private unmodifiedSavedView: SavedView
|
private unmodifiedSavedView: SavedView
|
||||||
private activeSavedView: SavedView | null = null
|
|
||||||
|
|
||||||
private unsubscribeNotifier: Subject<any> = new Subject()
|
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
|
||||||
get savedViewIsModified(): boolean {
|
get savedViewIsModified(): boolean {
|
||||||
if (
|
if (!this.list.activeSavedViewId || !this.unmodifiedSavedView) return false
|
||||||
!this.list.activeSavedViewId ||
|
else {
|
||||||
!this.unmodifiedSavedView ||
|
|
||||||
!this.activeSavedViewCanChange
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return (
|
return (
|
||||||
this.unmodifiedSavedView.sort_field !== this.list.sortField ||
|
this.unmodifiedSavedView.sort_field !== this.list.sortField ||
|
||||||
this.unmodifiedSavedView.sort_reverse !== this.list.sortReverse ||
|
this.unmodifiedSavedView.sort_reverse !== this.list.sortReverse ||
|
||||||
@@ -189,16 +180,6 @@ export class DocumentListComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get activeSavedViewCanChange(): boolean {
|
|
||||||
if (!this.activeSavedView) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return this.permissionService.currentUserHasObjectPermissions(
|
|
||||||
PermissionAction.Change,
|
|
||||||
this.activeSavedView
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
get isFiltered() {
|
get isFiltered() {
|
||||||
return !!this.filterEditor?.rulesModified
|
return !!this.filterEditor?.rulesModified
|
||||||
}
|
}
|
||||||
@@ -275,13 +256,11 @@ export class DocumentListComponent
|
|||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe(({ view }) => {
|
.subscribe(({ view }) => {
|
||||||
if (!view) {
|
if (!view) {
|
||||||
this.activeSavedView = null
|
|
||||||
this.router.navigate(['404'], {
|
this.router.navigate(['404'], {
|
||||||
replaceUrl: true,
|
replaceUrl: true,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.activeSavedView = view
|
|
||||||
this.unmodifiedSavedView = view
|
this.unmodifiedSavedView = view
|
||||||
this.list.activateSavedViewWithQueryParams(
|
this.list.activateSavedViewWithQueryParams(
|
||||||
view,
|
view,
|
||||||
@@ -305,7 +284,6 @@ export class DocumentListComponent
|
|||||||
// loading a saved view on /documents
|
// loading a saved view on /documents
|
||||||
this.loadViewConfig(parseInt(queryParams.get('view')))
|
this.loadViewConfig(parseInt(queryParams.get('view')))
|
||||||
} else {
|
} else {
|
||||||
this.activeSavedView = null
|
|
||||||
this.list.activateSavedView(null)
|
this.list.activateSavedView(null)
|
||||||
this.list.loadFromQueryParams(queryParams)
|
this.list.loadFromQueryParams(queryParams)
|
||||||
this.unmodifiedFilterRules = []
|
this.unmodifiedFilterRules = []
|
||||||
@@ -388,7 +366,7 @@ export class DocumentListComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveViewConfig() {
|
saveViewConfig() {
|
||||||
if (this.list.activeSavedViewId != null && this.activeSavedViewCanChange) {
|
if (this.list.activeSavedViewId != null) {
|
||||||
let savedView: SavedView = {
|
let savedView: SavedView = {
|
||||||
id: this.list.activeSavedViewId,
|
id: this.list.activeSavedViewId,
|
||||||
filter_rules: this.list.filterRules,
|
filter_rules: this.list.filterRules,
|
||||||
@@ -402,7 +380,6 @@ export class DocumentListComponent
|
|||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (view) => {
|
next: (view) => {
|
||||||
this.activeSavedView = view
|
|
||||||
this.unmodifiedSavedView = view
|
this.unmodifiedSavedView = view
|
||||||
this.toastService.showInfo(
|
this.toastService.showInfo(
|
||||||
$localize`View "${this.list.activeSavedViewTitle}" saved successfully.`
|
$localize`View "${this.list.activeSavedViewTitle}" saved successfully.`
|
||||||
@@ -424,11 +401,6 @@ export class DocumentListComponent
|
|||||||
.getCached(viewID)
|
.getCached(viewID)
|
||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe((view) => {
|
.subscribe((view) => {
|
||||||
if (!view) {
|
|
||||||
this.activeSavedView = null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.activeSavedView = view
|
|
||||||
this.unmodifiedSavedView = view
|
this.unmodifiedSavedView = view
|
||||||
this.list.activateSavedView(view)
|
this.list.activateSavedView(view)
|
||||||
this.list.reload(() => {
|
this.list.reload(() => {
|
||||||
@@ -446,48 +418,24 @@ export class DocumentListComponent
|
|||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled = false
|
||||||
let savedView: SavedView = {
|
let savedView: SavedView = {
|
||||||
name: formValue.name,
|
name: formValue.name,
|
||||||
|
show_on_dashboard: formValue.showOnDashboard,
|
||||||
|
show_in_sidebar: formValue.showInSideBar,
|
||||||
filter_rules: this.list.filterRules,
|
filter_rules: this.list.filterRules,
|
||||||
sort_reverse: this.list.sortReverse,
|
sort_reverse: this.list.sortReverse,
|
||||||
sort_field: this.list.sortField,
|
sort_field: this.list.sortField,
|
||||||
display_mode: this.list.displayMode,
|
display_mode: this.list.displayMode,
|
||||||
display_fields: this.activeDisplayFields,
|
display_fields: this.activeDisplayFields,
|
||||||
}
|
}
|
||||||
const permissions = formValue.permissions_form
|
|
||||||
if (permissions) {
|
|
||||||
if (permissions.owner !== null && permissions.owner !== undefined) {
|
|
||||||
savedView.owner = permissions.owner
|
|
||||||
}
|
|
||||||
if (permissions.set_permissions) {
|
|
||||||
savedView['set_permissions'] = permissions.set_permissions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.savedViewService
|
this.savedViewService
|
||||||
.create(savedView)
|
.create(savedView)
|
||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (createdView) => {
|
next: () => {
|
||||||
this.saveCreatedViewVisibility(
|
modal.close()
|
||||||
createdView,
|
this.toastService.showInfo(
|
||||||
formValue.showOnDashboard,
|
$localize`View "${savedView.name}" created successfully.`
|
||||||
formValue.showInSideBar
|
|
||||||
)
|
)
|
||||||
.pipe(first())
|
|
||||||
.subscribe({
|
|
||||||
next: () => {
|
|
||||||
modal.close()
|
|
||||||
this.toastService.showInfo(
|
|
||||||
$localize`View "${savedView.name}" created successfully.`
|
|
||||||
)
|
|
||||||
},
|
|
||||||
error: (error) => {
|
|
||||||
modal.close()
|
|
||||||
this.toastService.showError(
|
|
||||||
$localize`View "${savedView.name}" created successfully, but could not update visibility settings.`,
|
|
||||||
error
|
|
||||||
)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
error: (httpError) => {
|
error: (httpError) => {
|
||||||
let error = httpError.error
|
let error = httpError.error
|
||||||
@@ -501,32 +449,6 @@ export class DocumentListComponent
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveCreatedViewVisibility(
|
|
||||||
createdView: SavedView,
|
|
||||||
showOnDashboard: boolean,
|
|
||||||
showInSideBar: boolean
|
|
||||||
) {
|
|
||||||
if (!showOnDashboard && !showInSideBar) {
|
|
||||||
return of(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const dashboardViewIds = this.savedViewService.dashboardViews.map(
|
|
||||||
(v) => v.id
|
|
||||||
)
|
|
||||||
const sidebarViewIds = this.savedViewService.sidebarViews.map((v) => v.id)
|
|
||||||
if (showOnDashboard) {
|
|
||||||
dashboardViewIds.push(createdView.id)
|
|
||||||
}
|
|
||||||
if (showInSideBar) {
|
|
||||||
sidebarViewIds.push(createdView.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.settingsService.updateSavedViewsVisibility(
|
|
||||||
dashboardViewIds,
|
|
||||||
sidebarViewIds
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
openDocumentDetail(document: Document | number) {
|
openDocumentDetail(document: Document | number) {
|
||||||
this.router.navigate([
|
this.router.navigate([
|
||||||
'documents',
|
'documents',
|
||||||
@@ -571,5 +493,6 @@ export class DocumentListComponent
|
|||||||
|
|
||||||
resetFilters() {
|
resetFilters() {
|
||||||
this.filterEditor.resetSelected()
|
this.filterEditor.resetSelected()
|
||||||
|
this.list.currentPage = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name" autocomplete="off"></pngx-input-text>
|
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name" autocomplete="off"></pngx-input-text>
|
||||||
<pngx-input-check i18n-title title="Show in sidebar" formControlName="showInSideBar"></pngx-input-check>
|
<pngx-input-check i18n-title title="Show in sidebar" formControlName="showInSideBar"></pngx-input-check>
|
||||||
<pngx-input-check i18n-title title="Show on dashboard" formControlName="showOnDashboard"></pngx-input-check>
|
<pngx-input-check i18n-title title="Show on dashboard" formControlName="showOnDashboard"></pngx-input-check>
|
||||||
<pngx-permissions-form [users]="users" accordion="true" formControlName="permissions_form"></pngx-permissions-form>
|
|
||||||
@if (error?.filter_rules) {
|
@if (error?.filter_rules) {
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger" role="alert">
|
||||||
<h6 class="alert-heading" i18n>Filter rules error occurred while saving this view</h6>
|
<h6 class="alert-heading" i18n>Filter rules error occurred while saving this view</h6>
|
||||||
|
|||||||
@@ -7,13 +7,7 @@ import {
|
|||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { By } from '@angular/platform-browser'
|
import { By } from '@angular/platform-browser'
|
||||||
import { NgbActiveModal, NgbModalModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal, NgbModalModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { of } from 'rxjs'
|
|
||||||
import { GroupService } from 'src/app/services/rest/group.service'
|
|
||||||
import { UserService } from 'src/app/services/rest/user.service'
|
|
||||||
import { CheckComponent } from '../../common/input/check/check.component'
|
import { CheckComponent } from '../../common/input/check/check.component'
|
||||||
import { PermissionsFormComponent } from '../../common/input/permissions/permissions-form/permissions-form.component'
|
|
||||||
import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component'
|
|
||||||
import { PermissionsUserComponent } from '../../common/input/permissions/permissions-user/permissions-user.component'
|
|
||||||
import { TextComponent } from '../../common/input/text/text.component'
|
import { TextComponent } from '../../common/input/text/text.component'
|
||||||
import { SaveViewConfigDialogComponent } from './save-view-config-dialog.component'
|
import { SaveViewConfigDialogComponent } from './save-view-config-dialog.component'
|
||||||
|
|
||||||
@@ -24,21 +18,7 @@ describe('SaveViewConfigDialogComponent', () => {
|
|||||||
|
|
||||||
beforeEach(fakeAsync(() => {
|
beforeEach(fakeAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [NgbActiveModal],
|
||||||
NgbActiveModal,
|
|
||||||
{
|
|
||||||
provide: UserService,
|
|
||||||
useValue: {
|
|
||||||
listAll: () => of({ results: [] }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GroupService,
|
|
||||||
useValue: {
|
|
||||||
listAll: () => of({ results: [] }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
imports: [
|
imports: [
|
||||||
NgbModalModule,
|
NgbModalModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
@@ -46,9 +26,6 @@ describe('SaveViewConfigDialogComponent', () => {
|
|||||||
SaveViewConfigDialogComponent,
|
SaveViewConfigDialogComponent,
|
||||||
TextComponent,
|
TextComponent,
|
||||||
CheckComponent,
|
CheckComponent,
|
||||||
PermissionsFormComponent,
|
|
||||||
PermissionsUserComponent,
|
|
||||||
PermissionsGroupComponent,
|
|
||||||
],
|
],
|
||||||
}).compileComponents()
|
}).compileComponents()
|
||||||
|
|
||||||
@@ -104,26 +81,6 @@ describe('SaveViewConfigDialogComponent', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support permissions input', () => {
|
|
||||||
const permissions = {
|
|
||||||
owner: 10,
|
|
||||||
set_permissions: {
|
|
||||||
view: { users: [2], groups: [3] },
|
|
||||||
change: { users: [4], groups: [5] },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
let result
|
|
||||||
component.saveClicked.subscribe((saveResult) => (result = saveResult))
|
|
||||||
component.saveViewConfigForm.get('permissions_form').patchValue(permissions)
|
|
||||||
component.save()
|
|
||||||
expect(result).toEqual({
|
|
||||||
name: '',
|
|
||||||
showInSideBar: false,
|
|
||||||
showOnDashboard: false,
|
|
||||||
permissions_form: permissions,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support default name', () => {
|
it('should support default name', () => {
|
||||||
const saveClickedSpy = jest.spyOn(component.saveClicked, 'emit')
|
const saveClickedSpy = jest.spyOn(component.saveClicked, 'emit')
|
||||||
const modalCloseSpy = jest.spyOn(modal, 'close')
|
const modalCloseSpy = jest.spyOn(modal, 'close')
|
||||||
|
|||||||
@@ -13,27 +13,17 @@ import {
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
} from '@angular/forms'
|
} from '@angular/forms'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { User } from 'src/app/data/user'
|
|
||||||
import { UserService } from 'src/app/services/rest/user.service'
|
|
||||||
import { CheckComponent } from '../../common/input/check/check.component'
|
import { CheckComponent } from '../../common/input/check/check.component'
|
||||||
import { PermissionsFormComponent } from '../../common/input/permissions/permissions-form/permissions-form.component'
|
|
||||||
import { TextComponent } from '../../common/input/text/text.component'
|
import { TextComponent } from '../../common/input/text/text.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pngx-save-view-config-dialog',
|
selector: 'pngx-save-view-config-dialog',
|
||||||
templateUrl: './save-view-config-dialog.component.html',
|
templateUrl: './save-view-config-dialog.component.html',
|
||||||
styleUrls: ['./save-view-config-dialog.component.scss'],
|
styleUrls: ['./save-view-config-dialog.component.scss'],
|
||||||
imports: [
|
imports: [CheckComponent, TextComponent, FormsModule, ReactiveFormsModule],
|
||||||
CheckComponent,
|
|
||||||
TextComponent,
|
|
||||||
PermissionsFormComponent,
|
|
||||||
FormsModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class SaveViewConfigDialogComponent implements OnInit {
|
export class SaveViewConfigDialogComponent implements OnInit {
|
||||||
private modal = inject(NgbActiveModal)
|
private modal = inject(NgbActiveModal)
|
||||||
private userService = inject(UserService)
|
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
public saveClicked = new EventEmitter()
|
public saveClicked = new EventEmitter()
|
||||||
@@ -46,8 +36,6 @@ export class SaveViewConfigDialogComponent implements OnInit {
|
|||||||
|
|
||||||
closeEnabled = false
|
closeEnabled = false
|
||||||
|
|
||||||
users: User[]
|
|
||||||
|
|
||||||
_defaultName = ''
|
_defaultName = ''
|
||||||
|
|
||||||
get defaultName() {
|
get defaultName() {
|
||||||
@@ -64,7 +52,6 @@ export class SaveViewConfigDialogComponent implements OnInit {
|
|||||||
name: new FormControl(''),
|
name: new FormControl(''),
|
||||||
showInSideBar: new FormControl(false),
|
showInSideBar: new FormControl(false),
|
||||||
showOnDashboard: new FormControl(false),
|
showOnDashboard: new FormControl(false),
|
||||||
permissions_form: new FormControl(null),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -72,22 +59,10 @@ export class SaveViewConfigDialogComponent implements OnInit {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.closeEnabled = true
|
this.closeEnabled = true
|
||||||
})
|
})
|
||||||
this.userService.listAll().subscribe((r) => {
|
|
||||||
this.users = r.results
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
const formValue = this.saveViewConfigForm.value
|
this.saveClicked.emit(this.saveViewConfigForm.value)
|
||||||
const saveViewConfig = {
|
|
||||||
name: formValue.name,
|
|
||||||
showInSideBar: formValue.showInSideBar,
|
|
||||||
showOnDashboard: formValue.showOnDashboard,
|
|
||||||
}
|
|
||||||
if (formValue.permissions_form) {
|
|
||||||
saveViewConfig['permissions_form'] = formValue.permissions_form
|
|
||||||
}
|
|
||||||
this.saveClicked.emit(saveViewConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
|
|||||||
@@ -25,23 +25,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
@if (canDeleteSavedView(view)) {
|
<label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
|
||||||
<label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
|
<pngx-confirm-button
|
||||||
<button
|
label="Delete"
|
||||||
class="btn btn-sm btn-outline-secondary form-control mb-2"
|
i18n-label
|
||||||
type="button"
|
(confirm)="deleteSavedView(view)"
|
||||||
(click)="editPermissions(view)"
|
*pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.SavedView }"
|
||||||
*pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.SavedView }"
|
buttonClasses="btn-sm btn-outline-danger form-control"
|
||||||
i18n>Permissions</button>
|
iconName="trash">
|
||||||
<pngx-confirm-button
|
</pngx-confirm-button>
|
||||||
label="Delete"
|
|
||||||
i18n-label
|
|
||||||
(confirm)="deleteSavedView(view)"
|
|
||||||
*pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.SavedView }"
|
|
||||||
buttonClasses="btn-sm btn-outline-danger form-control"
|
|
||||||
iconName="trash">
|
|
||||||
</pngx-confirm-button>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
|||||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { By } from '@angular/platform-browser'
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||||
import { Subject, of, throwError } from 'rxjs'
|
import { of, throwError } from 'rxjs'
|
||||||
import { SavedView } from 'src/app/data/saved-view'
|
import { SavedView } from 'src/app/data/saved-view'
|
||||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
|
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
|
||||||
import { CheckComponent } from '../../common/input/check/check.component'
|
import { CheckComponent } from '../../common/input/check/check.component'
|
||||||
@@ -32,9 +32,7 @@ describe('SavedViewsComponent', () => {
|
|||||||
let component: SavedViewsComponent
|
let component: SavedViewsComponent
|
||||||
let fixture: ComponentFixture<SavedViewsComponent>
|
let fixture: ComponentFixture<SavedViewsComponent>
|
||||||
let savedViewService: SavedViewService
|
let savedViewService: SavedViewService
|
||||||
let settingsService: SettingsService
|
|
||||||
let toastService: ToastService
|
let toastService: ToastService
|
||||||
let modalService: NgbModal
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -59,8 +57,6 @@ describe('SavedViewsComponent', () => {
|
|||||||
provide: PermissionsService,
|
provide: PermissionsService,
|
||||||
useValue: {
|
useValue: {
|
||||||
currentUserCan: () => true,
|
currentUserCan: () => true,
|
||||||
currentUserHasObjectPermissions: () => true,
|
|
||||||
currentUserOwnsObject: () => true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -81,13 +77,11 @@ describe('SavedViewsComponent', () => {
|
|||||||
}).compileComponents()
|
}).compileComponents()
|
||||||
|
|
||||||
savedViewService = TestBed.inject(SavedViewService)
|
savedViewService = TestBed.inject(SavedViewService)
|
||||||
settingsService = TestBed.inject(SettingsService)
|
|
||||||
toastService = TestBed.inject(ToastService)
|
toastService = TestBed.inject(ToastService)
|
||||||
modalService = TestBed.inject(NgbModal)
|
|
||||||
fixture = TestBed.createComponent(SavedViewsComponent)
|
fixture = TestBed.createComponent(SavedViewsComponent)
|
||||||
component = fixture.componentInstance
|
component = fixture.componentInstance
|
||||||
|
|
||||||
jest.spyOn(savedViewService, 'list').mockReturnValue(
|
jest.spyOn(savedViewService, 'listAll').mockReturnValue(
|
||||||
of({
|
of({
|
||||||
all: savedViews.map((v) => v.id),
|
all: savedViews.map((v) => v.id),
|
||||||
count: savedViews.length,
|
count: savedViews.length,
|
||||||
@@ -100,13 +94,14 @@ describe('SavedViewsComponent', () => {
|
|||||||
|
|
||||||
it('should support save saved views, show error', () => {
|
it('should support save saved views, show error', () => {
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
|
const toastSpy = jest.spyOn(toastService, 'show')
|
||||||
const savedViewPatchSpy = jest.spyOn(savedViewService, 'patchMany')
|
const savedViewPatchSpy = jest.spyOn(savedViewService, 'patchMany')
|
||||||
const control = component.savedViewsForm
|
|
||||||
.get('savedViews')
|
const toggle = fixture.debugElement.query(
|
||||||
.get(savedViews[0].id.toString())
|
By.css('.form-check.form-switch input')
|
||||||
.get('name')
|
)
|
||||||
control.setValue(`${savedViews[0].name}-changed`)
|
toggle.nativeElement.checked = true
|
||||||
control.markAsDirty()
|
toggle.nativeElement.dispatchEvent(new Event('change'))
|
||||||
|
|
||||||
// saved views error first
|
// saved views error first
|
||||||
savedViewPatchSpy.mockReturnValueOnce(
|
savedViewPatchSpy.mockReturnValueOnce(
|
||||||
@@ -115,13 +110,12 @@ describe('SavedViewsComponent', () => {
|
|||||||
component.save()
|
component.save()
|
||||||
expect(toastErrorSpy).toHaveBeenCalled()
|
expect(toastErrorSpy).toHaveBeenCalled()
|
||||||
expect(savedViewPatchSpy).toHaveBeenCalled()
|
expect(savedViewPatchSpy).toHaveBeenCalled()
|
||||||
|
toastSpy.mockClear()
|
||||||
toastErrorSpy.mockClear()
|
toastErrorSpy.mockClear()
|
||||||
savedViewPatchSpy.mockClear()
|
savedViewPatchSpy.mockClear()
|
||||||
|
|
||||||
// succeed saved views
|
// succeed saved views
|
||||||
savedViewPatchSpy.mockReturnValueOnce(of(savedViews as SavedView[]))
|
savedViewPatchSpy.mockReturnValueOnce(of(savedViews as SavedView[]))
|
||||||
control.setValue(savedViews[0].name)
|
|
||||||
control.markAsDirty()
|
|
||||||
component.save()
|
component.save()
|
||||||
expect(toastErrorSpy).not.toHaveBeenCalled()
|
expect(toastErrorSpy).not.toHaveBeenCalled()
|
||||||
expect(savedViewPatchSpy).toHaveBeenCalled()
|
expect(savedViewPatchSpy).toHaveBeenCalled()
|
||||||
@@ -133,46 +127,26 @@ describe('SavedViewsComponent', () => {
|
|||||||
expect(patchSpy).not.toHaveBeenCalled()
|
expect(patchSpy).not.toHaveBeenCalled()
|
||||||
|
|
||||||
const view = savedViews[0]
|
const view = savedViews[0]
|
||||||
component.savedViewsForm
|
const toggle = fixture.debugElement.query(
|
||||||
.get('savedViews')
|
By.css('.form-check.form-switch input')
|
||||||
.get(view.id.toString())
|
)
|
||||||
.get('name')
|
toggle.nativeElement.checked = true
|
||||||
.setValue('changed-view-name')
|
toggle.nativeElement.dispatchEvent(new Event('change'))
|
||||||
component.savedViewsForm
|
// register change
|
||||||
.get('savedViews')
|
component.savedViewsForm.get('savedViews').get(view.id.toString()).value[
|
||||||
.get(view.id.toString())
|
'show_on_dashboard'
|
||||||
.get('name')
|
] = !view.show_on_dashboard
|
||||||
.markAsDirty()
|
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
|
|
||||||
component.save()
|
component.save()
|
||||||
expect(patchSpy).toHaveBeenCalled()
|
expect(patchSpy).toHaveBeenCalledWith([
|
||||||
const patchBody = patchSpy.mock.calls[0][0][0]
|
{
|
||||||
expect(patchBody).toMatchObject({
|
id: view.id,
|
||||||
id: view.id,
|
name: view.name,
|
||||||
name: 'changed-view-name',
|
show_in_sidebar: view.show_in_sidebar,
|
||||||
})
|
show_on_dashboard: !view.show_on_dashboard,
|
||||||
expect(patchBody.show_on_dashboard).toBeUndefined()
|
},
|
||||||
expect(patchBody.show_in_sidebar).toBeUndefined()
|
])
|
||||||
})
|
|
||||||
|
|
||||||
it('should persist visibility changes to user settings', () => {
|
|
||||||
const patchSpy = jest.spyOn(savedViewService, 'patchMany')
|
|
||||||
const updateVisibilitySpy = jest
|
|
||||||
.spyOn(settingsService, 'updateSavedViewsVisibility')
|
|
||||||
.mockReturnValue(of({ success: true }))
|
|
||||||
|
|
||||||
const dashboardControl = component.savedViewsForm
|
|
||||||
.get('savedViews')
|
|
||||||
.get(savedViews[0].id.toString())
|
|
||||||
.get('show_on_dashboard')
|
|
||||||
dashboardControl.setValue(false)
|
|
||||||
dashboardControl.markAsDirty()
|
|
||||||
|
|
||||||
component.save()
|
|
||||||
|
|
||||||
expect(patchSpy).not.toHaveBeenCalled()
|
|
||||||
expect(updateVisibilitySpy).toHaveBeenCalledWith([], [savedViews[0].id])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support delete saved view', () => {
|
it('should support delete saved view', () => {
|
||||||
@@ -188,55 +162,14 @@ describe('SavedViewsComponent', () => {
|
|||||||
|
|
||||||
it('should support reset', () => {
|
it('should support reset', () => {
|
||||||
const view = savedViews[0]
|
const view = savedViews[0]
|
||||||
component.savedViewsForm
|
component.savedViewsForm.get('savedViews').get(view.id.toString()).value[
|
||||||
.get('savedViews')
|
'show_on_dashboard'
|
||||||
.get(view.id.toString())
|
] = !view.show_on_dashboard
|
||||||
.get('show_on_dashboard')
|
|
||||||
.setValue(!view.show_on_dashboard)
|
|
||||||
component.reset()
|
component.reset()
|
||||||
expect(
|
expect(
|
||||||
component.savedViewsForm
|
component.savedViewsForm.get('savedViews').get(view.id.toString()).value[
|
||||||
.get('savedViews')
|
'show_on_dashboard'
|
||||||
.get(view.id.toString())
|
]
|
||||||
.get('show_on_dashboard').value
|
|
||||||
).toEqual(view.show_on_dashboard)
|
).toEqual(view.show_on_dashboard)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support editing permissions', () => {
|
|
||||||
const confirmClicked = new Subject<any>()
|
|
||||||
const modalRef = {
|
|
||||||
componentInstance: {
|
|
||||||
confirmClicked,
|
|
||||||
buttonsEnabled: true,
|
|
||||||
},
|
|
||||||
close: jest.fn(),
|
|
||||||
} as any
|
|
||||||
jest.spyOn(modalService, 'open').mockReturnValue(modalRef)
|
|
||||||
const patchSpy = jest.spyOn(savedViewService, 'patch')
|
|
||||||
patchSpy.mockReturnValue(of(savedViews[0] as SavedView))
|
|
||||||
|
|
||||||
component.editPermissions(savedViews[0] as SavedView)
|
|
||||||
confirmClicked.next({
|
|
||||||
permissions: {
|
|
||||||
owner: 1,
|
|
||||||
set_permissions: {
|
|
||||||
view: { users: [2], groups: [] },
|
|
||||||
change: { users: [], groups: [3] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
merge: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(patchSpy).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
id: savedViews[0].id,
|
|
||||||
owner: 1,
|
|
||||||
set_permissions: {
|
|
||||||
view: { users: [2], groups: [] },
|
|
||||||
change: { users: [], groups: [3] },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
expect(modalRef.close).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,17 +6,11 @@ import {
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
} from '@angular/forms'
|
} from '@angular/forms'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
import { dirtyCheck } from '@ngneat/dirty-check-forms'
|
import { dirtyCheck } from '@ngneat/dirty-check-forms'
|
||||||
import { BehaviorSubject, Observable, of, switchMap, takeUntil } from 'rxjs'
|
import { BehaviorSubject, Observable, takeUntil } from 'rxjs'
|
||||||
import { PermissionsDialogComponent } from 'src/app/components/common/permissions-dialog/permissions-dialog.component'
|
|
||||||
import { DisplayMode } from 'src/app/data/document'
|
import { DisplayMode } from 'src/app/data/document'
|
||||||
import { SavedView } from 'src/app/data/saved-view'
|
import { SavedView } from 'src/app/data/saved-view'
|
||||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||||
import {
|
|
||||||
PermissionAction,
|
|
||||||
PermissionsService,
|
|
||||||
} from 'src/app/services/permissions.service'
|
|
||||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
@@ -47,10 +41,8 @@ export class SavedViewsComponent
|
|||||||
implements OnInit, OnDestroy
|
implements OnInit, OnDestroy
|
||||||
{
|
{
|
||||||
private savedViewService = inject(SavedViewService)
|
private savedViewService = inject(SavedViewService)
|
||||||
private permissionsService = inject(PermissionsService)
|
|
||||||
private settings = inject(SettingsService)
|
private settings = inject(SettingsService)
|
||||||
private toastService = inject(ToastService)
|
private toastService = inject(ToastService)
|
||||||
private modalService = inject(NgbModal)
|
|
||||||
|
|
||||||
DisplayMode = DisplayMode
|
DisplayMode = DisplayMode
|
||||||
|
|
||||||
@@ -73,17 +65,11 @@ export class SavedViewsComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.reloadViews()
|
|
||||||
}
|
|
||||||
|
|
||||||
private reloadViews(): void {
|
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.savedViewService
|
this.savedViewService.listAll().subscribe((r) => {
|
||||||
.listAll(null, null, { full_perms: true })
|
this.savedViews = r.results
|
||||||
.subscribe((r) => {
|
this.initialize()
|
||||||
this.savedViews = r.results
|
})
|
||||||
this.initialize()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
@@ -109,20 +95,16 @@ export class SavedViewsComponent
|
|||||||
display_mode: view.display_mode,
|
display_mode: view.display_mode,
|
||||||
display_fields: view.display_fields,
|
display_fields: view.display_fields,
|
||||||
}
|
}
|
||||||
const canEdit = this.canEditSavedView(view)
|
|
||||||
this.savedViewsGroup.addControl(
|
this.savedViewsGroup.addControl(
|
||||||
view.id.toString(),
|
view.id.toString(),
|
||||||
new FormGroup({
|
new FormGroup({
|
||||||
id: new FormControl({ value: null, disabled: !canEdit }),
|
id: new FormControl(null),
|
||||||
name: new FormControl({ value: null, disabled: !canEdit }),
|
name: new FormControl(null),
|
||||||
show_on_dashboard: new FormControl({
|
show_on_dashboard: new FormControl(null),
|
||||||
value: null,
|
show_in_sidebar: new FormControl(null),
|
||||||
disabled: false,
|
page_size: new FormControl(null),
|
||||||
}),
|
display_mode: new FormControl(null),
|
||||||
show_in_sidebar: new FormControl({ value: null, disabled: false }),
|
display_fields: new FormControl([]),
|
||||||
page_size: new FormControl({ value: null, disabled: !canEdit }),
|
|
||||||
display_mode: new FormControl({ value: null, disabled: !canEdit }),
|
|
||||||
display_fields: new FormControl({ value: [], disabled: !canEdit }),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -151,7 +133,10 @@ export class SavedViewsComponent
|
|||||||
$localize`Saved view "${savedView.name}" deleted.`
|
$localize`Saved view "${savedView.name}" deleted.`
|
||||||
)
|
)
|
||||||
this.savedViewService.clearCache()
|
this.savedViewService.clearCache()
|
||||||
this.reloadViews()
|
this.savedViewService.listAll().subscribe((r) => {
|
||||||
|
this.savedViews = r.results
|
||||||
|
this.initialize()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,119 +145,26 @@ export class SavedViewsComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
public save() {
|
public save() {
|
||||||
// Save only changed views, then save the visibility changes into user settings.
|
// only patch views that have actually changed
|
||||||
const groups = Object.values(this.savedViewsGroup.controls) as FormGroup[]
|
|
||||||
const visibilityChanged = groups.some(
|
|
||||||
(group) =>
|
|
||||||
group.get('show_on_dashboard')?.dirty ||
|
|
||||||
group.get('show_in_sidebar')?.dirty
|
|
||||||
)
|
|
||||||
|
|
||||||
const changed: SavedView[] = []
|
const changed: SavedView[] = []
|
||||||
const dashboardVisibleIds: number[] = []
|
Object.values(this.savedViewsGroup.controls)
|
||||||
const sidebarVisibleIds: number[] = []
|
.filter((g: FormGroup) => !g.pristine)
|
||||||
|
.forEach((group: FormGroup) => {
|
||||||
groups.forEach((group) => {
|
changed.push(group.value)
|
||||||
const value = group.getRawValue()
|
})
|
||||||
if (value.show_on_dashboard) {
|
|
||||||
dashboardVisibleIds.push(value.id)
|
|
||||||
}
|
|
||||||
if (value.show_in_sidebar) {
|
|
||||||
sidebarVisibleIds.push(value.id)
|
|
||||||
}
|
|
||||||
// Would be fine to send, but no longer stored on the model
|
|
||||||
delete value.show_on_dashboard
|
|
||||||
delete value.show_in_sidebar
|
|
||||||
|
|
||||||
if (!group.get('name')?.enabled) {
|
|
||||||
// Quick check for user doesn't have permissions, then bail
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelFieldsChanged =
|
|
||||||
group.get('name')?.dirty ||
|
|
||||||
group.get('page_size')?.dirty ||
|
|
||||||
group.get('display_mode')?.dirty ||
|
|
||||||
group.get('display_fields')?.dirty
|
|
||||||
|
|
||||||
if (!modelFieldsChanged) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
changed.push(value)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!changed.length && !visibilityChanged) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let saveOperation = of([])
|
|
||||||
if (changed.length) {
|
if (changed.length) {
|
||||||
saveOperation = saveOperation.pipe(
|
this.savedViewService.patchMany(changed).subscribe({
|
||||||
switchMap(() => this.savedViewService.patchMany(changed))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (visibilityChanged) {
|
|
||||||
saveOperation = saveOperation.pipe(
|
|
||||||
switchMap(() =>
|
|
||||||
this.settings.updateSavedViewsVisibility(
|
|
||||||
dashboardVisibleIds,
|
|
||||||
sidebarVisibleIds
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveOperation.subscribe({
|
|
||||||
next: () => {
|
|
||||||
this.toastService.showInfo($localize`Views saved successfully.`)
|
|
||||||
this.savedViewService.clearCache()
|
|
||||||
this.reloadViews()
|
|
||||||
},
|
|
||||||
error: (error) => {
|
|
||||||
this.toastService.showError($localize`Error while saving views.`, error)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public canEditSavedView(view: SavedView): boolean {
|
|
||||||
return this.permissionsService.currentUserHasObjectPermissions(
|
|
||||||
PermissionAction.Change,
|
|
||||||
view
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public canDeleteSavedView(view: SavedView): boolean {
|
|
||||||
return this.permissionsService.currentUserOwnsObject(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
public editPermissions(savedView: SavedView): void {
|
|
||||||
const modal = this.modalService.open(PermissionsDialogComponent, {
|
|
||||||
backdrop: 'static',
|
|
||||||
})
|
|
||||||
const dialog = modal.componentInstance as PermissionsDialogComponent
|
|
||||||
dialog.object = savedView
|
|
||||||
|
|
||||||
modal.componentInstance.confirmClicked.subscribe(({ permissions }) => {
|
|
||||||
modal.componentInstance.buttonsEnabled = false
|
|
||||||
const view = {
|
|
||||||
id: savedView.id,
|
|
||||||
owner: permissions.owner,
|
|
||||||
}
|
|
||||||
view['set_permissions'] = permissions.set_permissions
|
|
||||||
this.savedViewService.patch(view as SavedView).subscribe({
|
|
||||||
next: () => {
|
next: () => {
|
||||||
this.toastService.showInfo($localize`Permissions updated`)
|
this.toastService.showInfo($localize`Views saved successfully.`)
|
||||||
modal.close()
|
this.store.next(this.savedViewsForm.value)
|
||||||
this.reloadViews()
|
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
this.toastService.showError(
|
this.toastService.showError(
|
||||||
$localize`Error updating permissions`,
|
$localize`Error while saving views.`,
|
||||||
error
|
error
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,10 +62,6 @@ export const SETTINGS_KEYS = {
|
|||||||
'general-settings:update-checking:backend-setting',
|
'general-settings:update-checking:backend-setting',
|
||||||
SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE:
|
SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE:
|
||||||
'general-settings:saved-views:warn-on-unsaved-change',
|
'general-settings:saved-views:warn-on-unsaved-change',
|
||||||
DASHBOARD_VIEWS_VISIBLE_IDS:
|
|
||||||
'general-settings:saved-views:dashboard-views-visible-ids',
|
|
||||||
SIDEBAR_VIEWS_VISIBLE_IDS:
|
|
||||||
'general-settings:saved-views:sidebar-views-visible-ids',
|
|
||||||
DASHBOARD_VIEWS_SORT_ORDER:
|
DASHBOARD_VIEWS_SORT_ORDER:
|
||||||
'general-settings:saved-views:dashboard-views-sort-order',
|
'general-settings:saved-views:dashboard-views-sort-order',
|
||||||
SIDEBAR_VIEWS_SORT_ORDER:
|
SIDEBAR_VIEWS_SORT_ORDER:
|
||||||
@@ -252,16 +248,6 @@ export const SETTINGS: UiSetting[] = [
|
|||||||
type: 'array',
|
type: 'array',
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: SETTINGS_KEYS.DASHBOARD_VIEWS_VISIBLE_IDS,
|
|
||||||
type: 'array',
|
|
||||||
default: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SETTINGS_KEYS.SIDEBAR_VIEWS_VISIBLE_IDS,
|
|
||||||
type: 'array',
|
|
||||||
default: [],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER,
|
key: SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER,
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|||||||
@@ -57,11 +57,6 @@ describe(`Additional service tests for SavedViewService`, () => {
|
|||||||
let settingsService
|
let settingsService
|
||||||
|
|
||||||
it('should retrieve saved views and sort them', () => {
|
it('should retrieve saved views and sort them', () => {
|
||||||
jest.spyOn(settingsService, 'get').mockImplementation((key) => {
|
|
||||||
if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_VISIBLE_IDS) return [1, 2, 3]
|
|
||||||
if (key === SETTINGS_KEYS.SIDEBAR_VIEWS_VISIBLE_IDS) return [1, 2, 3]
|
|
||||||
return []
|
|
||||||
})
|
|
||||||
service.reload()
|
service.reload()
|
||||||
const req = httpTestingController.expectOne(
|
const req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=100000`
|
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=100000`
|
||||||
@@ -98,9 +93,7 @@ describe(`Additional service tests for SavedViewService`, () => {
|
|||||||
it('should sort dashboard views', () => {
|
it('should sort dashboard views', () => {
|
||||||
service['savedViews'] = saved_views
|
service['savedViews'] = saved_views
|
||||||
jest.spyOn(settingsService, 'get').mockImplementation((key) => {
|
jest.spyOn(settingsService, 'get').mockImplementation((key) => {
|
||||||
if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_VISIBLE_IDS) return [1, 2, 3]
|
|
||||||
if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER) return [3, 1, 2]
|
if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER) return [3, 1, 2]
|
||||||
return []
|
|
||||||
})
|
})
|
||||||
expect(service.dashboardViews).toEqual([
|
expect(service.dashboardViews).toEqual([
|
||||||
saved_views[2],
|
saved_views[2],
|
||||||
@@ -109,21 +102,10 @@ describe(`Additional service tests for SavedViewService`, () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should use user-specific dashboard visibility when configured', () => {
|
|
||||||
service['savedViews'] = saved_views
|
|
||||||
jest.spyOn(settingsService, 'get').mockImplementation((key) => {
|
|
||||||
if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_VISIBLE_IDS) return [4, 2]
|
|
||||||
if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER) return []
|
|
||||||
})
|
|
||||||
expect(service.dashboardViews).toEqual([saved_views[1], saved_views[3]])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should sort sidebar views', () => {
|
it('should sort sidebar views', () => {
|
||||||
service['savedViews'] = saved_views
|
service['savedViews'] = saved_views
|
||||||
jest.spyOn(settingsService, 'get').mockImplementation((key) => {
|
jest.spyOn(settingsService, 'get').mockImplementation((key) => {
|
||||||
if (key === SETTINGS_KEYS.SIDEBAR_VIEWS_VISIBLE_IDS) return [1, 2, 3]
|
|
||||||
if (key === SETTINGS_KEYS.SIDEBAR_VIEWS_SORT_ORDER) return [3, 1, 2]
|
if (key === SETTINGS_KEYS.SIDEBAR_VIEWS_SORT_ORDER) return [3, 1, 2]
|
||||||
return []
|
|
||||||
})
|
})
|
||||||
expect(service.sidebarViews).toEqual([
|
expect(service.sidebarViews).toEqual([
|
||||||
saved_views[2],
|
saved_views[2],
|
||||||
@@ -132,15 +114,6 @@ describe(`Additional service tests for SavedViewService`, () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should use user-specific sidebar visibility when configured', () => {
|
|
||||||
service['savedViews'] = saved_views
|
|
||||||
jest.spyOn(settingsService, 'get').mockImplementation((key) => {
|
|
||||||
if (key === SETTINGS_KEYS.SIDEBAR_VIEWS_VISIBLE_IDS) return [4, 2]
|
|
||||||
if (key === SETTINGS_KEYS.SIDEBAR_VIEWS_SORT_ORDER) return []
|
|
||||||
})
|
|
||||||
expect(service.sidebarViews).toEqual([saved_views[1], saved_views[3]])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should treat empty display_fields as null', () => {
|
it('should treat empty display_fields as null', () => {
|
||||||
subscription = service
|
subscription = service
|
||||||
.patch({
|
.patch({
|
||||||
|
|||||||
@@ -36,9 +36,7 @@ export class SavedViewService extends AbstractPaperlessService<SavedView> {
|
|||||||
return super.list(page, pageSize, sortField, sortReverse, extraParams).pipe(
|
return super.list(page, pageSize, sortField, sortReverse, extraParams).pipe(
|
||||||
tap({
|
tap({
|
||||||
next: (r) => {
|
next: (r) => {
|
||||||
const views = r.results.map((view) => this.withUserVisibility(view))
|
this.savedViews = r.results
|
||||||
this.savedViews = views
|
|
||||||
r.results = views
|
|
||||||
this._loading = false
|
this._loading = false
|
||||||
this.settingsService.dashboardIsEmpty =
|
this.settingsService.dashboardIsEmpty =
|
||||||
this.dashboardViews.length === 0
|
this.dashboardViews.length === 0
|
||||||
@@ -67,35 +65,8 @@ export class SavedViewService extends AbstractPaperlessService<SavedView> {
|
|||||||
return this.savedViews
|
return this.savedViews
|
||||||
}
|
}
|
||||||
|
|
||||||
private getVisibleViewIds(setting: string): number[] {
|
|
||||||
const configured = this.settingsService.get(setting)
|
|
||||||
return Array.isArray(configured) ? configured : []
|
|
||||||
}
|
|
||||||
|
|
||||||
private withUserVisibility(view: SavedView): SavedView {
|
|
||||||
return {
|
|
||||||
...view,
|
|
||||||
show_on_dashboard: this.isDashboardVisible(view),
|
|
||||||
show_in_sidebar: this.isSidebarVisible(view),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isDashboardVisible(view: SavedView): boolean {
|
|
||||||
const visibleIds = this.getVisibleViewIds(
|
|
||||||
SETTINGS_KEYS.DASHBOARD_VIEWS_VISIBLE_IDS
|
|
||||||
)
|
|
||||||
return visibleIds.includes(view.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
private isSidebarVisible(view: SavedView): boolean {
|
|
||||||
const visibleIds = this.getVisibleViewIds(
|
|
||||||
SETTINGS_KEYS.SIDEBAR_VIEWS_VISIBLE_IDS
|
|
||||||
)
|
|
||||||
return visibleIds.includes(view.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
get sidebarViews(): SavedView[] {
|
get sidebarViews(): SavedView[] {
|
||||||
const sidebarViews = this.savedViews.filter((v) => this.isSidebarVisible(v))
|
const sidebarViews = this.savedViews.filter((v) => v.show_in_sidebar)
|
||||||
|
|
||||||
const sorted: number[] = this.settingsService.get(
|
const sorted: number[] = this.settingsService.get(
|
||||||
SETTINGS_KEYS.SIDEBAR_VIEWS_SORT_ORDER
|
SETTINGS_KEYS.SIDEBAR_VIEWS_SORT_ORDER
|
||||||
@@ -110,9 +81,7 @@ export class SavedViewService extends AbstractPaperlessService<SavedView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get dashboardViews(): SavedView[] {
|
get dashboardViews(): SavedView[] {
|
||||||
const dashboardViews = this.savedViews.filter((v) =>
|
const dashboardViews = this.savedViews.filter((v) => v.show_on_dashboard)
|
||||||
this.isDashboardVisible(v)
|
|
||||||
)
|
|
||||||
|
|
||||||
const sorted: number[] = this.settingsService.get(
|
const sorted: number[] = this.settingsService.get(
|
||||||
SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER
|
SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ describe('SettingsService', () => {
|
|||||||
expect(req.request.method).toEqual('POST')
|
expect(req.request.method).toEqual('POST')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should update saved view sorting and visibility', () => {
|
it('should update saved view sorting', () => {
|
||||||
httpTestingController
|
httpTestingController
|
||||||
.expectOne(`${environment.apiBaseUrl}ui_settings/`)
|
.expectOne(`${environment.apiBaseUrl}ui_settings/`)
|
||||||
.flush(ui_settings)
|
.flush(ui_settings)
|
||||||
@@ -341,15 +341,6 @@ describe('SettingsService', () => {
|
|||||||
SETTINGS_KEYS.SIDEBAR_VIEWS_SORT_ORDER,
|
SETTINGS_KEYS.SIDEBAR_VIEWS_SORT_ORDER,
|
||||||
[1, 4]
|
[1, 4]
|
||||||
)
|
)
|
||||||
settingsService.updateSavedViewsVisibility([1, 4], [4, 1])
|
|
||||||
expect(setSpy).toHaveBeenCalledWith(
|
|
||||||
SETTINGS_KEYS.DASHBOARD_VIEWS_VISIBLE_IDS,
|
|
||||||
[1, 4]
|
|
||||||
)
|
|
||||||
expect(setSpy).toHaveBeenCalledWith(
|
|
||||||
SETTINGS_KEYS.SIDEBAR_VIEWS_VISIBLE_IDS,
|
|
||||||
[4, 1]
|
|
||||||
)
|
|
||||||
httpTestingController
|
httpTestingController
|
||||||
.expectOne(`${environment.apiBaseUrl}ui_settings/`)
|
.expectOne(`${environment.apiBaseUrl}ui_settings/`)
|
||||||
.flush(ui_settings)
|
.flush(ui_settings)
|
||||||
|
|||||||
@@ -699,17 +699,4 @@ export class SettingsService {
|
|||||||
])
|
])
|
||||||
return this.storeSettings()
|
return this.storeSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSavedViewsVisibility(
|
|
||||||
dashboardVisibleViewIds: number[],
|
|
||||||
sidebarVisibleViewIds: number[]
|
|
||||||
): Observable<any> {
|
|
||||||
this.set(SETTINGS_KEYS.DASHBOARD_VIEWS_VISIBLE_IDS, [
|
|
||||||
...new Set(dashboardVisibleViewIds),
|
|
||||||
])
|
|
||||||
this.set(SETTINGS_KEYS.SIDEBAR_VIEWS_VISIBLE_IDS, [
|
|
||||||
...new Set(sidebarVisibleViewIds),
|
|
||||||
])
|
|
||||||
return this.storeSettings()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const base_url = new URL(document.baseURI)
|
|||||||
export const environment = {
|
export const environment = {
|
||||||
production: true,
|
production: true,
|
||||||
apiBaseUrl: document.baseURI + 'api/',
|
apiBaseUrl: document.baseURI + 'api/',
|
||||||
apiVersion: '10', // match src/paperless/settings.py
|
apiVersion: '9', // match src/paperless/settings.py
|
||||||
appTitle: 'Paperless-ngx',
|
appTitle: 'Paperless-ngx',
|
||||||
tag: 'prod',
|
tag: 'prod',
|
||||||
version: '2.20.7',
|
version: '2.20.7',
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
# Generated by Django 5.2.11 on 2026-02-20 22:05
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
# from src-ui/src/app/data/ui-settings.ts
|
|
||||||
DASHBOARD_VIEWS_VISIBLE_IDS_KEY = (
|
|
||||||
"general-settings:saved-views:dashboard-views-visible-ids"
|
|
||||||
)
|
|
||||||
SIDEBAR_VIEWS_VISIBLE_IDS_KEY = "general-settings:saved-views:sidebar-views-visible-ids"
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_visible_ids(raw_value) -> set[int]:
|
|
||||||
if not isinstance(raw_value, list):
|
|
||||||
return set()
|
|
||||||
|
|
||||||
parsed_ids = set()
|
|
||||||
for raw_id in raw_value:
|
|
||||||
if isinstance(raw_id, int):
|
|
||||||
parsed_ids.add(raw_id)
|
|
||||||
elif isinstance(raw_id, str) and raw_id.isdigit():
|
|
||||||
parsed_ids.add(int(raw_id))
|
|
||||||
return parsed_ids
|
|
||||||
|
|
||||||
|
|
||||||
def _set_default_visibility_ids(apps, schema_editor):
|
|
||||||
SavedView = apps.get_model("documents", "SavedView")
|
|
||||||
UiSettings = apps.get_model("documents", "UiSettings")
|
|
||||||
User = apps.get_model("auth", "User")
|
|
||||||
|
|
||||||
dashboard_visible_ids_by_owner: dict[int, list[int]] = {}
|
|
||||||
for owner_id, view_id in SavedView.objects.filter(
|
|
||||||
owner__isnull=False,
|
|
||||||
show_on_dashboard=True,
|
|
||||||
).values_list("owner_id", "id"):
|
|
||||||
dashboard_visible_ids_by_owner.setdefault(owner_id, []).append(view_id)
|
|
||||||
|
|
||||||
sidebar_visible_ids_by_owner: dict[int, list[int]] = {}
|
|
||||||
for owner_id, view_id in SavedView.objects.filter(
|
|
||||||
owner__isnull=False,
|
|
||||||
show_in_sidebar=True,
|
|
||||||
).values_list("owner_id", "id"):
|
|
||||||
sidebar_visible_ids_by_owner.setdefault(owner_id, []).append(view_id)
|
|
||||||
|
|
||||||
for user in User.objects.all():
|
|
||||||
ui_settings, _ = UiSettings.objects.get_or_create(
|
|
||||||
user=user,
|
|
||||||
defaults={"settings": {}},
|
|
||||||
)
|
|
||||||
current_settings = ui_settings.settings
|
|
||||||
if not isinstance(current_settings, dict):
|
|
||||||
current_settings = {}
|
|
||||||
|
|
||||||
changed = False
|
|
||||||
|
|
||||||
if current_settings.get(DASHBOARD_VIEWS_VISIBLE_IDS_KEY) is None:
|
|
||||||
current_settings[DASHBOARD_VIEWS_VISIBLE_IDS_KEY] = (
|
|
||||||
dashboard_visible_ids_by_owner.get(user.id, [])
|
|
||||||
)
|
|
||||||
changed = True
|
|
||||||
if current_settings.get(SIDEBAR_VIEWS_VISIBLE_IDS_KEY) is None:
|
|
||||||
current_settings[SIDEBAR_VIEWS_VISIBLE_IDS_KEY] = (
|
|
||||||
sidebar_visible_ids_by_owner.get(user.id, [])
|
|
||||||
)
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if changed:
|
|
||||||
ui_settings.settings = current_settings
|
|
||||||
ui_settings.save(update_fields=["settings"])
|
|
||||||
|
|
||||||
|
|
||||||
def _restore_visibility_fields(apps, schema_editor):
|
|
||||||
SavedView = apps.get_model("documents", "SavedView")
|
|
||||||
UiSettings = apps.get_model("documents", "UiSettings")
|
|
||||||
|
|
||||||
dashboard_visible_ids_by_owner: dict[int, set[int]] = {}
|
|
||||||
sidebar_visible_ids_by_owner: dict[int, set[int]] = {}
|
|
||||||
for ui_settings in UiSettings.objects.all():
|
|
||||||
current_settings = ui_settings.settings
|
|
||||||
if not isinstance(current_settings, dict):
|
|
||||||
continue
|
|
||||||
dashboard_visible_ids_by_owner[ui_settings.user_id] = _parse_visible_ids(
|
|
||||||
current_settings.get(DASHBOARD_VIEWS_VISIBLE_IDS_KEY),
|
|
||||||
)
|
|
||||||
sidebar_visible_ids_by_owner[ui_settings.user_id] = _parse_visible_ids(
|
|
||||||
current_settings.get(SIDEBAR_VIEWS_VISIBLE_IDS_KEY),
|
|
||||||
)
|
|
||||||
|
|
||||||
SavedView.objects.update(show_on_dashboard=False, show_in_sidebar=False)
|
|
||||||
for owner_id, dashboard_visible_ids in dashboard_visible_ids_by_owner.items():
|
|
||||||
if not dashboard_visible_ids:
|
|
||||||
continue
|
|
||||||
SavedView.objects.filter(
|
|
||||||
owner_id=owner_id,
|
|
||||||
id__in=dashboard_visible_ids,
|
|
||||||
).update(
|
|
||||||
show_on_dashboard=True,
|
|
||||||
)
|
|
||||||
for owner_id, sidebar_visible_ids in sidebar_visible_ids_by_owner.items():
|
|
||||||
if not sidebar_visible_ids:
|
|
||||||
continue
|
|
||||||
SavedView.objects.filter(owner_id=owner_id, id__in=sidebar_visible_ids).update(
|
|
||||||
show_in_sidebar=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("documents", "0011_optimize_integer_field_sizes"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="savedview",
|
|
||||||
name="show_on_dashboard",
|
|
||||||
field=models.BooleanField(default=False, verbose_name="show on dashboard"),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="savedview",
|
|
||||||
name="show_in_sidebar",
|
|
||||||
field=models.BooleanField(default=False, verbose_name="show in sidebar"),
|
|
||||||
),
|
|
||||||
migrations.RunPython(
|
|
||||||
_set_default_visibility_ids,
|
|
||||||
reverse_code=_restore_visibility_fields,
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="savedview",
|
|
||||||
name="show_on_dashboard",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="savedview",
|
|
||||||
name="show_in_sidebar",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -443,6 +443,13 @@ class SavedView(ModelWithOwner):
|
|||||||
|
|
||||||
name = models.CharField(_("name"), max_length=128)
|
name = models.CharField(_("name"), max_length=128)
|
||||||
|
|
||||||
|
show_on_dashboard = models.BooleanField(
|
||||||
|
_("show on dashboard"),
|
||||||
|
)
|
||||||
|
show_in_sidebar = models.BooleanField(
|
||||||
|
_("show in sidebar"),
|
||||||
|
)
|
||||||
|
|
||||||
sort_field = models.CharField(
|
sort_field = models.CharField(
|
||||||
_("sort field"),
|
_("sort field"),
|
||||||
max_length=128,
|
max_length=128,
|
||||||
|
|||||||
@@ -1383,6 +1383,8 @@ class SavedViewSerializer(OwnedObjectSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
|
"show_on_dashboard",
|
||||||
|
"show_in_sidebar",
|
||||||
"sort_field",
|
"sort_field",
|
||||||
"sort_reverse",
|
"sort_reverse",
|
||||||
"filter_rules",
|
"filter_rules",
|
||||||
@@ -1392,7 +1394,6 @@ class SavedViewSerializer(OwnedObjectSerializer):
|
|||||||
"owner",
|
"owner",
|
||||||
"permissions",
|
"permissions",
|
||||||
"user_can_change",
|
"user_can_change",
|
||||||
"set_permissions",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
@@ -1430,7 +1431,7 @@ class SavedViewSerializer(OwnedObjectSerializer):
|
|||||||
and len(validated_data["display_fields"]) == 0
|
and len(validated_data["display_fields"]) == 0
|
||||||
):
|
):
|
||||||
validated_data["display_fields"] = None
|
validated_data["display_fields"] = None
|
||||||
instance = super().update(instance, validated_data)
|
super().update(instance, validated_data)
|
||||||
if rules_data is not None:
|
if rules_data is not None:
|
||||||
SavedViewFilterRule.objects.filter(saved_view=instance).delete()
|
SavedViewFilterRule.objects.filter(saved_view=instance).delete()
|
||||||
for rule_data in rules_data:
|
for rule_data in rules_data:
|
||||||
@@ -1442,7 +1443,7 @@ class SavedViewSerializer(OwnedObjectSerializer):
|
|||||||
if "user" in validated_data:
|
if "user" in validated_data:
|
||||||
# backwards compatibility
|
# backwards compatibility
|
||||||
validated_data["owner"] = validated_data.pop("user")
|
validated_data["owner"] = validated_data.pop("user")
|
||||||
saved_view = super().create(validated_data)
|
saved_view = SavedView.objects.create(**validated_data)
|
||||||
for rule_data in rules_data:
|
for rule_data in rules_data:
|
||||||
SavedViewFilterRule.objects.create(saved_view=saved_view, **rule_data)
|
SavedViewFilterRule.objects.create(saved_view=saved_view, **rule_data)
|
||||||
return saved_view
|
return saved_view
|
||||||
|
|||||||
@@ -2014,93 +2014,69 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
|||||||
mock_get_date_parser.assert_not_called()
|
mock_get_date_parser.assert_not_called()
|
||||||
|
|
||||||
def test_saved_views(self) -> None:
|
def test_saved_views(self) -> None:
|
||||||
u1 = User.objects.create_user("user1")
|
u1 = User.objects.create_superuser("user1")
|
||||||
u2 = User.objects.create_user("user2")
|
u2 = User.objects.create_superuser("user2")
|
||||||
u3 = User.objects.create_user("user3")
|
|
||||||
|
|
||||||
view_perm = Permission.objects.get(codename="view_savedview")
|
|
||||||
change_perm = Permission.objects.get(codename="change_savedview")
|
|
||||||
for user in [u1, u2, u3]:
|
|
||||||
user.user_permissions.add(view_perm, change_perm)
|
|
||||||
|
|
||||||
v1 = SavedView.objects.create(
|
v1 = SavedView.objects.create(
|
||||||
owner=u1,
|
owner=u1,
|
||||||
name="test1",
|
name="test1",
|
||||||
sort_field="",
|
sort_field="",
|
||||||
|
show_on_dashboard=False,
|
||||||
|
show_in_sidebar=False,
|
||||||
)
|
)
|
||||||
v2 = SavedView.objects.create(
|
SavedView.objects.create(
|
||||||
owner=u2,
|
owner=u2,
|
||||||
name="test2",
|
name="test2",
|
||||||
sort_field="",
|
sort_field="",
|
||||||
|
show_on_dashboard=False,
|
||||||
|
show_in_sidebar=False,
|
||||||
)
|
)
|
||||||
v3 = SavedView.objects.create(
|
SavedView.objects.create(
|
||||||
owner=u2,
|
owner=u2,
|
||||||
name="test3",
|
name="test3",
|
||||||
sort_field="",
|
sort_field="",
|
||||||
|
show_on_dashboard=False,
|
||||||
|
show_in_sidebar=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
assign_perm("view_savedview", u1, v2)
|
response = self.client.get("/api/saved_views/")
|
||||||
assign_perm("change_savedview", u1, v2)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
assign_perm("view_savedview", u1, v3)
|
self.assertEqual(response.data["count"], 0)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.client.get(f"/api/saved_views/{v1.id}/").status_code,
|
||||||
|
status.HTTP_404_NOT_FOUND,
|
||||||
|
)
|
||||||
|
|
||||||
self.client.force_authenticate(user=u1)
|
self.client.force_authenticate(user=u1)
|
||||||
|
|
||||||
response = self.client.get("/api/saved_views/")
|
response = self.client.get("/api/saved_views/")
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(response.data["count"], 3)
|
self.assertEqual(response.data["count"], 1)
|
||||||
|
|
||||||
for view_id in [v1.id, v2.id, v3.id]:
|
|
||||||
self.assertEqual(
|
|
||||||
self.client.get(f"/api/saved_views/{view_id}/").status_code,
|
|
||||||
status.HTTP_200_OK,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self.client.patch(
|
|
||||||
f"/api/saved_views/{v2.id}/",
|
|
||||||
{"sort_field": "added"},
|
|
||||||
format="json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
|
|
||||||
response = self.client.patch(
|
|
||||||
f"/api/saved_views/{v3.id}/",
|
|
||||||
{"sort_field": "added"},
|
|
||||||
format="json",
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
response.status_code,
|
self.client.get(f"/api/saved_views/{v1.id}/").status_code,
|
||||||
status.HTTP_403_FORBIDDEN,
|
status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self.client.patch(
|
self.client.force_authenticate(user=u2)
|
||||||
f"/api/saved_views/{v2.id}/",
|
|
||||||
{
|
|
||||||
"set_permissions": {
|
|
||||||
"view": {"users": [u3.id]},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
format="json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
response = self.client.patch(
|
|
||||||
f"/api/saved_views/{v2.id}/",
|
|
||||||
{"owner": u1.id},
|
|
||||||
format="json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
self.client.force_authenticate(user=u3)
|
|
||||||
|
|
||||||
response = self.client.get("/api/saved_views/")
|
response = self.client.get("/api/saved_views/")
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(response.data["count"], 0)
|
self.assertEqual(response.data["count"], 2)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.client.get(f"/api/saved_views/{v1.id}/").status_code,
|
||||||
|
status.HTTP_404_NOT_FOUND,
|
||||||
|
)
|
||||||
|
|
||||||
def test_saved_view_create_update_patch(self) -> None:
|
def test_saved_view_create_update_patch(self) -> None:
|
||||||
User.objects.create_user("user1")
|
User.objects.create_user("user1")
|
||||||
|
|
||||||
view = {
|
view = {
|
||||||
"name": "test",
|
"name": "test",
|
||||||
|
"show_on_dashboard": True,
|
||||||
|
"show_in_sidebar": True,
|
||||||
"sort_field": "created2",
|
"sort_field": "created2",
|
||||||
"filter_rules": [{"rule_type": 4, "value": "test"}],
|
"filter_rules": [{"rule_type": 4, "value": "test"}],
|
||||||
}
|
}
|
||||||
@@ -2115,13 +2091,13 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
|||||||
|
|
||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
f"/api/saved_views/{v1.id}/",
|
f"/api/saved_views/{v1.id}/",
|
||||||
{"sort_reverse": True},
|
{"show_in_sidebar": False},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
|
|
||||||
v1 = SavedView.objects.get(id=v1.id)
|
v1 = SavedView.objects.get(id=v1.id)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertTrue(v1.sort_reverse)
|
self.assertFalse(v1.show_in_sidebar)
|
||||||
self.assertEqual(v1.filter_rules.count(), 1)
|
self.assertEqual(v1.filter_rules.count(), 1)
|
||||||
|
|
||||||
view["filter_rules"] = [{"rule_type": 12, "value": "secret"}]
|
view["filter_rules"] = [{"rule_type": 12, "value": "secret"}]
|
||||||
@@ -2155,6 +2131,8 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
|||||||
|
|
||||||
view = {
|
view = {
|
||||||
"name": "test",
|
"name": "test",
|
||||||
|
"show_on_dashboard": True,
|
||||||
|
"show_in_sidebar": True,
|
||||||
"sort_field": "created2",
|
"sort_field": "created2",
|
||||||
"filter_rules": [{"rule_type": 4, "value": "test"}],
|
"filter_rules": [{"rule_type": 4, "value": "test"}],
|
||||||
"page_size": 20,
|
"page_size": 20,
|
||||||
@@ -2242,6 +2220,8 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
|||||||
"""
|
"""
|
||||||
view = {
|
view = {
|
||||||
"name": "test",
|
"name": "test",
|
||||||
|
"show_on_dashboard": True,
|
||||||
|
"show_in_sidebar": True,
|
||||||
"sort_field": "created2",
|
"sort_field": "created2",
|
||||||
"filter_rules": [{"rule_type": 4, "value": "test"}],
|
"filter_rules": [{"rule_type": 4, "value": "test"}],
|
||||||
"page_size": 20,
|
"page_size": 20,
|
||||||
@@ -2317,6 +2297,8 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
|||||||
owner=self.user,
|
owner=self.user,
|
||||||
name="test",
|
name="test",
|
||||||
sort_field=SavedView.DisplayFields.CUSTOM_FIELD % custom_field.id,
|
sort_field=SavedView.DisplayFields.CUSTOM_FIELD % custom_field.id,
|
||||||
|
show_on_dashboard=True,
|
||||||
|
show_in_sidebar=True,
|
||||||
display_fields=[
|
display_fields=[
|
||||||
SavedView.DisplayFields.TITLE,
|
SavedView.DisplayFields.TITLE,
|
||||||
SavedView.DisplayFields.CREATED,
|
SavedView.DisplayFields.CREATED,
|
||||||
|
|||||||
@@ -1307,12 +1307,13 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
|
|||||||
tag1 = Tag.objects.create(name="bank tag1")
|
tag1 = Tag.objects.create(name="bank tag1")
|
||||||
Tag.objects.create(name="tag2")
|
Tag.objects.create(name="tag2")
|
||||||
|
|
||||||
shared_view = SavedView.objects.create(
|
SavedView.objects.create(
|
||||||
name="bank view",
|
name="bank view",
|
||||||
|
show_on_dashboard=True,
|
||||||
|
show_in_sidebar=True,
|
||||||
sort_field="",
|
sort_field="",
|
||||||
owner=user2,
|
owner=user1,
|
||||||
)
|
)
|
||||||
assign_perm("view_savedview", user1, shared_view)
|
|
||||||
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(
|
||||||
|
|||||||
@@ -1,175 +0,0 @@
|
|||||||
from documents.tests.utils import TestMigrations
|
|
||||||
|
|
||||||
DASHBOARD_VIEWS_VISIBLE_IDS_KEY = (
|
|
||||||
"general-settings:saved-views:dashboard-views-visible-ids"
|
|
||||||
)
|
|
||||||
SIDEBAR_VIEWS_VISIBLE_IDS_KEY = "general-settings:saved-views:sidebar-views-visible-ids"
|
|
||||||
|
|
||||||
|
|
||||||
class TestMigrateSavedViewVisibilityToUiSettings(TestMigrations):
|
|
||||||
migrate_from = "0011_optimize_integer_field_sizes"
|
|
||||||
migrate_to = "0012_savedview_visibility_to_ui_settings"
|
|
||||||
|
|
||||||
def setUpBeforeMigration(self, apps) -> None:
|
|
||||||
User = apps.get_model("auth", "User")
|
|
||||||
SavedView = apps.get_model("documents", "SavedView")
|
|
||||||
UiSettings = apps.get_model("documents", "UiSettings")
|
|
||||||
|
|
||||||
self.user_with_empty_settings = User.objects.create(username="user1")
|
|
||||||
self.user_with_existing_settings = User.objects.create(username="user2")
|
|
||||||
self.user_with_owned_views = User.objects.create(username="user3")
|
|
||||||
self.user_with_empty_settings_id = self.user_with_empty_settings.id
|
|
||||||
self.user_with_existing_settings_id = self.user_with_existing_settings.id
|
|
||||||
self.user_with_owned_views_id = self.user_with_owned_views.id
|
|
||||||
|
|
||||||
self.dashboard_view = SavedView.objects.create(
|
|
||||||
owner=self.user_with_empty_settings,
|
|
||||||
name="dashboard",
|
|
||||||
show_on_dashboard=True,
|
|
||||||
show_in_sidebar=True,
|
|
||||||
sort_field="created",
|
|
||||||
)
|
|
||||||
self.sidebar_only_view = SavedView.objects.create(
|
|
||||||
owner=self.user_with_empty_settings,
|
|
||||||
name="sidebar-only",
|
|
||||||
show_on_dashboard=False,
|
|
||||||
show_in_sidebar=True,
|
|
||||||
sort_field="created",
|
|
||||||
)
|
|
||||||
self.hidden_view = SavedView.objects.create(
|
|
||||||
owner=self.user_with_empty_settings,
|
|
||||||
name="hidden",
|
|
||||||
show_on_dashboard=False,
|
|
||||||
show_in_sidebar=False,
|
|
||||||
sort_field="created",
|
|
||||||
)
|
|
||||||
self.other_owner_visible_view = SavedView.objects.create(
|
|
||||||
owner=self.user_with_owned_views,
|
|
||||||
name="other-owner-visible",
|
|
||||||
show_on_dashboard=True,
|
|
||||||
show_in_sidebar=True,
|
|
||||||
sort_field="created",
|
|
||||||
)
|
|
||||||
|
|
||||||
UiSettings.objects.create(user=self.user_with_empty_settings, settings={})
|
|
||||||
UiSettings.objects.create(
|
|
||||||
user=self.user_with_existing_settings,
|
|
||||||
settings={
|
|
||||||
DASHBOARD_VIEWS_VISIBLE_IDS_KEY: [self.sidebar_only_view.id],
|
|
||||||
SIDEBAR_VIEWS_VISIBLE_IDS_KEY: [self.dashboard_view.id],
|
|
||||||
"preserve": "value",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_visibility_defaults_are_seeded_and_existing_values_preserved(self) -> None:
|
|
||||||
UiSettings = self.apps.get_model("documents", "UiSettings")
|
|
||||||
|
|
||||||
seeded_settings = UiSettings.objects.get(
|
|
||||||
user_id=self.user_with_empty_settings_id,
|
|
||||||
).settings
|
|
||||||
self.assertCountEqual(
|
|
||||||
seeded_settings[DASHBOARD_VIEWS_VISIBLE_IDS_KEY],
|
|
||||||
[self.dashboard_view.id],
|
|
||||||
)
|
|
||||||
self.assertCountEqual(
|
|
||||||
seeded_settings[SIDEBAR_VIEWS_VISIBLE_IDS_KEY],
|
|
||||||
[self.dashboard_view.id, self.sidebar_only_view.id],
|
|
||||||
)
|
|
||||||
|
|
||||||
existing_settings = UiSettings.objects.get(
|
|
||||||
user_id=self.user_with_existing_settings_id,
|
|
||||||
).settings
|
|
||||||
self.assertEqual(
|
|
||||||
existing_settings[DASHBOARD_VIEWS_VISIBLE_IDS_KEY],
|
|
||||||
[self.sidebar_only_view.id],
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
existing_settings[SIDEBAR_VIEWS_VISIBLE_IDS_KEY],
|
|
||||||
[self.dashboard_view.id],
|
|
||||||
)
|
|
||||||
self.assertEqual(existing_settings["preserve"], "value")
|
|
||||||
|
|
||||||
created_settings = UiSettings.objects.get(
|
|
||||||
user_id=self.user_with_owned_views_id,
|
|
||||||
).settings
|
|
||||||
self.assertCountEqual(
|
|
||||||
created_settings[DASHBOARD_VIEWS_VISIBLE_IDS_KEY],
|
|
||||||
[self.other_owner_visible_view.id],
|
|
||||||
)
|
|
||||||
self.assertCountEqual(
|
|
||||||
created_settings[SIDEBAR_VIEWS_VISIBLE_IDS_KEY],
|
|
||||||
[self.other_owner_visible_view.id],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestReverseMigrateSavedViewVisibilityFromUiSettings(TestMigrations):
|
|
||||||
migrate_from = "0012_savedview_visibility_to_ui_settings"
|
|
||||||
migrate_to = "0011_optimize_integer_field_sizes"
|
|
||||||
|
|
||||||
def setUpBeforeMigration(self, apps) -> None:
|
|
||||||
User = apps.get_model("auth", "User")
|
|
||||||
SavedView = apps.get_model("documents", "SavedView")
|
|
||||||
UiSettings = apps.get_model("documents", "UiSettings")
|
|
||||||
|
|
||||||
user1 = User.objects.create(username="user1")
|
|
||||||
user2 = User.objects.create(username="user2")
|
|
||||||
user3 = User.objects.create(username="user3")
|
|
||||||
|
|
||||||
self.view1 = SavedView.objects.create(
|
|
||||||
owner=user1,
|
|
||||||
name="view-1",
|
|
||||||
sort_field="created",
|
|
||||||
)
|
|
||||||
self.view2 = SavedView.objects.create(
|
|
||||||
owner=user1,
|
|
||||||
name="view-2",
|
|
||||||
sort_field="created",
|
|
||||||
)
|
|
||||||
self.view3 = SavedView.objects.create(
|
|
||||||
owner=user1,
|
|
||||||
name="view-3",
|
|
||||||
sort_field="created",
|
|
||||||
)
|
|
||||||
self.view4 = SavedView.objects.create(
|
|
||||||
owner=user2,
|
|
||||||
name="view-4",
|
|
||||||
sort_field="created",
|
|
||||||
)
|
|
||||||
|
|
||||||
UiSettings.objects.create(
|
|
||||||
user=user1,
|
|
||||||
settings={
|
|
||||||
DASHBOARD_VIEWS_VISIBLE_IDS_KEY: [self.view1.id],
|
|
||||||
SIDEBAR_VIEWS_VISIBLE_IDS_KEY: [self.view2.id],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
UiSettings.objects.create(
|
|
||||||
user=user2,
|
|
||||||
settings={
|
|
||||||
DASHBOARD_VIEWS_VISIBLE_IDS_KEY: [
|
|
||||||
self.view2.id,
|
|
||||||
self.view3.id,
|
|
||||||
self.view4.id,
|
|
||||||
],
|
|
||||||
SIDEBAR_VIEWS_VISIBLE_IDS_KEY: [self.view4.id],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
UiSettings.objects.create(user=user3, settings={})
|
|
||||||
|
|
||||||
def test_visibility_fields_restored_from_owner_visibility(self) -> None:
|
|
||||||
SavedView = self.apps.get_model("documents", "SavedView")
|
|
||||||
|
|
||||||
restored_view1 = SavedView.objects.get(pk=self.view1.id)
|
|
||||||
restored_view2 = SavedView.objects.get(pk=self.view2.id)
|
|
||||||
restored_view3 = SavedView.objects.get(pk=self.view3.id)
|
|
||||||
restored_view4 = SavedView.objects.get(pk=self.view4.id)
|
|
||||||
|
|
||||||
self.assertTrue(restored_view1.show_on_dashboard)
|
|
||||||
self.assertFalse(restored_view2.show_on_dashboard)
|
|
||||||
self.assertFalse(restored_view3.show_on_dashboard)
|
|
||||||
self.assertTrue(restored_view4.show_on_dashboard)
|
|
||||||
|
|
||||||
self.assertFalse(restored_view1.show_in_sidebar)
|
|
||||||
self.assertTrue(restored_view2.show_in_sidebar)
|
|
||||||
self.assertFalse(restored_view3.show_in_sidebar)
|
|
||||||
self.assertTrue(restored_view4.show_in_sidebar)
|
|
||||||
@@ -1660,21 +1660,24 @@ class LogViewSet(ViewSet):
|
|||||||
return Response(existing_logs)
|
return Response(existing_logs)
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(**generate_object_with_permissions_schema(SavedViewSerializer))
|
class SavedViewViewSet(ModelViewSet, PassUserMixin):
|
||||||
class SavedViewViewSet(BulkPermissionMixin, PassUserMixin, ModelViewSet):
|
|
||||||
model = SavedView
|
model = SavedView
|
||||||
|
|
||||||
queryset = SavedView.objects.select_related("owner").prefetch_related(
|
queryset = SavedView.objects.all()
|
||||||
"filter_rules",
|
|
||||||
)
|
|
||||||
serializer_class = SavedViewSerializer
|
serializer_class = SavedViewSerializer
|
||||||
pagination_class = StandardPagination
|
pagination_class = StandardPagination
|
||||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||||
filter_backends = (
|
|
||||||
OrderingFilter,
|
def get_queryset(self):
|
||||||
ObjectOwnedOrGrantedPermissionsFilter,
|
user = self.request.user
|
||||||
)
|
return (
|
||||||
ordering_fields = ("name",)
|
SavedView.objects.filter(owner=user)
|
||||||
|
.select_related("owner")
|
||||||
|
.prefetch_related("filter_rules")
|
||||||
|
)
|
||||||
|
|
||||||
|
def perform_create(self, serializer) -> None:
|
||||||
|
serializer.save(owner=self.request.user)
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(
|
@extend_schema_view(
|
||||||
@@ -2198,11 +2201,7 @@ class GlobalSearchView(PassUserMixin):
|
|||||||
)
|
)
|
||||||
docs = docs[:OBJECT_LIMIT]
|
docs = docs[:OBJECT_LIMIT]
|
||||||
saved_views = (
|
saved_views = (
|
||||||
get_objects_for_user_owner_aware(
|
SavedView.objects.filter(owner=request.user, name__icontains=query)
|
||||||
request.user,
|
|
||||||
"view_savedview",
|
|
||||||
SavedView,
|
|
||||||
).filter(name__icontains=query)
|
|
||||||
if request.user.has_perm("documents.view_savedview")
|
if request.user.has_perm("documents.view_savedview")
|
||||||
else []
|
else []
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -374,10 +374,10 @@ REST_FRAMEWORK = {
|
|||||||
"rest_framework.authentication.SessionAuthentication",
|
"rest_framework.authentication.SessionAuthentication",
|
||||||
],
|
],
|
||||||
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.AcceptHeaderVersioning",
|
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.AcceptHeaderVersioning",
|
||||||
"DEFAULT_VERSION": "10", # match src-ui/src/environments/environment.prod.ts
|
"DEFAULT_VERSION": "9", # match src-ui/src/environments/environment.prod.ts
|
||||||
# Make sure these are ordered and that the most recent version appears
|
# Make sure these are ordered and that the most recent version appears
|
||||||
# last. See api.md#api-versioning when adding new versions.
|
# last. See api.md#api-versioning when adding new versions.
|
||||||
"ALLOWED_VERSIONS": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
|
"ALLOWED_VERSIONS": ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
|
||||||
# DRF Spectacular default schema
|
# DRF Spectacular default schema
|
||||||
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class Migration(migrations.Migration):
|
|||||||
name="stop_processing",
|
name="stop_processing",
|
||||||
field=models.BooleanField(
|
field=models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
help_text="If True, no further rules will be processed after this one if any document is consumed.",
|
help_text="If True, no further rules will be processed after this one if any document is queued.",
|
||||||
verbose_name="Stop processing further rules",
|
verbose_name="Stop processing further rules",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user