mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-15 01:28:30 +00:00
Resolve "Support ad hoc filtering in grid view for editor roles and lower"
This commit is contained in:
parent
f453c84f11
commit
bbaca8e381
22 changed files with 1090 additions and 51 deletions
backend
src/baserow/contrib/database
tests/baserow/contrib/database/api/views/grid
changelog/entries/unreleased/feature
premium/web-frontend/modules/baserow_premium
web-frontend/modules/database
|
@ -71,6 +71,7 @@ from baserow.contrib.database.views.exceptions import (
|
|||
ViewFilterTypeDoesNotExist,
|
||||
ViewFilterTypeNotAllowedForField,
|
||||
)
|
||||
from baserow.contrib.database.views.filters import AdHocFilters
|
||||
from baserow.contrib.database.views.handler import ViewHandler
|
||||
from baserow.contrib.database.views.models import GridView
|
||||
from baserow.contrib.database.views.registries import (
|
||||
|
@ -167,6 +168,58 @@ class GridViewView(APIView):
|
|||
description="If provided only rows with data that matches the search "
|
||||
"query are going to be returned.",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="filters",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.STR,
|
||||
description=(
|
||||
"A JSON serialized string containing the filter tree to apply "
|
||||
"to this view. The filter tree is a nested structure containing "
|
||||
"the filters that need to be applied. \n\n"
|
||||
"An example of a valid filter tree is the following:"
|
||||
'`{"filter_type": "AND", "filters": [{"field": 1, "type": "equal", '
|
||||
'"value": "test"}]}`.\n\n'
|
||||
f"The following filters are available: "
|
||||
f'{", ".join(view_filter_type_registry.get_types())}.'
|
||||
"Please note that by passing the filters parameter the "
|
||||
"view filters saved for the view itself will be ignored."
|
||||
),
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="filter__{field}__{filter}",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.STR,
|
||||
description=(
|
||||
f"The rows can optionally be filtered by the same view filters "
|
||||
f"available for the views. Multiple filters can be provided if "
|
||||
f"they follow the same format. The field and filter variable "
|
||||
f"indicate how to filter and the value indicates where to filter "
|
||||
f"on.\n\n"
|
||||
"Please note that if the `filters` parameter is provided, "
|
||||
"this parameter will be ignored. \n\n"
|
||||
f"For example if you provide the following GET parameter "
|
||||
f"`filter__field_1__equal=test` then only rows where the value of "
|
||||
f"field_1 is equal to test are going to be returned.\n\n"
|
||||
f"The following filters are available: "
|
||||
f'{", ".join(view_filter_type_registry.get_types())}.'
|
||||
"Please note that by passing the filter parameters the "
|
||||
"view filters saved for the view itself will be ignored."
|
||||
),
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="filter_type",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.STR,
|
||||
description=(
|
||||
"`AND`: Indicates that the rows must match all the provided "
|
||||
"filters.\n"
|
||||
"`OR`: Indicates that the rows only have to match one of the "
|
||||
"filters.\n\n"
|
||||
"This works only if two or more filters are provided."
|
||||
"Please note that if the `filters` parameter is provided, "
|
||||
"this parameter will be ignored. \n\n"
|
||||
),
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="include_fields",
|
||||
location=OpenApiParameter.QUERY,
|
||||
|
@ -227,7 +280,15 @@ class GridViewView(APIView):
|
|||
},
|
||||
serializer_name="PaginationSerializerWithGridViewFieldOptions",
|
||||
),
|
||||
400: get_error_schema(["ERROR_USER_NOT_IN_GROUP"]),
|
||||
400: get_error_schema(
|
||||
[
|
||||
"ERROR_USER_NOT_IN_GROUP",
|
||||
"ERROR_FILTER_FIELD_NOT_FOUND",
|
||||
"ERROR_VIEW_FILTER_TYPE_DOES_NOT_EXIST",
|
||||
"ERROR_VIEW_FILTER_TYPE_UNSUPPORTED_FIELD",
|
||||
"ERROR_FILTERS_PARAM_VALIDATION_ERROR",
|
||||
]
|
||||
),
|
||||
404: get_error_schema(
|
||||
["ERROR_GRID_DOES_NOT_EXIST", "ERROR_FIELD_DOES_NOT_EXIST"]
|
||||
),
|
||||
|
@ -237,6 +298,9 @@ class GridViewView(APIView):
|
|||
{
|
||||
UserNotInWorkspace: ERROR_USER_NOT_IN_GROUP,
|
||||
ViewDoesNotExist: ERROR_GRID_DOES_NOT_EXIST,
|
||||
FilterFieldNotFound: ERROR_FILTER_FIELD_NOT_FOUND,
|
||||
ViewFilterTypeDoesNotExist: ERROR_VIEW_FILTER_TYPE_DOES_NOT_EXIST,
|
||||
ViewFilterTypeNotAllowedForField: ERROR_VIEW_FILTER_TYPE_UNSUPPORTED_FIELD,
|
||||
FieldDoesNotExist: ERROR_FIELD_DOES_NOT_EXIST,
|
||||
}
|
||||
)
|
||||
|
@ -254,6 +318,7 @@ class GridViewView(APIView):
|
|||
|
||||
include_fields = request.GET.get("include_fields")
|
||||
exclude_fields = request.GET.get("exclude_fields")
|
||||
adhoc_filters = AdHocFilters.from_request(request)
|
||||
|
||||
view_handler = ViewHandler()
|
||||
view = view_handler.get_view_as_user(
|
||||
|
@ -281,11 +346,15 @@ class GridViewView(APIView):
|
|||
model = view.table.get_model()
|
||||
queryset = view_handler.get_queryset(
|
||||
view,
|
||||
apply_filters=not adhoc_filters.has_any_filters,
|
||||
search=query_params.get("search"),
|
||||
search_mode=query_params.get("search_mode"),
|
||||
model=model,
|
||||
)
|
||||
|
||||
if adhoc_filters.has_any_filters:
|
||||
queryset = adhoc_filters.apply_to_queryset(model, queryset)
|
||||
|
||||
if "count" in request.GET:
|
||||
return Response({"count": queryset.count()})
|
||||
|
||||
|
@ -442,6 +511,58 @@ class GridViewFieldAggregationsView(APIView):
|
|||
"returned with the result."
|
||||
),
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="filters",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.STR,
|
||||
description=(
|
||||
"A JSON serialized string containing the filter tree to apply "
|
||||
"for the aggregation. The filter tree is a nested structure containing "
|
||||
"the filters that need to be applied. \n\n"
|
||||
"An example of a valid filter tree is the following:"
|
||||
'`{"filter_type": "AND", "filters": [{"field": 1, "type": "equal", '
|
||||
'"value": "test"}]}`.\n\n'
|
||||
f"The following filters are available: "
|
||||
f'{", ".join(view_filter_type_registry.get_types())}.'
|
||||
"Please note that by passing the filters parameter the "
|
||||
"view filters saved for the view itself will be ignored."
|
||||
),
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="filter__{field}__{filter}",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.STR,
|
||||
description=(
|
||||
f"The aggregation can optionally be filtered by the same view filters "
|
||||
f"available for the views. Multiple filters can be provided if "
|
||||
f"they follow the same format. The field and filter variable "
|
||||
f"indicate how to filter and the value indicates where to filter "
|
||||
f"on.\n\n"
|
||||
"Please note that if the `filters` parameter is provided, "
|
||||
"this parameter will be ignored. \n\n"
|
||||
f"For example if you provide the following GET parameter "
|
||||
f"`filter__field_1__equal=test` then only rows where the value of "
|
||||
f"field_1 is equal to test are going to be returned.\n\n"
|
||||
f"The following filters are available: "
|
||||
f'{", ".join(view_filter_type_registry.get_types())}.'
|
||||
"Please note that by passing the filter parameters the "
|
||||
"view filters saved for the view itself will be ignored."
|
||||
),
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="filter_type",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.STR,
|
||||
description=(
|
||||
"`AND`: Indicates that the aggregated rows must match all the provided "
|
||||
"filters.\n"
|
||||
"`OR`: Indicates that the aggregated rows only have to match one of the "
|
||||
"filters.\n\n"
|
||||
"This works only if two or more filters are provided."
|
||||
"Please note that if the `filters` parameter is provided, "
|
||||
"this parameter will be ignored. \n\n"
|
||||
),
|
||||
),
|
||||
SEARCH_MODE_API_PARAM,
|
||||
],
|
||||
tags=["Database table grid view"],
|
||||
|
@ -457,6 +578,10 @@ class GridViewFieldAggregationsView(APIView):
|
|||
400: get_error_schema(
|
||||
[
|
||||
"ERROR_USER_NOT_IN_GROUP",
|
||||
"ERROR_FILTER_FIELD_NOT_FOUND",
|
||||
"ERROR_VIEW_FILTER_TYPE_DOES_NOT_EXIST",
|
||||
"ERROR_VIEW_FILTER_TYPE_UNSUPPORTED_FIELD",
|
||||
"ERROR_FILTERS_PARAM_VALIDATION_ERROR",
|
||||
]
|
||||
),
|
||||
404: get_error_schema(
|
||||
|
@ -470,6 +595,9 @@ class GridViewFieldAggregationsView(APIView):
|
|||
{
|
||||
UserNotInWorkspace: ERROR_USER_NOT_IN_GROUP,
|
||||
ViewDoesNotExist: ERROR_GRID_DOES_NOT_EXIST,
|
||||
FilterFieldNotFound: ERROR_FILTER_FIELD_NOT_FOUND,
|
||||
ViewFilterTypeDoesNotExist: ERROR_VIEW_FILTER_TYPE_DOES_NOT_EXIST,
|
||||
ViewFilterTypeNotAllowedForField: ERROR_VIEW_FILTER_TYPE_UNSUPPORTED_FIELD,
|
||||
}
|
||||
)
|
||||
@allowed_includes("total")
|
||||
|
@ -482,6 +610,7 @@ class GridViewFieldAggregationsView(APIView):
|
|||
asked.
|
||||
"""
|
||||
|
||||
adhoc_filters = AdHocFilters.from_request(request)
|
||||
search = query_params.get("search")
|
||||
search_mode = query_params.get("search_mode")
|
||||
view_handler = ViewHandler()
|
||||
|
@ -491,7 +620,12 @@ class GridViewFieldAggregationsView(APIView):
|
|||
# Note: we can't optimize model by giving a model with just
|
||||
# the aggregated field because we may need other fields for filtering
|
||||
result = view_handler.get_view_field_aggregations(
|
||||
request.user, view, with_total=total, search=search, search_mode=search_mode
|
||||
request.user,
|
||||
view,
|
||||
with_total=total,
|
||||
search=search,
|
||||
search_mode=search_mode,
|
||||
adhoc_filters=adhoc_filters,
|
||||
)
|
||||
|
||||
# Decimal("NaN") can't be serialized, therefore we have to replace it
|
||||
|
@ -806,6 +940,8 @@ class PublicGridViewRowsView(APIView):
|
|||
400: get_error_schema(
|
||||
[
|
||||
"ERROR_USER_NOT_IN_GROUP",
|
||||
"ERROR_ORDER_BY_FIELD_NOT_FOUND",
|
||||
"ERROR_ORDER_BY_FIELD_NOT_POSSIBLE",
|
||||
"ERROR_FILTER_FIELD_NOT_FOUND",
|
||||
"ERROR_VIEW_FILTER_TYPE_DOES_NOT_EXIST",
|
||||
"ERROR_VIEW_FILTER_TYPE_UNSUPPORTED_FIELD",
|
||||
|
|
68
backend/src/baserow/contrib/database/views/filters.py
Normal file
68
backend/src/baserow/contrib/database/views/filters.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Literal, Optional
|
||||
|
||||
from baserow.contrib.database.api.views.serializers import validate_api_grouped_filters
|
||||
from baserow.contrib.database.fields.field_filters import (
|
||||
FILTER_TYPE_AND,
|
||||
FILTER_TYPE_OR,
|
||||
)
|
||||
from baserow.contrib.database.views.view_filter_groups import (
|
||||
construct_filter_builder_from_grouped_api_filters,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdHocFilters:
|
||||
"""Dataclass that can hold data for basic and grouped filters at the same time."""
|
||||
|
||||
# grouped filters
|
||||
api_filters: Optional[dict[str, any]] = None
|
||||
|
||||
# simple filters
|
||||
filter_type: Literal["OR", "AND"] = "OR"
|
||||
filter_object: Optional[dict] = None
|
||||
|
||||
@classmethod
|
||||
def from_request(cls, request):
|
||||
filter_type = (
|
||||
FILTER_TYPE_OR
|
||||
if request.GET.get("filter_type", "AND").upper() == "OR"
|
||||
else FILTER_TYPE_AND
|
||||
)
|
||||
filter_object = {key: request.GET.getlist(key) for key in request.GET.keys()}
|
||||
api_filters = None
|
||||
if (filters := filter_object.get("filters", None)) and len(filters) > 0:
|
||||
api_filters = validate_api_grouped_filters(filters[0])
|
||||
|
||||
return AdHocFilters(
|
||||
api_filters=api_filters,
|
||||
filter_type=filter_type,
|
||||
filter_object=filter_object,
|
||||
)
|
||||
|
||||
@property
|
||||
def has_simple_filters(self):
|
||||
return (
|
||||
any(param for param in self.filter_object if param.startswith("filter__"))
|
||||
if self.filter_object
|
||||
else False
|
||||
)
|
||||
|
||||
@property
|
||||
def has_any_filters(self):
|
||||
return self.api_filters or self.has_simple_filters
|
||||
|
||||
def apply_to_queryset(self, model, queryset):
|
||||
if self.api_filters and len(self.api_filters):
|
||||
filter_builder = construct_filter_builder_from_grouped_api_filters(
|
||||
self.api_filters,
|
||||
model,
|
||||
)
|
||||
return filter_builder.apply_to_queryset(queryset)
|
||||
|
||||
if self.filter_object:
|
||||
return queryset.filter_by_fields_object(
|
||||
self.filter_object, self.filter_type, None
|
||||
)
|
||||
|
||||
return queryset
|
|
@ -39,6 +39,7 @@ from baserow.contrib.database.rows.handler import RowHandler
|
|||
from baserow.contrib.database.search.handler import SearchModes
|
||||
from baserow.contrib.database.table.models import GeneratedTableModel, Table
|
||||
from baserow.contrib.database.views.exceptions import ViewOwnershipTypeDoesNotExist
|
||||
from baserow.contrib.database.views.filters import AdHocFilters
|
||||
from baserow.contrib.database.views.operations import (
|
||||
CreatePublicViewOperationType,
|
||||
CreateViewDecorationOperationType,
|
||||
|
@ -2703,6 +2704,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
|
|||
view: View,
|
||||
model: Union[GeneratedTableModel, None] = None,
|
||||
with_total: bool = False,
|
||||
adhoc_filters: Optional[AdHocFilters] = None,
|
||||
search: Optional[str] = None,
|
||||
search_mode: Optional[SearchModes] = None,
|
||||
) -> Dict[str, Any]:
|
||||
|
@ -2721,6 +2723,8 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
|
|||
automatically.
|
||||
:param with_total: Whether the total row count should be returned in the
|
||||
result.
|
||||
:param adhoc_filters: The filters that can be optionally applied
|
||||
instead of the view's own filters.
|
||||
:param search: the search string to considerate. If the search parameter is
|
||||
defined, we don't use the cache so we recompute aggregation on the fly.
|
||||
:param search_mode: the search mode that the search is using.
|
||||
|
@ -2737,6 +2741,9 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
|
|||
allow_if_template=True,
|
||||
)
|
||||
|
||||
if not adhoc_filters:
|
||||
adhoc_filters = AdHocFilters()
|
||||
|
||||
view_type = view_type_registry.get_by_model(view.specific_class)
|
||||
|
||||
# Check if view supports field aggregation
|
||||
|
@ -2750,7 +2757,9 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
|
|||
(
|
||||
values,
|
||||
need_computation,
|
||||
) = self._get_aggregations_to_compute(view, aggregations, no_cache=search)
|
||||
) = self._get_aggregations_to_compute(
|
||||
view, aggregations, no_cache=search or adhoc_filters.has_any_filters
|
||||
)
|
||||
|
||||
use_lock = hasattr(cache, "lock")
|
||||
used_lock = False
|
||||
|
@ -2781,11 +2790,12 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
|
|||
],
|
||||
model,
|
||||
with_total=with_total,
|
||||
adhoc_filters=adhoc_filters,
|
||||
search=search,
|
||||
search_mode=search_mode,
|
||||
)
|
||||
|
||||
if not search:
|
||||
if not search and not adhoc_filters.has_any_filters:
|
||||
to_cache = {}
|
||||
for key, value in db_result.items():
|
||||
# We don't cache total value
|
||||
|
@ -2818,6 +2828,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
|
|||
aggregations: Iterable[Tuple[django_models.Field, str]],
|
||||
model: Union[GeneratedTableModel, None] = None,
|
||||
with_total: bool = False,
|
||||
adhoc_filters: Optional[AdHocFilters] = None,
|
||||
search: Optional[str] = None,
|
||||
search_mode: Optional[SearchModes] = None,
|
||||
) -> Dict[str, Any]:
|
||||
|
@ -2834,6 +2845,8 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
|
|||
automatically.
|
||||
:param with_total: Whether the total row count should be returned in the
|
||||
result.
|
||||
:param adhoc_filters: The filters that can be optionally applied
|
||||
instead of the view's own filters.
|
||||
:param search: the search string to consider.
|
||||
:param search: the mode that the search is in.
|
||||
:raises FieldAggregationNotSupported: When the view type doesn't support
|
||||
|
@ -2854,6 +2867,9 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
|
|||
if model is None:
|
||||
model = view.table.get_model()
|
||||
|
||||
if adhoc_filters is None:
|
||||
adhoc_filters = AdHocFilters()
|
||||
|
||||
queryset = model.objects.all().enhance_by_fields()
|
||||
|
||||
view_type = view_type_registry.get_by_model(view.specific_class)
|
||||
|
@ -2866,7 +2882,12 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
|
|||
|
||||
# Apply filters and search to have accurate aggregations
|
||||
if view_type.can_filter:
|
||||
queryset = self.apply_filters(view, queryset)
|
||||
queryset = (
|
||||
adhoc_filters.apply_to_queryset(model, queryset)
|
||||
if adhoc_filters.has_any_filters
|
||||
else self.apply_filters(view, queryset)
|
||||
)
|
||||
|
||||
if search is not None:
|
||||
queryset = queryset.search_all_fields(search, search_mode=search_mode)
|
||||
|
||||
|
|
|
@ -1191,6 +1191,249 @@ def test_view_aggregations(api_client, data_fixture):
|
|||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_view_aggregations_no_adhoc_filtering_uses_view_filters(
|
||||
api_client, data_fixture
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
text_field = data_fixture.create_text_field(table=table, name="text_field")
|
||||
grid_view = data_fixture.create_grid_view(
|
||||
table=table, user=user, public=True, create_options=False
|
||||
)
|
||||
# this filter would filters out all rows
|
||||
equal_filter = data_fixture.create_view_filter(
|
||||
view=grid_view, field=text_field, type="equal", value="y"
|
||||
)
|
||||
RowHandler().create_row(
|
||||
user, table, values={"text_field": "a"}, user_field_names=True
|
||||
)
|
||||
RowHandler().create_row(
|
||||
user, table, values={"text_field": "b"}, user_field_names=True
|
||||
)
|
||||
view_handler = ViewHandler()
|
||||
view_handler.update_field_options(
|
||||
view=grid_view,
|
||||
field_options={
|
||||
text_field.id: {
|
||||
"aggregation_type": "unique_count",
|
||||
"aggregation_raw_type": "unique_count",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
url = reverse(
|
||||
"api:database:views:grid:field-aggregations",
|
||||
kwargs={"view_id": grid_view.id},
|
||||
)
|
||||
|
||||
# without ad hoc filters the view filter is applied
|
||||
response = api_client.get(
|
||||
url,
|
||||
**{"HTTP_AUTHORIZATION": f"JWT {token}"},
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert response_json == {text_field.db_column: 0}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_view_aggregations_adhoc_filtering_overrides_existing_filters(
|
||||
api_client, data_fixture
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
text_field = data_fixture.create_text_field(table=table, name="text_field")
|
||||
grid_view = data_fixture.create_grid_view(
|
||||
table=table, user=user, public=True, create_options=False
|
||||
)
|
||||
# in usual scenario this filter would filtered out all rows
|
||||
equal_filter = data_fixture.create_view_filter(
|
||||
view=grid_view, field=text_field, type="equal", value="y"
|
||||
)
|
||||
RowHandler().create_row(
|
||||
user, table, values={"text_field": "a"}, user_field_names=True
|
||||
)
|
||||
RowHandler().create_row(
|
||||
user, table, values={"text_field": "b"}, user_field_names=True
|
||||
)
|
||||
view_handler = ViewHandler()
|
||||
view_handler.update_field_options(
|
||||
view=grid_view,
|
||||
field_options={
|
||||
text_field.id: {
|
||||
"aggregation_type": "unique_count",
|
||||
"aggregation_raw_type": "unique_count",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
url = reverse(
|
||||
"api:database:views:grid:field-aggregations",
|
||||
kwargs={"view_id": grid_view.id},
|
||||
)
|
||||
|
||||
advanced_filters = {
|
||||
"filter_type": "AND",
|
||||
"filters": [
|
||||
{
|
||||
"field": text_field.id,
|
||||
"type": "equal",
|
||||
"value": "a",
|
||||
},
|
||||
],
|
||||
}
|
||||
get_params = [f"filters={json.dumps(advanced_filters)}"]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert response_json == {text_field.db_column: 1}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_view_aggregations_adhoc_filtering_advanced_filters_are_preferred_to_other_filter_query_params(
|
||||
api_client, data_fixture
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
text_field = data_fixture.create_text_field(table=table, name="text_field")
|
||||
grid_view = data_fixture.create_grid_view(
|
||||
table=table, user=user, public=True, create_options=False
|
||||
)
|
||||
RowHandler().create_row(
|
||||
user, table, values={"text_field": "a"}, user_field_names=True
|
||||
)
|
||||
RowHandler().create_row(
|
||||
user, table, values={"text_field": "b"}, user_field_names=True
|
||||
)
|
||||
view_handler = ViewHandler()
|
||||
view_handler.update_field_options(
|
||||
view=grid_view,
|
||||
field_options={
|
||||
text_field.id: {
|
||||
"aggregation_type": "unique_count",
|
||||
"aggregation_raw_type": "unique_count",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
url = reverse(
|
||||
"api:database:views:grid:field-aggregations",
|
||||
kwargs={"view_id": grid_view.id},
|
||||
)
|
||||
advanced_filters = {
|
||||
"filter_type": "OR",
|
||||
"filters": [
|
||||
{
|
||||
"field": text_field.id,
|
||||
"type": "equal",
|
||||
"value": "a",
|
||||
},
|
||||
{
|
||||
"field": text_field.id,
|
||||
"type": "equal",
|
||||
"value": "b",
|
||||
},
|
||||
],
|
||||
}
|
||||
get_params = [
|
||||
"filters=" + json.dumps(advanced_filters),
|
||||
f"filter__field_{text_field.id}__equal=z",
|
||||
f"filter_type=AND",
|
||||
]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert response_json == {text_field.db_column: 2}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_view_aggregations_adhoc_filtering_invalid_advanced_filters(
|
||||
api_client, data_fixture
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
text_field = data_fixture.create_text_field(table=table, name="text_field")
|
||||
grid_view = data_fixture.create_grid_view(
|
||||
table=table, user=user, public=True, create_options=False
|
||||
)
|
||||
view_handler = ViewHandler()
|
||||
view_handler.update_field_options(
|
||||
view=grid_view,
|
||||
field_options={
|
||||
text_field.id: {
|
||||
"aggregation_type": "unique_count",
|
||||
"aggregation_raw_type": "unique_count",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
RowHandler().create_row(
|
||||
user, table, values={"text_field": "a"}, user_field_names=True
|
||||
)
|
||||
|
||||
url = reverse(
|
||||
"api:database:views:grid:field-aggregations",
|
||||
kwargs={"view_id": grid_view.id},
|
||||
)
|
||||
|
||||
expected_errors = [
|
||||
(
|
||||
"invalid_json",
|
||||
{
|
||||
"error": "The provided filters are not valid JSON.",
|
||||
"code": "invalid_json",
|
||||
},
|
||||
),
|
||||
(
|
||||
json.dumps({"filter_type": "invalid"}),
|
||||
{
|
||||
"filter_type": [
|
||||
{
|
||||
"error": '"invalid" is not a valid choice.',
|
||||
"code": "invalid_choice",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
(
|
||||
json.dumps(
|
||||
{"filter_type": "OR", "filters": "invalid", "groups": "invalid"}
|
||||
),
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"error": 'Expected a list of items but got type "str".',
|
||||
"code": "not_a_list",
|
||||
}
|
||||
],
|
||||
"groups": {
|
||||
"non_field_errors": [
|
||||
{
|
||||
"error": 'Expected a list of items but got type "str".',
|
||||
"code": "not_a_list",
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
for filters, error_detail in expected_errors:
|
||||
get_params = [f"filters={filters}"]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response_json["error"] == "ERROR_FILTERS_PARAM_VALIDATION_ERROR"
|
||||
assert response_json["detail"] == error_detail
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_view_aggregations_cache_invalidation_with_dependant_fields(
|
||||
api_client, data_fixture
|
||||
|
@ -3193,3 +3436,421 @@ def test_list_rows_public_advanced_filters_are_preferred_to_other_filter_query_p
|
|||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert len(response_json["results"]) == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_grid_rows_adhoc_filtering_query_param_filter(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
text_field = data_fixture.create_text_field(table=table, name="normal")
|
||||
# hidden field should behave the same as normal one
|
||||
text_field_hidden = data_fixture.create_text_field(table=table, name="hidden")
|
||||
grid_view = data_fixture.create_grid_view(
|
||||
table=table, user=user, create_options=False
|
||||
)
|
||||
data_fixture.create_grid_view_field_option(grid_view, text_field, hidden=False)
|
||||
data_fixture.create_grid_view_field_option(
|
||||
grid_view, text_field_hidden, hidden=True
|
||||
)
|
||||
|
||||
first_row = RowHandler().create_row(
|
||||
user, table, values={"normal": "a", "hidden": "y"}, user_field_names=True
|
||||
)
|
||||
RowHandler().create_row(
|
||||
user, table, values={"normal": "b", "hidden": "z"}, user_field_names=True
|
||||
)
|
||||
|
||||
url = reverse("api:database:views:grid:list", kwargs={"view_id": grid_view.id})
|
||||
get_params = [f"filter__field_{text_field.id}__contains=a"]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert len(response_json["results"]) == 1
|
||||
assert response_json["results"][0]["id"] == first_row.id
|
||||
|
||||
url = reverse("api:database:views:grid:list", kwargs={"view_id": grid_view.id})
|
||||
get_params = [
|
||||
f"filter__field_{text_field.id}__contains=a",
|
||||
f"filter__field_{text_field.id}__contains=b",
|
||||
f"filter_type=OR",
|
||||
]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert len(response_json["results"]) == 2
|
||||
|
||||
url = reverse("api:database:views:grid:list", kwargs={"view_id": grid_view.id})
|
||||
get_params = [f"filter__field_{text_field_hidden.id}__contains=y"]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert len(response_json["results"]) == 1
|
||||
|
||||
url = reverse("api:database:views:grid:list", kwargs={"view_id": grid_view.id})
|
||||
get_params = [f"filter__field_{text_field.id}__random=y"]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response_json["error"] == "ERROR_VIEW_FILTER_TYPE_DOES_NOT_EXIST"
|
||||
|
||||
url = reverse("api:database:views:grid:list", kwargs={"view_id": grid_view.id})
|
||||
get_params = [f"filter__field_{text_field.id}__higher_than=1"]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response_json["error"] == "ERROR_VIEW_FILTER_TYPE_UNSUPPORTED_FIELD"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_grid_rows_adhoc_filtering_invalid_advanced_filters(
|
||||
api_client, data_fixture
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
text_field = data_fixture.create_text_field(table=table, name="text_field")
|
||||
grid_view = data_fixture.create_grid_view(
|
||||
table=table, user=user, public=True, create_options=False
|
||||
)
|
||||
data_fixture.create_grid_view_field_option(grid_view, text_field, hidden=False)
|
||||
|
||||
RowHandler().create_row(
|
||||
user, table, values={"text_field": "a"}, user_field_names=True
|
||||
)
|
||||
|
||||
url = reverse("api:database:views:grid:list", kwargs={"view_id": grid_view.id})
|
||||
|
||||
expected_errors = [
|
||||
(
|
||||
"invalid_json",
|
||||
{
|
||||
"error": "The provided filters are not valid JSON.",
|
||||
"code": "invalid_json",
|
||||
},
|
||||
),
|
||||
(
|
||||
json.dumps({"filter_type": "invalid"}),
|
||||
{
|
||||
"filter_type": [
|
||||
{
|
||||
"error": '"invalid" is not a valid choice.',
|
||||
"code": "invalid_choice",
|
||||
}
|
||||
]
|
||||
},
|
||||
),
|
||||
(
|
||||
json.dumps(
|
||||
{"filter_type": "OR", "filters": "invalid", "groups": "invalid"}
|
||||
),
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"error": 'Expected a list of items but got type "str".',
|
||||
"code": "not_a_list",
|
||||
}
|
||||
],
|
||||
"groups": {
|
||||
"non_field_errors": [
|
||||
{
|
||||
"error": 'Expected a list of items but got type "str".',
|
||||
"code": "not_a_list",
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
for filters, error_detail in expected_errors:
|
||||
get_params = [f"filters={filters}"]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response_json["error"] == "ERROR_FILTERS_PARAM_VALIDATION_ERROR"
|
||||
assert response_json["detail"] == error_detail
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_grid_rows_adhoc_filtering_advanced_filters_are_preferred_to_other_filter_query_params(
|
||||
api_client, data_fixture
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
text_field = data_fixture.create_text_field(table=table, name="text_field")
|
||||
grid_view = data_fixture.create_grid_view(
|
||||
table=table, user=user, public=True, create_options=False
|
||||
)
|
||||
data_fixture.create_grid_view_field_option(grid_view, text_field)
|
||||
|
||||
RowHandler().create_row(
|
||||
user, table, values={"text_field": "a"}, user_field_names=True
|
||||
)
|
||||
RowHandler().create_row(
|
||||
user, table, values={"text_field": "b"}, user_field_names=True
|
||||
)
|
||||
|
||||
url = reverse("api:database:views:grid:list", kwargs={"view_id": grid_view.id})
|
||||
advanced_filters = {
|
||||
"filter_type": "OR",
|
||||
"filters": [
|
||||
{
|
||||
"field": text_field.id,
|
||||
"type": "equal",
|
||||
"value": "a",
|
||||
},
|
||||
{
|
||||
"field": text_field.id,
|
||||
"type": "equal",
|
||||
"value": "b",
|
||||
},
|
||||
],
|
||||
}
|
||||
get_params = [
|
||||
"filters=" + json.dumps(advanced_filters),
|
||||
f"filter__field_{text_field.id}__equal=z",
|
||||
f"filter_type=AND",
|
||||
]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert len(response_json["results"]) == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_grid_rows_adhoc_filtering_overrides_existing_filters(
|
||||
api_client, data_fixture
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
text_field = data_fixture.create_text_field(table=table, name="text_field")
|
||||
grid_view = data_fixture.create_grid_view(
|
||||
table=table, user=user, public=True, create_options=False
|
||||
)
|
||||
# in usual scenario this filter would filtered out all rows
|
||||
equal_filter = data_fixture.create_view_filter(
|
||||
view=grid_view, field=text_field, type="equal", value="y"
|
||||
)
|
||||
RowHandler().create_row(
|
||||
user, table, values={"text_field": "a"}, user_field_names=True
|
||||
)
|
||||
RowHandler().create_row(
|
||||
user, table, values={"text_field": "b"}, user_field_names=True
|
||||
)
|
||||
|
||||
url = reverse("api:database:views:grid:list", kwargs={"view_id": grid_view.id})
|
||||
advanced_filters = {
|
||||
"filter_type": "OR",
|
||||
"filters": [
|
||||
{
|
||||
"field": text_field.id,
|
||||
"type": "equal",
|
||||
"value": "a",
|
||||
},
|
||||
{
|
||||
"field": text_field.id,
|
||||
"type": "equal",
|
||||
"value": "b",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
get_params = [
|
||||
"filters=" + json.dumps(advanced_filters),
|
||||
]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert len(response_json["results"]) == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_grid_rows_adhoc_filtering_advanced_filters(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
public_field = data_fixture.create_text_field(table=table, name="public")
|
||||
# hidden fields should behave like normal ones
|
||||
hidden_field = data_fixture.create_text_field(table=table, name="hidden")
|
||||
grid_view = data_fixture.create_grid_view(
|
||||
table=table, user=user, public=True, create_options=False
|
||||
)
|
||||
data_fixture.create_grid_view_field_option(grid_view, public_field, hidden=False)
|
||||
data_fixture.create_grid_view_field_option(grid_view, hidden_field, hidden=True)
|
||||
|
||||
first_row = RowHandler().create_row(
|
||||
user, table, values={"public": "a", "hidden": "y"}, user_field_names=True
|
||||
)
|
||||
RowHandler().create_row(
|
||||
user, table, values={"public": "b", "hidden": "z"}, user_field_names=True
|
||||
)
|
||||
|
||||
url = reverse("api:database:views:grid:list", kwargs={"view_id": grid_view.id})
|
||||
advanced_filters = {
|
||||
"filter_type": "AND",
|
||||
"filters": [
|
||||
{
|
||||
"field": public_field.id,
|
||||
"type": "contains",
|
||||
"value": "a",
|
||||
}
|
||||
],
|
||||
}
|
||||
get_params = ["filters=" + json.dumps(advanced_filters)]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert len(response_json["results"]) == 1
|
||||
assert response_json["results"][0]["id"] == first_row.id
|
||||
|
||||
advanced_filters = {
|
||||
"filter_type": "AND",
|
||||
"groups": [
|
||||
{
|
||||
"filter_type": "OR",
|
||||
"filters": [
|
||||
{
|
||||
"field": public_field.id,
|
||||
"type": "contains",
|
||||
"value": "a",
|
||||
},
|
||||
{
|
||||
"field": public_field.id,
|
||||
"type": "contains",
|
||||
"value": "b",
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
get_params = ["filters=" + json.dumps(advanced_filters)]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert len(response_json["results"]) == 2
|
||||
|
||||
# groups can be arbitrarily nested
|
||||
advanced_filters = {
|
||||
"filter_type": "AND",
|
||||
"groups": [
|
||||
{
|
||||
"filter_type": "AND",
|
||||
"filters": [
|
||||
{
|
||||
"field": public_field.id,
|
||||
"type": "contains",
|
||||
"value": "",
|
||||
},
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"filter_type": "OR",
|
||||
"filters": [
|
||||
{
|
||||
"field": public_field.id,
|
||||
"type": "contains",
|
||||
"value": "a",
|
||||
},
|
||||
{
|
||||
"field": public_field.id,
|
||||
"type": "contains",
|
||||
"value": "b",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
get_params = ["filters=" + json.dumps(advanced_filters)]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert len(response_json["results"]) == 2
|
||||
|
||||
advanced_filters = {
|
||||
"filter_type": "AND",
|
||||
"filters": [
|
||||
{
|
||||
"field": hidden_field.id,
|
||||
"type": "contains",
|
||||
"value": "y",
|
||||
}
|
||||
],
|
||||
}
|
||||
get_params = ["filters=" + json.dumps(advanced_filters)]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert len(response_json["results"]) == 1
|
||||
|
||||
advanced_filters = {
|
||||
"filter_type": "AND",
|
||||
"filters": [
|
||||
{
|
||||
"field": public_field.id,
|
||||
"type": "random",
|
||||
"value": "y",
|
||||
}
|
||||
],
|
||||
}
|
||||
get_params = ["filters=" + json.dumps(advanced_filters)]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response_json["error"] == "ERROR_VIEW_FILTER_TYPE_DOES_NOT_EXIST"
|
||||
|
||||
advanced_filters = {
|
||||
"filter_type": "AND",
|
||||
"filters": [
|
||||
{
|
||||
"field": public_field.id,
|
||||
"type": "higher_than",
|
||||
"value": "y",
|
||||
}
|
||||
],
|
||||
}
|
||||
get_params = ["filters=" + json.dumps(advanced_filters)]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response_json["error"] == "ERROR_VIEW_FILTER_TYPE_UNSUPPORTED_FIELD"
|
||||
|
||||
for filters in [
|
||||
"invalid_json",
|
||||
json.dumps({"filter_type": "invalid"}),
|
||||
json.dumps({"filter_type": "OR", "filters": "invalid"}),
|
||||
]:
|
||||
get_params = [f"filters={filters}"]
|
||||
response = api_client.get(
|
||||
f'{url}?{"&".join(get_params)}', HTTP_AUTHORIZATION=f"JWT {token}"
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response_json["error"] == "ERROR_FILTERS_PARAM_VALIDATION_ERROR"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "feature",
|
||||
"message": "Support ad hoc filtering in grid view for editor roles and lower",
|
||||
"issue_number": 2329,
|
||||
"bullet_points": [],
|
||||
"created_at": "2024-02-22"
|
||||
}
|
|
@ -49,6 +49,8 @@ export const state = () => ({
|
|||
draggingRow: null,
|
||||
draggingOriginalStackId: null,
|
||||
draggingOriginalBefore: null,
|
||||
// If true, ad hoc filtering is used instead of persistent one
|
||||
adhocFiltering: false,
|
||||
})
|
||||
|
||||
export const mutations = {
|
||||
|
@ -178,6 +180,9 @@ export const mutations = {
|
|||
const currentValue = row._.metadata[rowMetadataType]
|
||||
Vue.set(row._.metadata, rowMetadataType, updateFunction(currentValue))
|
||||
},
|
||||
SET_ADHOC_FILTERING(state, adhocFiltering) {
|
||||
state.adhocFiltering = adhocFiltering
|
||||
},
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
|
@ -194,8 +199,15 @@ export const actions = {
|
|||
*/
|
||||
async fetchInitial(
|
||||
{ dispatch, commit, getters, rootGetters },
|
||||
{ kanbanId, singleSelectFieldId, includeFieldOptions = true }
|
||||
{
|
||||
kanbanId,
|
||||
singleSelectFieldId,
|
||||
adhocFiltering,
|
||||
includeFieldOptions = true,
|
||||
}
|
||||
) {
|
||||
commit('SET_ADHOC_FILTERING', adhocFiltering)
|
||||
const view = rootGetters['view/get'](kanbanId)
|
||||
const { data } = await KanbanService(this.$client).fetchRows({
|
||||
kanbanId,
|
||||
limit: getters.getBufferRequestSize,
|
||||
|
@ -204,7 +216,7 @@ export const actions = {
|
|||
selectOptions: [],
|
||||
publicUrl: rootGetters['page/view/public/getIsPublic'],
|
||||
publicAuthToken: rootGetters['page/view/public/getAuthToken'],
|
||||
filters: getFilters(rootGetters, kanbanId),
|
||||
filters: getFilters(view, adhocFiltering),
|
||||
})
|
||||
Object.keys(data.rows).forEach((key) => {
|
||||
populateStack(data.rows[key], data)
|
||||
|
@ -226,6 +238,7 @@ export const actions = {
|
|||
{ selectOptionId }
|
||||
) {
|
||||
const stack = getters.getStack(selectOptionId)
|
||||
const view = rootGetters['view/get'](getters.getLastKanbanId)
|
||||
const { data } = await KanbanService(this.$client).fetchRows({
|
||||
kanbanId: getters.getLastKanbanId,
|
||||
limit: getters.getBufferRequestSize,
|
||||
|
@ -240,7 +253,7 @@ export const actions = {
|
|||
],
|
||||
publicUrl: rootGetters['page/view/public/getIsPublic'],
|
||||
publicAuthToken: rootGetters['page/view/public/getAuthToken'],
|
||||
filters: getFilters(rootGetters, getters.getLastKanbanId),
|
||||
filters: getFilters(view, getters.getAdhocFiltering),
|
||||
})
|
||||
const count = data.rows[selectOptionId].count
|
||||
const rows = data.rows[selectOptionId].results
|
||||
|
@ -1038,6 +1051,9 @@ export const getters = {
|
|||
}
|
||||
}
|
||||
},
|
||||
getAdhocFiltering(state) {
|
||||
return state.adhocFiltering
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -70,7 +70,9 @@ export class KanbanViewType extends PremiumViewType {
|
|||
return KanbanView
|
||||
}
|
||||
|
||||
async fetch({ store }, view, fields, storePrefix = '') {
|
||||
async fetch({ store }, database, view, fields, storePrefix = '') {
|
||||
const isPublic = store.getters[storePrefix + 'view/public/getIsPublic']
|
||||
const adhocFiltering = isPublic
|
||||
// If the single select field is `null` we can't fetch the initial data anyway,
|
||||
// we don't have to do anything. The KanbanView component will handle it by
|
||||
// showing a form to choose or create a single select field.
|
||||
|
@ -80,23 +82,28 @@ export class KanbanViewType extends PremiumViewType {
|
|||
await store.dispatch(storePrefix + 'view/kanban/fetchInitial', {
|
||||
kanbanId: view.id,
|
||||
singleSelectFieldId: view.single_select_field,
|
||||
adhocFiltering,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async refresh(
|
||||
{ store },
|
||||
database,
|
||||
view,
|
||||
fields,
|
||||
storePrefix = '',
|
||||
includeFieldOptions = false,
|
||||
sourceEvent = null
|
||||
) {
|
||||
const isPublic = store.getters[storePrefix + 'view/public/getIsPublic']
|
||||
const adhocFiltering = isPublic
|
||||
try {
|
||||
await store.dispatch(storePrefix + 'view/kanban/fetchInitial', {
|
||||
kanbanId: view.id,
|
||||
singleSelectFieldId: view.single_select_field,
|
||||
includeFieldOptions,
|
||||
adhocFiltering,
|
||||
})
|
||||
} catch (error) {
|
||||
if (
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
v-if="
|
||||
hasSelectedView &&
|
||||
view._.type.canFilter &&
|
||||
(readOnly ||
|
||||
(adhocFiltering ||
|
||||
$hasPermission(
|
||||
'database.table.view.create_filter',
|
||||
view,
|
||||
|
@ -91,8 +91,9 @@
|
|||
>
|
||||
<ViewFilter
|
||||
:view="view"
|
||||
:is-public-view="isPublic"
|
||||
:fields="fields"
|
||||
:read-only="readOnly"
|
||||
:read-only="adhocFiltering"
|
||||
:disable-filter="disableFilter"
|
||||
@changed="refresh()"
|
||||
></ViewFilter>
|
||||
|
@ -375,6 +376,25 @@ export default {
|
|||
)
|
||||
)
|
||||
},
|
||||
adhocFiltering() {
|
||||
if (this.readOnly) {
|
||||
return true
|
||||
}
|
||||
|
||||
return (
|
||||
this.view.type === 'grid' &&
|
||||
this.$hasPermission(
|
||||
'database.table.view.list_filter',
|
||||
this.view,
|
||||
this.database.workspace.id
|
||||
) &&
|
||||
!this.$hasPermission(
|
||||
'database.table.view.create_filter',
|
||||
this.view,
|
||||
this.database.workspace.id
|
||||
)
|
||||
)
|
||||
},
|
||||
...mapGetters({
|
||||
isPublic: 'page/view/public/getIsPublic',
|
||||
}),
|
||||
|
@ -457,6 +477,7 @@ export default {
|
|||
try {
|
||||
await type.refresh(
|
||||
{ store: this.$store },
|
||||
this.database,
|
||||
this.view,
|
||||
fieldsToRefresh,
|
||||
this.storePrefix,
|
||||
|
|
|
@ -146,6 +146,7 @@ export default {
|
|||
const type = this.$registry.get('view', view.type)
|
||||
await type.fetch(
|
||||
{ store: this.$store },
|
||||
this.database,
|
||||
view,
|
||||
this.fields,
|
||||
'template/'
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
ref="filter-value"
|
||||
:filter="filter"
|
||||
:view="view"
|
||||
:is-public-view="isPublicView"
|
||||
:fields="fields"
|
||||
:disabled="disableFilter"
|
||||
:read-only="readOnly"
|
||||
|
@ -112,6 +113,11 @@ export default {
|
|||
required: false,
|
||||
default: () => {},
|
||||
},
|
||||
isPublicView: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
disableFilter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
:ref="`condition-${filter.id}`"
|
||||
:filter="filter"
|
||||
:view="view"
|
||||
:is-public-view="isPublicView"
|
||||
:fields="fields"
|
||||
:disable-filter="disableFilter"
|
||||
:read-only="readOnly"
|
||||
|
@ -71,6 +72,7 @@
|
|||
:ref="`condition-${filter.id}`"
|
||||
:filter="filter"
|
||||
:view="view"
|
||||
:is-public-view="isPublicView"
|
||||
:fields="fields"
|
||||
:disable-filter="disableFilter"
|
||||
:read-only="readOnly"
|
||||
|
@ -198,6 +200,11 @@ export default {
|
|||
required: false,
|
||||
default: () => {},
|
||||
},
|
||||
isPublicView: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
<ViewFilterForm
|
||||
:fields="fields"
|
||||
:view="view"
|
||||
:is-public-view="isPublicView"
|
||||
:read-only="readOnly"
|
||||
:disable-filter="disableFilter"
|
||||
@changed="$emit('changed')"
|
||||
|
@ -47,6 +48,11 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isPublicView: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
:filter-type="view.filter_type"
|
||||
:fields="fields"
|
||||
:view="view"
|
||||
:is-public-view="isPublicView"
|
||||
:read-only="readOnly"
|
||||
:add-condition-string="$t('viewFilterContext.addFilter')"
|
||||
class="filters__items--with-padding filters__items--scrollable"
|
||||
|
@ -74,6 +75,11 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isPublicView: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
|
|
|
@ -62,7 +62,7 @@ export default {
|
|||
return isNumeric(this.filter.value)
|
||||
},
|
||||
isDropdown() {
|
||||
return this.readOnly && this.view
|
||||
return this.readOnly && this.view && this.isPublicView
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
|
|
@ -10,6 +10,11 @@ export default {
|
|||
required: false,
|
||||
default: undefined,
|
||||
},
|
||||
isPublicView: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
filter: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
|
|
@ -77,7 +77,7 @@ export default {
|
|||
// It might be possible that the view also has some stores that need to be
|
||||
// filled with initial data, so we're going to call the fetch function here.
|
||||
const type = app.$registry.get('view', view.type)
|
||||
await type.fetch({ store }, view, fields, 'page/')
|
||||
await type.fetch({ store }, database, view, fields, 'page/')
|
||||
return {
|
||||
database,
|
||||
table,
|
||||
|
|
|
@ -182,7 +182,7 @@ export default {
|
|||
return error({ statusCode: 400, message: type.getDeactivatedText() })
|
||||
}
|
||||
|
||||
await type.fetch({ store }, view, data.fields, 'page/')
|
||||
await type.fetch({ store }, data.database, view, data.fields, 'page/')
|
||||
} catch (e) {
|
||||
// In case of a network error we want to fail hard.
|
||||
if (e.response === undefined && !(e instanceof StoreItemLookupError)) {
|
||||
|
|
|
@ -130,12 +130,19 @@ export default (client) => {
|
|||
},
|
||||
fetchFieldAggregations({
|
||||
gridId,
|
||||
filters = {},
|
||||
search = '',
|
||||
searchMode = '',
|
||||
signal = null,
|
||||
}) {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
Object.keys(filters).forEach((key) => {
|
||||
filters[key].forEach((value) => {
|
||||
params.append(key, value)
|
||||
})
|
||||
})
|
||||
|
||||
if (search) {
|
||||
params.append('search', search)
|
||||
if (searchMode) {
|
||||
|
|
|
@ -148,6 +148,8 @@ export default ({ service, customPopulateRow }) => {
|
|||
requestSize: 100,
|
||||
// The current view id.
|
||||
viewId: -1,
|
||||
// If true, ad hoc filtering is used instead of persistent one
|
||||
adhocFiltering: false,
|
||||
// Indicates whether the store is currently fetching another batch of rows.
|
||||
fetching: false,
|
||||
// A list of all the rows in the table. The ones that haven't been fetched yet
|
||||
|
@ -266,6 +268,9 @@ export default ({ service, customPopulateRow }) => {
|
|||
const currentValue = row._.metadata[rowMetadataType]
|
||||
Vue.set(row._.metadata, rowMetadataType, updateFunction(currentValue))
|
||||
},
|
||||
SET_ADHOC_FILTERING(state, adhocFiltering) {
|
||||
state.adhocFiltering = adhocFiltering
|
||||
},
|
||||
}
|
||||
|
||||
const actions = {
|
||||
|
@ -276,13 +281,15 @@ export default ({ service, customPopulateRow }) => {
|
|||
*/
|
||||
async fetchInitialRows(
|
||||
context,
|
||||
{ viewId, fields, initialRowArguments = {} }
|
||||
{ viewId, fields, adhocFiltering, initialRowArguments = {} }
|
||||
) {
|
||||
const { commit, getters, rootGetters } = context
|
||||
commit('SET_VIEW_ID', viewId)
|
||||
commit('SET_SEARCH', {
|
||||
activeSearchTerm: '',
|
||||
})
|
||||
commit('SET_ADHOC_FILTERING', adhocFiltering)
|
||||
const view = rootGetters['view/get'](viewId)
|
||||
const { data } = await service(this.$client).fetchRows({
|
||||
viewId,
|
||||
offset: 0,
|
||||
|
@ -292,7 +299,7 @@ export default ({ service, customPopulateRow }) => {
|
|||
publicUrl: rootGetters['page/view/public/getIsPublic'],
|
||||
publicAuthToken: rootGetters['page/view/public/getAuthToken'],
|
||||
orderBy: getOrderBy(rootGetters, getters.getViewId),
|
||||
filters: getFilters(rootGetters, getters.getViewId),
|
||||
filters: getFilters(view, adhocFiltering),
|
||||
...initialRowArguments,
|
||||
})
|
||||
const rows = Array(data.count).fill(null)
|
||||
|
@ -351,6 +358,8 @@ export default ({ service, customPopulateRow }) => {
|
|||
return
|
||||
}
|
||||
|
||||
const view = rootGetters['view/get'](getters.getViewId)
|
||||
|
||||
// We can only make one request at the same time, so we're going to set the
|
||||
// fetching state to `true` to prevent multiple requests being fired
|
||||
// simultaneously.
|
||||
|
@ -367,7 +376,7 @@ export default ({ service, customPopulateRow }) => {
|
|||
publicUrl: rootGetters['page/view/public/getIsPublic'],
|
||||
publicAuthToken: rootGetters['page/view/public/getAuthToken'],
|
||||
orderBy: getOrderBy(rootGetters, getters.getViewId),
|
||||
filters: getFilters(rootGetters, getters.getViewId),
|
||||
filters: getFilters(view, getters.getAdhocFiltering),
|
||||
})
|
||||
commit('UPDATE_ROWS', {
|
||||
offset: rangeToFetch.offset,
|
||||
|
@ -399,8 +408,9 @@ export default ({ service, customPopulateRow }) => {
|
|||
*/
|
||||
async refresh(
|
||||
{ dispatch, commit, getters, rootGetters },
|
||||
{ fields, includeFieldOptions = false }
|
||||
{ fields, adhocFiltering, includeFieldOptions = false }
|
||||
) {
|
||||
commit('SET_ADHOC_FILTERING', adhocFiltering)
|
||||
// If another refresh or fetch request is currently running, we need to cancel
|
||||
// it because the response is most likely going to be outdated and we don't
|
||||
// need it anymore.
|
||||
|
@ -409,7 +419,7 @@ export default ({ service, customPopulateRow }) => {
|
|||
}
|
||||
|
||||
lastRequestController = new AbortController()
|
||||
|
||||
const view = rootGetters['view/get'](getters.getViewId)
|
||||
try {
|
||||
// We first need to fetch the count of all rows because we need to know how
|
||||
// many rows there are in total to estimate what are new visible range it
|
||||
|
@ -424,7 +434,7 @@ export default ({ service, customPopulateRow }) => {
|
|||
searchMode: getDefaultSearchModeFromEnv(this.$config),
|
||||
publicUrl: rootGetters['page/view/public/getIsPublic'],
|
||||
publicAuthToken: rootGetters['page/view/public/getAuthToken'],
|
||||
filters: getFilters(rootGetters, getters.getViewId),
|
||||
filters: getFilters(view, adhocFiltering),
|
||||
})
|
||||
|
||||
// Create a new empty array containing un-fetched rows.
|
||||
|
@ -469,7 +479,7 @@ export default ({ service, customPopulateRow }) => {
|
|||
publicUrl: rootGetters['page/view/public/getIsPublic'],
|
||||
publicAuthToken: rootGetters['page/view/public/getAuthToken'],
|
||||
orderBy: getOrderBy(rootGetters, getters.getViewId),
|
||||
filters: getFilters(rootGetters, getters.getViewId),
|
||||
filters: getFilters(view, adhocFiltering),
|
||||
})
|
||||
|
||||
results.forEach((row, index) => {
|
||||
|
@ -1078,6 +1088,9 @@ export default ({ service, customPopulateRow }) => {
|
|||
isHidingRowsNotMatchingSearch(state) {
|
||||
return true
|
||||
},
|
||||
getAdhocFiltering(state) {
|
||||
return state.adhocFiltering
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -89,6 +89,8 @@ export const state = () => ({
|
|||
multiSelectStartFieldIndex: -1,
|
||||
// The last used grid id.
|
||||
lastGridId: -1,
|
||||
// If true, ad hoc filtering is used instead of persistent one
|
||||
adhocFiltering: false,
|
||||
// Contains the custom field options per view. Things like the field width are
|
||||
// stored here.
|
||||
fieldOptions: {},
|
||||
|
@ -156,6 +158,9 @@ export const mutations = {
|
|||
SET_LAST_GRID_ID(state, gridId) {
|
||||
state.lastGridId = gridId
|
||||
},
|
||||
SET_ADHOC_FILTERING(state, adhocFiltering) {
|
||||
state.adhocFiltering = adhocFiltering
|
||||
},
|
||||
SET_SCROLL_TOP(state, scrollTop) {
|
||||
state.scrollTop = scrollTop
|
||||
},
|
||||
|
@ -612,6 +617,7 @@ export const actions = {
|
|||
) {
|
||||
const windowHeight = getters.getWindowHeight
|
||||
const gridId = getters.getLastGridId
|
||||
const view = rootGetters['view/get'](getters.getLastGridId)
|
||||
|
||||
// Calculate what the middle row index of the visible window based on the scroll
|
||||
// top.
|
||||
|
@ -719,7 +725,7 @@ export const actions = {
|
|||
publicAuthToken: rootGetters['page/view/public/getAuthToken'],
|
||||
groupBy: getGroupBy(rootGetters, getters.getLastGridId),
|
||||
orderBy: getOrderBy(rootGetters, getters.getLastGridId),
|
||||
filters: getFilters(rootGetters, getters.getLastGridId),
|
||||
filters: getFilters(view, getters.getAdhocFiltering),
|
||||
})
|
||||
.then(({ data }) => {
|
||||
data.results.forEach((row) => {
|
||||
|
@ -858,7 +864,7 @@ export const actions = {
|
|||
*/
|
||||
async fetchInitial(
|
||||
{ dispatch, commit, getters, rootGetters },
|
||||
{ gridId, fields }
|
||||
{ gridId, fields, adhocFiltering }
|
||||
) {
|
||||
// Reset scrollTop when switching table
|
||||
fireScrollTop.distance = 0
|
||||
|
@ -870,7 +876,9 @@ export const actions = {
|
|||
hideRowsNotMatchingSearch: true,
|
||||
})
|
||||
commit('SET_LAST_GRID_ID', gridId)
|
||||
commit('SET_ADHOC_FILTERING', adhocFiltering)
|
||||
|
||||
const view = rootGetters['view/get'](getters.getLastGridId)
|
||||
const limit = getters.getBufferRequestSize * 2
|
||||
const { data } = await GridService(this.$client).fetchRows({
|
||||
gridId,
|
||||
|
@ -883,7 +891,7 @@ export const actions = {
|
|||
publicAuthToken: rootGetters['page/view/public/getAuthToken'],
|
||||
groupBy: getGroupBy(rootGetters, getters.getLastGridId),
|
||||
orderBy: getOrderBy(rootGetters, getters.getLastGridId),
|
||||
filters: getFilters(rootGetters, getters.getLastGridId),
|
||||
filters: getFilters(view, adhocFiltering),
|
||||
})
|
||||
data.results.forEach((row) => {
|
||||
const metadata = extractRowMetadata(data, row.id)
|
||||
|
@ -917,8 +925,9 @@ export const actions = {
|
|||
*/
|
||||
refresh(
|
||||
{ dispatch, commit, getters, rootGetters },
|
||||
{ view, fields, includeFieldOptions = false }
|
||||
{ view, fields, adhocFiltering, includeFieldOptions = false }
|
||||
) {
|
||||
commit('SET_ADHOC_FILTERING', adhocFiltering)
|
||||
const gridId = getters.getLastGridId
|
||||
|
||||
if (lastRefreshRequest !== null) {
|
||||
|
@ -933,7 +942,7 @@ export const actions = {
|
|||
signal: lastRefreshRequestController.signal,
|
||||
publicUrl: rootGetters['page/view/public/getIsPublic'],
|
||||
publicAuthToken: rootGetters['page/view/public/getAuthToken'],
|
||||
filters: getFilters(rootGetters, getters.getLastGridId),
|
||||
filters: getFilters(view, adhocFiltering),
|
||||
})
|
||||
.then((response) => {
|
||||
const count = response.data.count
|
||||
|
@ -960,7 +969,7 @@ export const actions = {
|
|||
publicAuthToken: rootGetters['page/view/public/getAuthToken'],
|
||||
groupBy: getGroupBy(rootGetters, getters.getLastGridId),
|
||||
orderBy: getOrderBy(rootGetters, getters.getLastGridId),
|
||||
filters: getFilters(rootGetters, getters.getLastGridId),
|
||||
filters: getFilters(view, adhocFiltering),
|
||||
})
|
||||
.then(({ data }) => ({
|
||||
data,
|
||||
|
@ -1164,6 +1173,7 @@ export const actions = {
|
|||
this.$client
|
||||
).fetchFieldAggregations({
|
||||
gridId: view.id,
|
||||
filters: getFilters(view, getters.getAdhocFiltering),
|
||||
search,
|
||||
searchMode: getDefaultSearchModeFromEnv(this.$config),
|
||||
signal: lastAggregationRequest.controller.signal,
|
||||
|
@ -1632,6 +1642,7 @@ export const actions = {
|
|||
}
|
||||
|
||||
const gridId = getters.getLastGridId
|
||||
const view = rootGetters['view/get'](getters.getLastGridId)
|
||||
const { data } = await GridService(this.$client).fetchRows({
|
||||
gridId,
|
||||
offset: startIndex,
|
||||
|
@ -1642,7 +1653,7 @@ export const actions = {
|
|||
publicAuthToken: rootGetters['page/view/public/getAuthToken'],
|
||||
groupBy: getGroupBy(rootGetters, getters.getLastGridId),
|
||||
orderBy: getOrderBy(rootGetters, getters.getLastGridId),
|
||||
filters: getFilters(rootGetters, getters.getLastGridId),
|
||||
filters: getFilters(view, getters.getAdhocFiltering),
|
||||
includeFields: fields,
|
||||
excludeFields,
|
||||
})
|
||||
|
@ -3102,6 +3113,9 @@ export const getters = {
|
|||
getGroupByMetadata(state) {
|
||||
return state.groupByMetadata
|
||||
},
|
||||
getAdhocFiltering(state) {
|
||||
return state.adhocFiltering
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -453,26 +453,35 @@ export function getOrderBy(rootGetters, viewId) {
|
|||
}
|
||||
}
|
||||
|
||||
export function getFilters(rootGetters, viewId) {
|
||||
export function isadhocFiltering(app, workspace, view, publicView) {
|
||||
return (
|
||||
publicView ||
|
||||
(app.$hasPermission(
|
||||
'database.table.view.list_filter',
|
||||
view,
|
||||
workspace.id
|
||||
) &&
|
||||
!app.$hasPermission(
|
||||
'database.table.view.create_filter',
|
||||
view,
|
||||
workspace.id
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
export function getFilters(view, adhocFiltering) {
|
||||
const payload = {}
|
||||
|
||||
if (rootGetters['page/view/public/getIsPublic']) {
|
||||
const view = rootGetters['view/get'](viewId)
|
||||
|
||||
if (!view.filters_disabled) {
|
||||
const {
|
||||
filter_type: filterType,
|
||||
filter_groups: filterGroups,
|
||||
filters,
|
||||
} = view
|
||||
const filterTree = createFiltersTree(filterType, filters, filterGroups)
|
||||
if (filterTree.hasFilters()) {
|
||||
const serializedTree = filterTree.getFiltersTreeSerialized()
|
||||
payload.filters = [JSON.stringify(serializedTree)]
|
||||
}
|
||||
}
|
||||
return payload
|
||||
if (adhocFiltering && !view.filters_disabled) {
|
||||
const {
|
||||
filter_type: filterType,
|
||||
filter_groups: filterGroups,
|
||||
filters,
|
||||
} = view
|
||||
const filterTree = createFiltersTree(filterType, filters, filterGroups)
|
||||
const serializedTree = filterTree.getFiltersTreeSerialized()
|
||||
payload.filters = [JSON.stringify(serializedTree)]
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,9 +7,11 @@ import GalleryViewHeader from '@baserow/modules/database/components/view/gallery
|
|||
import FormView from '@baserow/modules/database/components/view/form/FormView'
|
||||
import FormViewHeader from '@baserow/modules/database/components/view/form/FormViewHeader'
|
||||
import { FileFieldType } from '@baserow/modules/database/fieldTypes'
|
||||
import { newFieldMatchesActiveSearchTerm } from '@baserow/modules/database/utils/view'
|
||||
import {
|
||||
newFieldMatchesActiveSearchTerm,
|
||||
isadhocFiltering,
|
||||
} from '@baserow/modules/database/utils/view'
|
||||
import { clone } from '@baserow/modules/core/utils/object'
|
||||
|
||||
export const maxPossibleOrderValue = 32767
|
||||
|
||||
export class ViewType extends Registerable {
|
||||
|
@ -185,6 +187,7 @@ export class ViewType extends Registerable {
|
|||
*/
|
||||
refresh(
|
||||
context,
|
||||
database,
|
||||
view,
|
||||
fields,
|
||||
storePrefix = '',
|
||||
|
@ -385,10 +388,18 @@ export class GridViewType extends ViewType {
|
|||
return 'database-public-grid-view'
|
||||
}
|
||||
|
||||
async fetch({ store }, view, fields, storePrefix = '') {
|
||||
async fetch({ store }, database, view, fields, storePrefix = '') {
|
||||
const isPublic = store.getters[storePrefix + 'view/public/getIsPublic']
|
||||
const adhocFiltering = isadhocFiltering(
|
||||
this.app,
|
||||
database.workspace,
|
||||
view,
|
||||
isPublic
|
||||
)
|
||||
await store.dispatch(storePrefix + 'view/grid/fetchInitial', {
|
||||
gridId: view.id,
|
||||
fields,
|
||||
adhocFiltering,
|
||||
})
|
||||
// The grid view store keeps a copy of the group bys that must only be updated
|
||||
// after the refresh of the page. This is because the group by depends on the rows
|
||||
|
@ -401,16 +412,25 @@ export class GridViewType extends ViewType {
|
|||
|
||||
async refresh(
|
||||
{ store },
|
||||
database,
|
||||
view,
|
||||
fields,
|
||||
storePrefix = '',
|
||||
includeFieldOptions = false,
|
||||
sourceEvent = null
|
||||
) {
|
||||
const isPublic = store.getters[storePrefix + 'view/public/getIsPublic']
|
||||
const adhocFiltering = isadhocFiltering(
|
||||
this.app,
|
||||
database.workspace,
|
||||
view,
|
||||
isPublic
|
||||
)
|
||||
await store.dispatch(storePrefix + 'view/grid/refresh', {
|
||||
view,
|
||||
fields,
|
||||
includeFieldOptions,
|
||||
adhocFiltering,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -590,24 +610,31 @@ class BaseBufferedRowView extends ViewType {
|
|||
return {}
|
||||
}
|
||||
|
||||
async fetch({ store }, view, fields, storePrefix = '') {
|
||||
async fetch({ store }, database, view, fields, storePrefix = '') {
|
||||
const isPublic = store.getters[storePrefix + 'view/public/getIsPublic']
|
||||
const adhocFiltering = isPublic
|
||||
await store.dispatch(`${storePrefix}view/${this.getType()}/fetchInitial`, {
|
||||
viewId: view.id,
|
||||
fields,
|
||||
adhocFiltering,
|
||||
})
|
||||
}
|
||||
|
||||
async refresh(
|
||||
{ store },
|
||||
database,
|
||||
view,
|
||||
fields,
|
||||
storePrefix = '',
|
||||
includeFieldOptions = false,
|
||||
sourceEvent = null
|
||||
) {
|
||||
const isPublic = store.getters[storePrefix + 'view/public/getIsPublic']
|
||||
const adhocFiltering = isPublic
|
||||
await store.dispatch(storePrefix + 'view/' + this.getType() + '/refresh', {
|
||||
fields,
|
||||
includeFieldOptions,
|
||||
adhocFiltering,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -879,6 +906,7 @@ export class FormViewType extends ViewType {
|
|||
|
||||
async refresh(
|
||||
{ store },
|
||||
database,
|
||||
view,
|
||||
fields,
|
||||
storePrefix = '',
|
||||
|
@ -927,7 +955,7 @@ export class FormViewType extends ViewType {
|
|||
)
|
||||
}
|
||||
|
||||
async fetch({ store }, view, fields, storePrefix = '') {
|
||||
async fetch({ store }, database, view, fields, storePrefix = '') {
|
||||
await store.dispatch(storePrefix + 'view/form/fetchInitial', {
|
||||
formId: view.id,
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue