diff --git a/changelog/entries/unreleased/bug/4596_generate_values_empty_output_field.json b/changelog/entries/unreleased/bug/4596_generate_values_empty_output_field.json new file mode 100644 index 0000000000..d8944302fe --- /dev/null +++ b/changelog/entries/unreleased/bug/4596_generate_values_empty_output_field.json @@ -0,0 +1,9 @@ +{ + "type": "bug", + "message": "Fix Generate all AI values doesn't work when the output field is Single Select and Generate only values for empty cells is checked", + "issue_origin": "github", + "issue_number": 4596, + "domain": "database", + "bullet_points": [], + "created_at": "2026-01-28" +} diff --git a/changelog/entries/unreleased/feature/list_all_application_types_in_left_sidebar.json b/changelog/entries/unreleased/feature/list_all_application_types_in_left_sidebar.json new file mode 100644 index 0000000000..e2d3a1ebfe --- /dev/null +++ b/changelog/entries/unreleased/feature/list_all_application_types_in_left_sidebar.json @@ -0,0 +1,8 @@ +{ + "type": "feature", + "message": "List all application types in left sidebar, even if there are no items in there.", + "domain": "core", + "issue_number": null, + "bullet_points": [], + "created_at": "2026-01-26" +} diff --git a/premium/backend/src/baserow_premium/fields/job_types.py b/premium/backend/src/baserow_premium/fields/job_types.py index 0e97181b5c..eba4395a5c 100644 --- a/premium/backend/src/baserow_premium/fields/job_types.py +++ b/premium/backend/src/baserow_premium/fields/job_types.py @@ -188,9 +188,14 @@ def _filter_empty_values( :return: The filtered queryset. """ - return queryset.filter( - **{f"{ai_field.db_column}__isnull": True} - ) | queryset.filter(**{ai_field.db_column: ""}) + baserow_field_type = ai_field.get_type().get_baserow_field_type(ai_field) + model_field = baserow_field_type.get_model_field(ai_field) + q = ai_field.get_type().empty_query( + ai_field.db_column, + model_field, + ai_field, + ) + return queryset.filter(q) def _get_field(self, field_id: int) -> AIField: """ diff --git a/premium/backend/tests/baserow_premium_tests/fields/test_generate_ai_values_job_type.py b/premium/backend/tests/baserow_premium_tests/fields/test_generate_ai_values_job_type.py index e522879915..1d6664ebcf 100644 --- a/premium/backend/tests/baserow_premium_tests/fields/test_generate_ai_values_job_type.py +++ b/premium/backend/tests/baserow_premium_tests/fields/test_generate_ai_values_job_type.py @@ -11,6 +11,7 @@ from baserow.contrib.database.fields.exceptions import FieldDoesNotExist from baserow.contrib.database.fields.handler import FieldHandler +from baserow.contrib.database.fields.models import SelectOption from baserow.contrib.database.rows.exceptions import RowDoesNotExist from baserow.contrib.database.rows.handler import RowHandler from baserow.contrib.database.views.exceptions import ViewDoesNotExist @@ -19,6 +20,7 @@ from baserow.core.jobs.handler import JobHandler from baserow.core.storage import get_default_storage from baserow.core.user_files.handler import UserFileHandler +from baserow_premium.fields.ai_field_output_types import ChoiceAIFieldOutputType from baserow_premium.fields.models import GenerateAIValuesJob @@ -182,6 +184,48 @@ def test_create_job_with_only_empty_flag_table_mode(premium_data_fixture): assert job.mode == GenerateAIValuesJob.MODES.TABLE +@pytest.mark.django_db +@pytest.mark.field_ai +def test_create_job_with_only_empty_choice_output_type(premium_data_fixture): + """Test job creation with only_empty=True and single select output.""" + + premium_data_fixture.register_fake_generate_ai_type() + user = premium_data_fixture.create_user() + database = premium_data_fixture.create_database_application(user=user) + table = premium_data_fixture.create_database_table(database=database) + field = premium_data_fixture.create_ai_field( + table=table, ai_prompt="'test'", ai_output_type=ChoiceAIFieldOutputType.type + ) + option_1 = SelectOption.objects.create(field=field, value="A", order=1) + SelectOption.objects.create(field=field, value="B", order=2) + model = table.get_model() + + rows = ( + RowHandler() + .create_rows( + user, table, rows_values=[{f"field_{field.id}": option_1.value}, {}, {}] + ) + .created_rows + ) + row_ids = [row.id for row in rows] + + job = JobHandler().create_and_start_job( + user, + "generate_ai_values", + field_id=field.id, + row_ids=row_ids, + only_empty=True, + sync=True, + ) + + assert job.only_empty is True + assert job.mode == GenerateAIValuesJob.MODES.ROWS + + choice_values = model.objects.all().values_list(f"field_{field.id}", flat=True) + assert choice_values[0] == option_1.id + assert all(x is not None for x in choice_values), choice_values + + @pytest.mark.django_db @pytest.mark.field_ai def test_create_job_with_nonexistent_field(premium_data_fixture): diff --git a/web-frontend/modules/core/assets/scss/components/tree.scss b/web-frontend/modules/core/assets/scss/components/tree.scss index e7d375c05f..83701a822a 100644 --- a/web-frontend/modules/core/assets/scss/components/tree.scss +++ b/web-frontend/modules/core/assets/scss/components/tree.scss @@ -5,7 +5,7 @@ margin: 0; &:not(:last-child) { - margin-bottom: 12px; + margin-bottom: 8px; } .tree__item & { @@ -312,8 +312,40 @@ } .tree__heading { + display: flex; + margin: 8px; + justify-content: space-between; + align-items: center; +} + +.tree__heading-name { font-size: 11px; font-weight: 500; color: $palette-neutral-900; - margin: 8px; + line-height: 16px; +} + +.tree__heading-add { + color: $palette-neutral-600; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + line-height: 16px; + + @include rounded($rounded); + + &:hover { + text-decoration: none; + background-color: $palette-neutral-200; + outline: solid 2px $palette-neutral-200; + color: $palette-neutral-800; + } +} + +.tree__separator { + border-bottom: solid 1px $palette-neutral-200; + margin: 8px 8px 16px; } diff --git a/web-frontend/modules/core/components/sidebar/SidebarWithWorkspace.vue b/web-frontend/modules/core/components/sidebar/SidebarWithWorkspace.vue index bc2cecae22..6953f0ecde 100644 --- a/web-frontend/modules/core/components/sidebar/SidebarWithWorkspace.vue +++ b/web-frontend/modules/core/components/sidebar/SidebarWithWorkspace.vue @@ -1,6 +1,6 @@