1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-15 01:28:30 +00:00

Made some minor changes and bug fixes to the row select modal

This commit is contained in:
Bram Wiepjes 2022-09-06 13:14:06 +00:00
parent 2446f86024
commit 9ecff6566a
7 changed files with 138 additions and 37 deletions
backend
src/baserow/contrib/database/api/views
tests/baserow/contrib/database/api/views
web-frontend/modules
core/assets/scss/components
database
components/row
services
utils

View file

@ -22,6 +22,15 @@ from baserow.contrib.database.views.registries import (
)
class ListQueryParamatersSerializer(serializers.Serializer):
limit = serializers.IntegerField(required=False, default=None)
type = serializers.ChoiceField(
required=False,
default=None,
choices=lazy(view_type_registry.get_types, list)(),
)
class FieldOptionsField(serializers.Field):
default_error_messages = {
"invalid_key": "Field option key must be numeric.",

View file

@ -18,6 +18,7 @@ from baserow.api.decorators import (
map_exceptions,
validate_body,
validate_body_custom_fields,
validate_query_parameters,
)
from baserow.api.errors import ERROR_USER_NOT_IN_GROUP
from baserow.api.pagination import PageNumberPagination
@ -123,6 +124,7 @@ from .serializers import (
CreateViewFilterSerializer,
CreateViewSerializer,
CreateViewSortSerializer,
ListQueryParamatersSerializer,
OrderViewsSerializer,
PublicViewAuthRequestSerializer,
PublicViewAuthResponseSerializer,
@ -171,6 +173,26 @@ class ViewsView(APIView):
description="Returns only views of the table related to the provided "
"value.",
),
OpenApiParameter(
name="type",
location=OpenApiParameter.QUERY,
type=OpenApiTypes.STR,
description=(
"Optionally filter on the view type. If provided, only views of "
"that type will be returned."
),
),
OpenApiParameter(
name="limit",
location=OpenApiParameter.QUERY,
type=OpenApiTypes.INT,
description=(
"The maximum amount of views that must be returned. This endpoint "
"doesn't support pagination, but if you for example just need to "
"fetch the first view, you can do that by setting a limit. There "
"isn't a limit by default."
),
),
OpenApiParameter(
name="include",
location=OpenApiParameter.QUERY,
@ -211,8 +233,9 @@ class ViewsView(APIView):
UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
}
)
@validate_query_parameters(ListQueryParamatersSerializer)
@allowed_includes("filters", "sortings", "decorations")
def get(self, request, table_id, filters, sortings, decorations):
def get(self, request, table_id, query_params, filters, sortings, decorations):
"""
Responds with a list of serialized views that belong to the table if the user
has access to that group.
@ -224,6 +247,11 @@ class ViewsView(APIView):
)
views = View.objects.filter(table=table).select_related("content_type", "table")
if query_params["type"]:
view_type = view_type_registry.get(query_params["type"])
content_type = ContentType.objects.get_for_model(view_type.model_class)
views = views.filter(content_type=content_type)
if filters:
views = views.prefetch_related("viewfilter_set")
@ -233,6 +261,9 @@ class ViewsView(APIView):
if decorations:
views = views.prefetch_related("viewdecoration_set")
if query_params["limit"]:
views = views[: query_params["limit"]]
views = specific_iterator(views)
data = [

View file

@ -94,6 +94,56 @@ def test_list_views(api_client, data_fixture):
assert response.json()["error"] == "ERROR_TABLE_DOES_NOT_EXIST"
@pytest.mark.django_db
def test_list_views_with_limit(api_client, data_fixture):
user, token = data_fixture.create_user_and_token(
email="test@test.nl", password="password", first_name="Test1"
)
table_1 = data_fixture.create_database_table(user=user)
view_1 = data_fixture.create_grid_view(table=table_1, order=1)
data_fixture.create_grid_view(table=table_1, order=3)
response = api_client.get(
reverse("api:database:views:list", kwargs={"table_id": table_1.id}),
{"limit": 1},
**{"HTTP_AUTHORIZATION": f"JWT {token}"},
)
assert response.status_code == HTTP_200_OK
response_json = response.json()
assert len(response_json) == 1
assert response_json[0]["id"] == view_1.id
@pytest.mark.django_db
def test_list_views_with_type_filter(api_client, data_fixture):
user, token = data_fixture.create_user_and_token(
email="test@test.nl", password="password", first_name="Test1"
)
table_1 = data_fixture.create_database_table(user=user)
grid = data_fixture.create_grid_view(table=table_1, order=1)
gallery = data_fixture.create_gallery_view(table=table_1, order=2)
response = api_client.get(
reverse("api:database:views:list", kwargs={"table_id": table_1.id}),
{"type": "grid"},
**{"HTTP_AUTHORIZATION": f"JWT {token}"},
)
assert response.status_code == HTTP_200_OK
response_json = response.json()
assert len(response_json) == 1
assert response_json[0]["id"] == grid.id
response = api_client.get(
reverse("api:database:views:list", kwargs={"table_id": table_1.id}),
{"type": "gallery"},
**{"HTTP_AUTHORIZATION": f"JWT {token}"},
)
assert response.status_code == HTTP_200_OK
response_json = response.json()
assert len(response_json) == 1
assert response_json[0]["id"] == gallery.id
@pytest.mark.django_db
def test_list_views_doesnt_do_n_queries(api_client, data_fixture):
user, token = data_fixture.create_user_and_token(

View file

@ -192,4 +192,5 @@
height: 44px;
background-color: $color-neutral-50;
border-top: solid 1px $color-neutral-200;
min-width: 100%;
}

View file

@ -130,7 +130,7 @@
<div
class="select-row-modal__foot"
:style="{
width: 3 * 200 + 'px',
width: fields.length * 200 + 'px',
}"
></div>
</div>
@ -163,7 +163,11 @@ import SelectRowField from '@baserow/modules/database/components/row/SelectRowFi
import RowCreateModal from '@baserow/modules/database/components/row/RowCreateModal'
import { prepareRowForRequest } from '@baserow/modules/database/utils/row'
import { DatabaseApplicationType } from '@baserow/modules/database/applicationTypes'
import { sortFieldsByOrderAndIdFunction } from '@baserow/modules/database/utils/view'
import {
filterVisibleFieldsFunction,
sortFieldsByOrderAndIdFunction,
} from '@baserow/modules/database/utils/view'
import { GridViewType } from '@baserow/modules/database/viewTypes'
export default {
name: 'SelectRowContent',
@ -193,8 +197,6 @@ export default {
lastHoveredRow: null,
addRowHover: false,
searchDebounce: null,
firstView: null,
fieldOptions: {},
}
},
computed: {
@ -224,14 +226,6 @@ export default {
},
},
async mounted() {
// We need to get the first view in order to
// sort the fields by their order
await this.fetchFirstView(this.tableId)
// Get the field options
// they contain the order of the fields
await this.fetchFieldOptions(this.firstView.id)
// The first time we have to fetch the fields because they are unknown for this
// table.
if (!(await this.fetchFields(this.tableId))) {
@ -243,6 +237,8 @@ export default {
return false
}
await this.orderFieldsByFirstGridViewFieldOptions(this.tableId)
// Because most of the template depends on having some initial data we mark the
// state as loaded after that. Only a loading animation is shown if there isn't any
// data.
@ -309,9 +305,7 @@ export default {
const primaryIndex = data.findIndex((item) => item.primary === true)
this.primary =
primaryIndex !== -1 ? data.splice(primaryIndex, 1)[0] : null
this.fields = data.sort(
sortFieldsByOrderAndIdFunction(this.fieldOptions)
)
this.fields = data
return true
} catch (error) {
notifyIf(error, 'row')
@ -320,33 +314,38 @@ export default {
return false
}
},
async fetchFirstView(tableId) {
/**
* This method fetches the first grid and the related field options. The ordering
* of that grid view will be applied to the already fetched fields. If anything
* goes wrong or if there isn't a grid view, the original order will be used.
*/
async orderFieldsByFirstGridViewFieldOptions(tableId) {
try {
const { data: views } = await ViewService(this.$client).fetchAll(
tableId,
true,
true
false,
false,
false,
// We can safely limit to `1` because the backend provides the views ordered.
1,
// We want to fetch the first grid view because for that type we're sure it's
// compatible with `filterVisibleFieldsFunction` and
// `sortFieldsByOrderAndIdFunction`. Others might also work, but this
// component is styled like a grid view and it makes to most sense to reflect
// that here.
GridViewType.getType()
)
// Get the view with the smallest order
this.firstView = views.reduce((viewWithSmallestOrder, view) => {
if (viewWithSmallestOrder === null) {
return view
}
return view.order < viewWithSmallestOrder.order
? view
: viewWithSmallestOrder
}, null)
} catch (error) {
notifyIf(error, 'view')
}
},
async fetchFieldOptions(viewId) {
try {
if (views.length === 0) {
return
}
const {
data: { field_options: fieldOptions },
} = await ViewService(this.$client).fetchFieldOptions(viewId)
this.fieldOptions = fieldOptions
} = await ViewService(this.$client).fetchFieldOptions(views[0].id)
this.fields = this.fields
.filter(filterVisibleFieldsFunction(fieldOptions))
.sort(sortFieldsByOrderAndIdFunction(fieldOptions))
} catch (error) {
notifyIf(error, 'view')
}

View file

@ -7,7 +7,9 @@ export default (client) => {
tableId,
includeFilters = false,
includeSortings = false,
includeDecorations = false
includeDecorations = false,
limit = null,
type = null
) {
const config = {
params: {},
@ -30,6 +32,14 @@ export default (client) => {
config.params.include = include.join(',')
}
if (limit !== null) {
config.params.limit = limit
}
if (type !== null) {
config.params.type = type
}
return client.get(`/database/views/table/${tableId}/`, config)
},
create(tableId, values) {

View file

@ -26,6 +26,7 @@ export function getRowSortFunction($registry, sortings, fields) {
sortFunction = sortFunction.thenBy((a, b) => a.id - b.id)
return sortFunction
}
/**
* Generates a sort function for fields based on order and id.
*/