From edb06c290b914c876b5ab67b8c0e24a4425c0ead Mon Sep 17 00:00:00 2001 From: Bram Date: Tue, 19 May 2026 11:34:29 +0200 Subject: [PATCH 1/2] feat Show matching restricted view filters and allow setting default values (#5149) --- .../contrib/database/api/rows/views.py | 41 +- ...show_matching_restricted_view_filters.json | 9 + .../assets/scss/components/all.scss | 1 + .../restricted_view_filter_context.scss | 95 ++++ .../views/RestrictedViewFilterContext.vue | 454 ++++++++++++++++++ .../baserow_enterprise/locales/en.json | 8 + .../baserow_enterprise/viewOwnershipTypes.js | 14 + .../baserow_premium/api/row_comments/views.py | 27 +- .../components/fields/multiple_select.scss | 4 + .../FieldMultipleSelectOptionsSubForm.vue | 7 + .../components/field/FieldNumberSubForm.vue | 7 + .../field/FieldSingleSelectOptionsSubForm.vue | 7 + .../components/field/FieldTextSubForm.vue | 7 + .../row/RowEditFieldMultipleSelect.vue | 6 +- .../row/RowEditFieldSingleSelect.vue | 1 + .../database/components/table/Table.vue | 1 + .../components/view/DefaultValuesModal.vue | 5 +- .../database/components/view/ViewContext.vue | 11 + .../view/ViewFieldConditionsForm.vue | 1 + .../database/components/view/ViewFilter.vue | 5 + .../components/view/ViewFilterForm.vue | 25 +- web-frontend/modules/database/locales/en.json | 1 + .../modules/database/mixins/fieldSubForm.js | 9 + .../modules/database/viewOwnershipTypes.js | 13 + .../integrations/LocalBaserowAdhocHeader.vue | 1 + .../__snapshots__/viewFilterForm.spec.js.snap | 4 + 26 files changed, 749 insertions(+), 15 deletions(-) create mode 100644 changelog/entries/unreleased/feature/4926_show_matching_restricted_view_filters.json create mode 100644 enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/restricted_view_filter_context.scss create mode 100644 enterprise/web-frontend/modules/baserow_enterprise/components/views/RestrictedViewFilterContext.vue diff --git a/backend/src/baserow/contrib/database/api/rows/views.py b/backend/src/baserow/contrib/database/api/rows/views.py index 7a1821a173..bad17ed884 100644 --- a/backend/src/baserow/contrib/database/api/rows/views.py +++ b/backend/src/baserow/contrib/database/api/rows/views.py @@ -558,7 +558,11 @@ def get(self, request, table_id, query_params): ), 401: get_error_schema(["ERROR_NO_PERMISSION_TO_TABLE"]), 404: get_error_schema( - ["ERROR_TABLE_DOES_NOT_EXIST", "ERROR_ROW_DOES_NOT_EXIST"] + [ + "ERROR_TABLE_DOES_NOT_EXIST", + "ERROR_ROW_DOES_NOT_EXIST", + "ERROR_VIEW_DOES_NOT_EXIST", + ] ), }, ) @@ -821,7 +825,11 @@ class RowView(APIView): ), 401: get_error_schema(["ERROR_NO_PERMISSION_TO_TABLE"]), 404: get_error_schema( - ["ERROR_TABLE_DOES_NOT_EXIST", "ERROR_ROW_DOES_NOT_EXIST"] + [ + "ERROR_TABLE_DOES_NOT_EXIST", + "ERROR_ROW_DOES_NOT_EXIST", + "ERROR_VIEW_DOES_NOT_EXIST", + ] ), }, ) @@ -830,6 +838,7 @@ class RowView(APIView): UserNotInWorkspace: ERROR_USER_NOT_IN_GROUP, TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, RowDoesNotExist: ERROR_ROW_DOES_NOT_EXIST, + ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST, NoPermissionToTable: ERROR_NO_PERMISSION_TO_TABLE, TokenCannotIncludeRowMetadata: ERROR_CANNOT_INCLUDE_ROW_METADATA, FieldDataConstraintException: ERROR_FIELD_DATA_CONSTRAINT, @@ -956,7 +965,11 @@ def get(self, request, table_id, row_id, metadata, query_params: dict): ), 401: get_error_schema(["ERROR_NO_PERMISSION_TO_TABLE"]), 404: get_error_schema( - ["ERROR_TABLE_DOES_NOT_EXIST", "ERROR_ROW_DOES_NOT_EXIST"] + [ + "ERROR_TABLE_DOES_NOT_EXIST", + "ERROR_ROW_DOES_NOT_EXIST", + "ERROR_VIEW_DOES_NOT_EXIST", + ] ), }, ) @@ -965,6 +978,7 @@ def get(self, request, table_id, row_id, metadata, query_params: dict): UserNotInWorkspace: ERROR_USER_NOT_IN_GROUP, TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, RowDoesNotExist: ERROR_ROW_DOES_NOT_EXIST, + ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST, NoPermissionToTable: ERROR_NO_PERMISSION_TO_TABLE, DeadlockException: ERROR_DATABASE_DEADLOCK, FieldDataConstraintException: ERROR_FIELD_DATA_CONSTRAINT, @@ -1088,7 +1102,11 @@ def patch( ["ERROR_USER_NOT_IN_GROUP", "ERROR_CANNOT_DELETE_ALREADY_DELETED_ITEM"] ), 404: get_error_schema( - ["ERROR_TABLE_DOES_NOT_EXIST", "ERROR_ROW_DOES_NOT_EXIST"] + [ + "ERROR_TABLE_DOES_NOT_EXIST", + "ERROR_ROW_DOES_NOT_EXIST", + "ERROR_VIEW_DOES_NOT_EXIST", + ] ), }, ) @@ -1097,6 +1115,7 @@ def patch( UserNotInWorkspace: ERROR_USER_NOT_IN_GROUP, TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, RowDoesNotExist: ERROR_ROW_DOES_NOT_EXIST, + ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST, NoPermissionToTable: ERROR_NO_PERMISSION_TO_TABLE, CannotDeleteAlreadyDeletedItem: ERROR_CANNOT_DELETE_ALREADY_DELETED_ITEM, CannotDeleteRowsInTable: ERROR_CANNOT_DELETE_ROWS_IN_TABLE, @@ -1337,7 +1356,11 @@ class BatchRowsView(APIView): ), 401: get_error_schema(["ERROR_NO_PERMISSION_TO_TABLE"]), 404: get_error_schema( - ["ERROR_TABLE_DOES_NOT_EXIST", "ERROR_ROW_DOES_NOT_EXIST"] + [ + "ERROR_TABLE_DOES_NOT_EXIST", + "ERROR_ROW_DOES_NOT_EXIST", + "ERROR_VIEW_DOES_NOT_EXIST", + ] ), }, ) @@ -1347,6 +1370,7 @@ class BatchRowsView(APIView): TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, RowDoesNotExist: ERROR_ROW_DOES_NOT_EXIST, RowIdsNotUnique: ERROR_ROW_IDS_NOT_UNIQUE, + ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST, NoPermissionToTable: ERROR_NO_PERMISSION_TO_TABLE, CannotCreateRowsInTable: ERROR_CANNOT_CREATE_ROWS_IN_TABLE, DeadlockException: ERROR_DATABASE_DEADLOCK, @@ -1501,7 +1525,11 @@ def post(self, request: Request, table_id: int, query_params) -> Response: ), 401: get_error_schema(["ERROR_NO_PERMISSION_TO_TABLE"]), 404: get_error_schema( - ["ERROR_TABLE_DOES_NOT_EXIST", "ERROR_ROW_DOES_NOT_EXIST"] + [ + "ERROR_TABLE_DOES_NOT_EXIST", + "ERROR_ROW_DOES_NOT_EXIST", + "ERROR_VIEW_DOES_NOT_EXIST", + ] ), }, ) @@ -1511,6 +1539,7 @@ def post(self, request: Request, table_id: int, query_params) -> Response: TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, RowDoesNotExist: ERROR_ROW_DOES_NOT_EXIST, RowIdsNotUnique: ERROR_ROW_IDS_NOT_UNIQUE, + ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST, NoPermissionToTable: ERROR_NO_PERMISSION_TO_TABLE, DeadlockException: ERROR_DATABASE_DEADLOCK, FieldDataConstraintException: ERROR_FIELD_DATA_CONSTRAINT, diff --git a/changelog/entries/unreleased/feature/4926_show_matching_restricted_view_filters.json b/changelog/entries/unreleased/feature/4926_show_matching_restricted_view_filters.json new file mode 100644 index 0000000000..84b5e180cb --- /dev/null +++ b/changelog/entries/unreleased/feature/4926_show_matching_restricted_view_filters.json @@ -0,0 +1,9 @@ +{ + "type": "feature", + "message": "Show matching restricted view filters and allow setting default values", + "issue_origin": "github", + "issue_number": 4926, + "domain": "database", + "bullet_points": [], + "created_at": "2026-04-08" +} diff --git a/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/all.scss b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/all.scss index 533258413e..6239ef9ae8 100644 --- a/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/all.scss +++ b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/all.scss @@ -25,3 +25,4 @@ @import 'assistant_onboarding'; @import 'date_dependency'; @import 'data_scanner'; +@import 'restricted_view_filter_context'; diff --git a/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/restricted_view_filter_context.scss b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/restricted_view_filter_context.scss new file mode 100644 index 0000000000..cf5d3c71a8 --- /dev/null +++ b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/restricted_view_filter_context.scss @@ -0,0 +1,95 @@ +.restricted-view-filter-context { + .expandable__content--card { + padding: 0; + } +} + +.restricted-view-filter-context__head { + display: flex; + align-items: flex-start; + gap: 10px; +} + +.restricted-view-filter-context__icon { + flex: 0 0 20px; + font-size: 16px; + position: relative; + top: 1px; + + &--warning { + color: $palette-yellow-800; + } + + &--success { + color: $palette-green-700; + } +} + +.restricted-view-filter-context__text { + flex: 1 1 auto; + min-width: 0; +} + +.restricted-view-filter-context__title { + font-weight: 500; + font-size: 13px; + line-height: 20px; + margin-bottom: 4px; +} + +.restricted-view-filter-context__subtitle { + font-size: 12px; + color: $palette-neutral-900; + margin-top: 2px; + line-height: 16px; +} + +.restricted-view-filter-context__toggle { + flex: 0 0 auto; + font-size: 14px; + color: $color-neutral-500; + position: relative; + top: 3px; +} + +.restricted-view-filter-context__body { + padding: 20px 16px 20px 46px; +} + +.restricted-view-filter-context__field { + display: flex; + align-items: center; + width: 100%; + + &:not(:last-child) { + margin-bottom: 24px; + } +} + +.restricted-view-filter-context__field-label { + @extend %ellipsis; + + width: 110px; + flex: 0 0 110px; + margin-right: 20px; + font-size: 13px; + font-weight: 500; + line-height: 20px; + color: $palette-neutral-1200; +} + +.restricted-view-filter-context__field-icon { + width: 16px; + color: $palette-neutral-600; + font-size: 14px; +} + +.restricted-view-filter-context__field-input { + width: 100%; +} + +.restricted-view-filter-context__remove { + display: inline-block; + margin-top: 4px; + font-size: 12px; +} diff --git a/enterprise/web-frontend/modules/baserow_enterprise/components/views/RestrictedViewFilterContext.vue b/enterprise/web-frontend/modules/baserow_enterprise/components/views/RestrictedViewFilterContext.vue new file mode 100644 index 0000000000..f3724cc93b --- /dev/null +++ b/enterprise/web-frontend/modules/baserow_enterprise/components/views/RestrictedViewFilterContext.vue @@ -0,0 +1,454 @@ + + + diff --git a/enterprise/web-frontend/modules/baserow_enterprise/locales/en.json b/enterprise/web-frontend/modules/baserow_enterprise/locales/en.json index 958d50c02d..7726f6fce8 100644 --- a/enterprise/web-frontend/modules/baserow_enterprise/locales/en.json +++ b/enterprise/web-frontend/modules/baserow_enterprise/locales/en.json @@ -801,6 +801,14 @@ "restricted": "Restricted", "restrictedDescription": "Editors and lower can only see the filtered data and visible fields. It's possible to manage the members." }, + "restrictedViewFilterContext": { + "matchTitle": "Default view values match the filters", + "matchSubtitle": "Editors will see the rows they create.", + "mismatchTitle": "Default values don't match the filters", + "mismatchSubtitle": "Editors will be able to create rows but won't see them.", + "removeDefaultValue": "Remove default value", + "removeDefaultValueHelper": "This field has a default value but no filter. You can remove it here, or manage all default values by clicking the three dots next to the view and then on \"Default row values\"." + }, "databaseStep": { "ai": "Kuma AI" }, diff --git a/enterprise/web-frontend/modules/baserow_enterprise/viewOwnershipTypes.js b/enterprise/web-frontend/modules/baserow_enterprise/viewOwnershipTypes.js index 3a3a67bfe1..dcc9b77f93 100644 --- a/enterprise/web-frontend/modules/baserow_enterprise/viewOwnershipTypes.js +++ b/enterprise/web-frontend/modules/baserow_enterprise/viewOwnershipTypes.js @@ -3,6 +3,7 @@ import EnterpriseFeatures from '@baserow_enterprise/features' import PaidFeaturesModal from '@baserow_premium/components/PaidFeaturesModal' import { RBACPaidFeature } from '@baserow_enterprise/paidFeatures' import { FormViewType } from '@baserow/modules/database/viewTypes.js' +import RestrictedViewFilterContext from '@baserow_enterprise/components/views/RestrictedViewFilterContext' export class RestrictedViewOwnershipType extends ViewOwnershipType { static getType() { @@ -83,6 +84,19 @@ export class RestrictedViewOwnershipType extends ViewOwnershipType { return !canListFields } + getFilterContextComponent(view, database) { + if ( + !this.app.$hasPermission( + 'database.table.view.update_default_values', + view, + database.workspace.id + ) + ) { + return null + } + return RestrictedViewFilterContext + } + _canUpdateFieldOptions(view, database) { return this.app.$hasPermission( 'database.table.view.update_field_options', diff --git a/premium/backend/src/baserow_premium/api/row_comments/views.py b/premium/backend/src/baserow_premium/api/row_comments/views.py index 01da3c0ef8..c903e240b7 100755 --- a/premium/backend/src/baserow_premium/api/row_comments/views.py +++ b/premium/backend/src/baserow_premium/api/row_comments/views.py @@ -18,8 +18,10 @@ from baserow.api.serializers import get_example_pagination_serializer_class from baserow.contrib.database.api.rows.errors import ERROR_ROW_DOES_NOT_EXIST from baserow.contrib.database.api.tables.errors import ERROR_TABLE_DOES_NOT_EXIST +from baserow.contrib.database.api.views.errors import ERROR_VIEW_DOES_NOT_EXIST from baserow.contrib.database.rows.exceptions import RowDoesNotExist from baserow.contrib.database.table.exceptions import TableDoesNotExist +from baserow.contrib.database.views.exceptions import ViewDoesNotExist from baserow.core.action.registries import action_type_registry from baserow.core.exceptions import UserNotInWorkspace from baserow_premium.api.row_comments.errors import ( @@ -117,6 +119,7 @@ class RowCommentsView(APIView): [ "ERROR_TABLE_DOES_NOT_EXIST", "ERROR_ROW_DOES_NOT_EXIST", + "ERROR_VIEW_DOES_NOT_EXIST", ] ), }, @@ -125,6 +128,7 @@ class RowCommentsView(APIView): { TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, RowDoesNotExist: ERROR_ROW_DOES_NOT_EXIST, + ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST, UserNotInWorkspace: ERROR_USER_NOT_IN_GROUP, } ) @@ -176,7 +180,11 @@ def get(self, request, table_id, row_id, query_params): ["ERROR_USER_NOT_IN_GROUP", "ERROR_INVALID_COMMENT_MENTION"] ), 404: get_error_schema( - ["ERROR_TABLE_DOES_NOT_EXIST", "ERROR_ROW_DOES_NOT_EXIST"] + [ + "ERROR_TABLE_DOES_NOT_EXIST", + "ERROR_ROW_DOES_NOT_EXIST", + "ERROR_VIEW_DOES_NOT_EXIST", + ] ), }, ) @@ -184,6 +192,7 @@ def get(self, request, table_id, row_id, query_params): { TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, RowDoesNotExist: ERROR_ROW_DOES_NOT_EXIST, + ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST, UserNotInWorkspace: ERROR_USER_NOT_IN_GROUP, InvalidRowCommentMentionException: ERROR_INVALID_COMMENT_MENTION, } @@ -233,7 +242,11 @@ class RowCommentView(APIView): ), 401: get_error_schema(["ERROR_NO_PERMISSION_TO_TABLE"]), 404: get_error_schema( - ["ERROR_TABLE_DOES_NOT_EXIST", "ERROR_ROW_COMMENT_DOES_NOT_EXIST"] + [ + "ERROR_TABLE_DOES_NOT_EXIST", + "ERROR_ROW_COMMENT_DOES_NOT_EXIST", + "ERROR_VIEW_DOES_NOT_EXIST", + ] ), }, ) @@ -241,6 +254,7 @@ class RowCommentView(APIView): { TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, RowCommentDoesNotExist: ERROR_ROW_COMMENT_DOES_NOT_EXIST, + ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST, UserNotInWorkspace: ERROR_USER_NOT_IN_GROUP, UserNotRowCommentAuthorException: ERROR_USER_NOT_COMMENT_AUTHOR, InvalidRowCommentMentionException: ERROR_INVALID_COMMENT_MENTION, @@ -285,7 +299,11 @@ def patch(self, request, table_id, comment_id, data, query_params): ), 401: get_error_schema(["ERROR_NO_PERMISSION_TO_TABLE"]), 404: get_error_schema( - ["ERROR_TABLE_DOES_NOT_EXIST", "ERROR_ROW_COMMENT_DOES_NOT_EXIST"] + [ + "ERROR_TABLE_DOES_NOT_EXIST", + "ERROR_ROW_COMMENT_DOES_NOT_EXIST", + "ERROR_VIEW_DOES_NOT_EXIST", + ] ), }, ) @@ -293,6 +311,7 @@ def patch(self, request, table_id, comment_id, data, query_params): { TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, RowCommentDoesNotExist: ERROR_ROW_COMMENT_DOES_NOT_EXIST, + ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST, UserNotInWorkspace: ERROR_USER_NOT_IN_GROUP, UserNotRowCommentAuthorException: ERROR_USER_NOT_COMMENT_AUTHOR, } @@ -340,6 +359,7 @@ class RowCommentsNotificationModeView(APIView): [ "ERROR_TABLE_DOES_NOT_EXIST", "ERROR_ROW_DOES_NOT_EXIST", + "ERROR_VIEW_DOES_NOT_EXIST", ] ), }, @@ -348,6 +368,7 @@ class RowCommentsNotificationModeView(APIView): { TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, RowDoesNotExist: ERROR_ROW_DOES_NOT_EXIST, + ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST, UserNotInWorkspace: ERROR_USER_NOT_IN_GROUP, } ) diff --git a/web-frontend/modules/core/assets/scss/components/fields/multiple_select.scss b/web-frontend/modules/core/assets/scss/components/fields/multiple_select.scss index 19d97ed11f..696ae7de45 100644 --- a/web-frontend/modules/core/assets/scss/components/fields/multiple_select.scss +++ b/web-frontend/modules/core/assets/scss/components/fields/multiple_select.scss @@ -4,6 +4,10 @@ list-style: none; margin: 0 0 10px; padding: 0; + + &--empty { + margin-bottom: 0; + } } .field-multiple-select__item { diff --git a/web-frontend/modules/database/components/field/FieldMultipleSelectOptionsSubForm.vue b/web-frontend/modules/database/components/field/FieldMultipleSelectOptionsSubForm.vue index 36f9fa1656..1020f2d778 100644 --- a/web-frontend/modules/database/components/field/FieldMultipleSelectOptionsSubForm.vue +++ b/web-frontend/modules/database/components/field/FieldMultipleSelectOptionsSubForm.vue @@ -29,6 +29,13 @@ :value="option.id" /> +
+

+ {{ $t('fieldForm.defaultValueOverriddenByView') }} +

+
diff --git a/web-frontend/modules/database/components/field/FieldNumberSubForm.vue b/web-frontend/modules/database/components/field/FieldNumberSubForm.vue index b721ede520..e190091731 100644 --- a/web-frontend/modules/database/components/field/FieldNumberSubForm.vue +++ b/web-frontend/modules/database/components/field/FieldNumberSubForm.vue @@ -93,6 +93,13 @@ {{ $t('fieldForm.defaultValueDisabledByConstraint') }}

+
+

+ {{ $t('fieldForm.defaultValueOverriddenByView') }} +

+
diff --git a/web-frontend/modules/database/components/field/FieldSingleSelectOptionsSubForm.vue b/web-frontend/modules/database/components/field/FieldSingleSelectOptionsSubForm.vue index ec215b5d5f..6bfb3eb7b8 100644 --- a/web-frontend/modules/database/components/field/FieldSingleSelectOptionsSubForm.vue +++ b/web-frontend/modules/database/components/field/FieldSingleSelectOptionsSubForm.vue @@ -40,6 +40,13 @@ {{ $t('fieldForm.defaultValueDisabledByConstraint') }}

+
+

+ {{ $t('fieldForm.defaultValueOverriddenByView') }} +

+
diff --git a/web-frontend/modules/database/components/field/FieldTextSubForm.vue b/web-frontend/modules/database/components/field/FieldTextSubForm.vue index e190d751de..3f2e183328 100644 --- a/web-frontend/modules/database/components/field/FieldTextSubForm.vue +++ b/web-frontend/modules/database/components/field/FieldTextSubForm.vue @@ -17,6 +17,13 @@ {{ $t('fieldForm.defaultValueDisabledByConstraint') }}

+
+

+ {{ $t('fieldForm.defaultValueOverriddenByView') }} +

+
diff --git a/web-frontend/modules/database/components/row/RowEditFieldMultipleSelect.vue b/web-frontend/modules/database/components/row/RowEditFieldMultipleSelect.vue index 805b6cd859..8ba73384e6 100644 --- a/web-frontend/modules/database/components/row/RowEditFieldMultipleSelect.vue +++ b/web-frontend/modules/database/components/row/RowEditFieldMultipleSelect.vue @@ -1,6 +1,9 @@ diff --git a/web-frontend/modules/database/components/view/ViewFilter.vue b/web-frontend/modules/database/components/view/ViewFilter.vue index a105b021cf..662077e7a7 100644 --- a/web-frontend/modules/database/components/view/ViewFilter.vue +++ b/web-frontend/modules/database/components/view/ViewFilter.vue @@ -24,6 +24,7 @@ + > + +