Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ def get_related_table_row_ids_with_changes(
:return: A list of row IDs in the related table that have changed.
"""

if not rows:
return []

model = rows[0]._meta.model
table_field_objs = model.get_field_objects()

Expand Down Expand Up @@ -251,7 +254,7 @@ def get_filters_for_webhooks_to_call(

q = self._get_filters_for_webhooks_to_call(model=model, table=table, **kwargs)

if kwargs.get("rows") is not None:
if kwargs.get("rows"):
q |= self.get_filters_for_related_webhook_to_call(
model=model, table=table, **kwargs
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@

from baserow.contrib.database.fields.handler import FieldHandler
from baserow.contrib.database.rows.handler import RowHandler
from baserow.contrib.database.rows.webhook_event_types import RowsUpdatedEventType
from baserow.contrib.database.rows.webhook_event_types import (
RowsDeletedEventType,
RowsUpdatedEventType,
)
from baserow.contrib.database.webhooks.handler import WebhookHandler
from baserow.contrib.database.webhooks.models import TableWebhook
from baserow.contrib.database.webhooks.registries import webhook_event_type_registry
Expand Down Expand Up @@ -974,3 +977,30 @@ def _without_version_checks(queries):
assert len(_without_version_checks(captured.captured_queries)) >= len(
_without_version_checks(captured2.captured_queries)
)


@pytest.mark.django_db()
def test_get_filters_for_webhooks_to_call_with_empty_rows(data_fixture):
user = data_fixture.create_user()
table_a, table_b, link_a_to_b = data_fixture.create_two_linked_tables(user=user)

event_type: RowsDeletedEventType = webhook_event_type_registry.get("rows.deleted")
model_a = table_a.get_model()

q = event_type.get_filters_for_webhooks_to_call(
model=model_a, table=table_a, rows=[]
)

assert list(TableWebhook.objects.filter(q).values_list("id", flat=True)) == []


@pytest.mark.django_db()
def test_get_related_table_row_ids_with_changes_empty_rows(data_fixture):
user = data_fixture.create_user()
table_a, table_b, link_a_to_b = data_fixture.create_two_linked_tables(user=user)

event_type: RowsDeletedEventType = webhook_event_type_registry.get("rows.deleted")

result = event_type.get_related_table_row_ids_with_changes(table_b, rows=[])

assert result == []
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "bug",
"message": "Guard webhook filter computation against empty rows lists to prevent IndexError on undo.",
"issue_origin": "github",
"issue_number": 5838,
"domain": "database",
"bullet_points": [],
"created_at": "2026-08-04"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "bug",
"message": "Fix field store crash when deleting a Kanban single-select stack.",
"issue_origin": "github",
"issue_number": 5846,
"domain": "database",
"bullet_points": [],
"created_at": "2026-08-04"
}
Original file line number Diff line number Diff line change
Expand Up @@ -1120,7 +1120,7 @@ export const actions = {
await dispatch(
'field/forceUpdate',
{
singleSelectField,
field: singleSelectField,
oldField: clone(singleSelectField),
data,
relatedFields: data.related_fields,
Expand Down
52 changes: 52 additions & 0 deletions premium/web-frontend/test/unit/premium/store/view/kanban.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import kanbanStore from '@baserow_premium/store/view/kanban'
import fieldStore from '@baserow/modules/database/store/field'
import { TestApp } from '@baserow/test/helpers/testApp'
import { UNDO_REDO_ACTION_GROUP_HEADER } from '@baserow/modules/database/utils/action'

Expand All @@ -23,6 +24,7 @@ describe('Kanban view store', () => {
store = testApp.createStore({
modules: {
kanban: kanbanStore,
field: fieldStore,
},
})
})
Expand Down Expand Up @@ -509,4 +511,54 @@ describe('Kanban view store', () => {
before
)
})

test('deleteStack updates the field in the store instead of throwing', async () => {
const singleSelectField = {
id: 1,
table_id: 99,
name: 'Status',
type: 'single_select',
select_options: [
{ id: 1, value: 'A', color: 'blue' },
{ id: 2, value: 'B', color: 'red' },
],
}

const fieldState = Object.assign(fieldStore.state(), {
items: [singleSelectField],
})
store.replaceState({ ...store.state, field: fieldState })

const state = Object.assign(kanbanStore.state(), {
singleSelectFieldId: 1,
stacks: {},
})
store.replaceState({ ...store.state, kanban: state })

const updatedField = {
id: 1,
table_id: 99,
name: 'Status',
type: 'single_select',
select_options: [{ id: 1, value: 'A', color: 'blue' }],
related_fields: [],
}
testApp.mock.onPatch('/database/fields/1/').reply(200, updatedField)

const originalGetAll = store.$registry.getAll.bind(store.$registry)
vi.spyOn(store.$registry, 'getAll').mockImplementation((namespace) =>
namespace === 'view' ? {} : originalGetAll(namespace)
)

const doFieldUpdate = await store.dispatch('kanban/deleteStack', {
singleSelectField,
optionId: 2,
deferredFieldUpdate: true,
})

await doFieldUpdate()

expect(store.state.field.items[0].id).toBe(1)
expect(store.state.field.items[0].select_options).toHaveLength(1)
})
})
Loading