From 26903a9ece4e9a564bb091f8195f82bedfdbb60b Mon Sep 17 00:00:00 2001
From: Bram Wiepjes <bramw@protonmail.com>
Date: Tue, 25 Mar 2025 10:16:52 +0000
Subject: [PATCH] Fix import Airtable table sections view order

---
 .../contrib/database/airtable/registry.py     | 18 ++++-
 .../airtable/test_airtable_view_types.py      | 70 +++++++++++++++++++
 ...ble_view_section_import_order_problem.json |  8 +++
 3 files changed, 95 insertions(+), 1 deletion(-)
 create mode 100644 changelog/entries/unreleased/bug/fix_airtable_view_section_import_order_problem.json

diff --git a/backend/src/baserow/contrib/database/airtable/registry.py b/backend/src/baserow/contrib/database/airtable/registry.py
index a801072a5..e1e2783f8 100644
--- a/backend/src/baserow/contrib/database/airtable/registry.py
+++ b/backend/src/baserow/contrib/database/airtable/registry.py
@@ -727,17 +727,33 @@ class AirtableViewType(Instance):
                 "The `baserow_view_type` must be implemented for the AirtableViewType."
             )
 
+        view_id = raw_airtable_view["id"]
         view_name = raw_airtable_view["name"]
         view_name = self._check_personal_or_locked(
             view_name, raw_airtable_view, raw_airtable_table, import_report
         )
 
+        # Extract the ordered views from the sections and put them in a flat list so
+        # that we can find the order for the Baserow view.
+        flattened_view_order = []
+        for view in raw_airtable_table["viewOrder"]:
+            if view in raw_airtable_table["viewSectionsById"]:
+                section = raw_airtable_table["viewSectionsById"][view]
+                section_views = section["viewOrder"]
+                flattened_view_order.extend(section_views)
+                # Baserow doesn't support sections, but we can prepend the name of the
+                # section.
+                if view_id in section_views:
+                    view_name = f"{section['name']} / {view_name}"
+            else:
+                flattened_view_order.append(view)
+
         view_type = view_type_registry.get(self.baserow_view_type)
         view = view_type.model_class(
             id=raw_airtable_view["id"],
             pk=raw_airtable_view["id"],
             name=view_name,
-            order=raw_airtable_table["viewOrder"].index(raw_airtable_view["id"]) + 1,
+            order=flattened_view_order.index(raw_airtable_view["id"]) + 1,
         )
 
         filters_object = raw_airtable_view_data.get("filters", None)
diff --git a/backend/tests/baserow/contrib/database/airtable/test_airtable_view_types.py b/backend/tests/baserow/contrib/database/airtable/test_airtable_view_types.py
index 92f7d9187..b12f94b73 100644
--- a/backend/tests/baserow/contrib/database/airtable/test_airtable_view_types.py
+++ b/backend/tests/baserow/contrib/database/airtable/test_airtable_view_types.py
@@ -1028,3 +1028,73 @@ def test_import_gallery_view_with_cover_column_type_fit():
     assert import_report.items[0].scope == SCOPE_VIEW
     assert import_report.items[0].table == "Data"
     assert serialized_view["card_cover_image_field_id"] == "fldwSc9PqedIhTSqhi3"
+
+
+def test_import_view_in_section_order():
+    raw_airtable_table = deepcopy(RAW_AIRTABLE_TABLE)
+    raw_airtable_table["viewOrder"] = ["vsc0001", "vsc0002", "viw00005"]
+    raw_airtable_table["viewSectionsById"] = {
+        "vsc0001": {
+            "id": "vsc0001",
+            "name": "Section 1",
+            "createdByUserId": "usr0001",
+            "pinnedForUserId": None,
+            "viewOrder": ["viw00001", "viw00002"],
+        },
+        "vsc0002": {
+            "id": "vsc0001",
+            "name": "Section 1",
+            "createdByUserId": "usr0001",
+            "pinnedForUserId": None,
+            "viewOrder": [RAW_AIRTABLE_VIEW["id"], "viw00004"],
+        },
+    }
+
+    airtable_view_type = airtable_view_type_registry.get("grid")
+    import_report = AirtableImportReport()
+    serialized_view = airtable_view_type.to_serialized_baserow_view(
+        FIELD_MAPPING,
+        ROW_ID_MAPPING,
+        raw_airtable_table,
+        RAW_AIRTABLE_VIEW,
+        RAW_AIRTABLE_GRID_VIEW_DATA,
+        AirtableImportConfig(),
+        import_report,
+    )
+
+    assert serialized_view["order"] == 3
+
+
+def test_import_view_in_section_name():
+    raw_airtable_table = deepcopy(RAW_AIRTABLE_TABLE)
+    raw_airtable_table["viewOrder"] = ["vsc0001", "vsc0002", "viw00005"]
+    raw_airtable_table["viewSectionsById"] = {
+        "vsc0001": {
+            "id": "vsc0001",
+            "name": "Section 1",
+            "createdByUserId": "usr0001",
+            "pinnedForUserId": None,
+            "viewOrder": ["viw00001", "viw00002"],
+        },
+        "vsc0002": {
+            "id": "vsc0001",
+            "name": "Section 2",
+            "createdByUserId": "usr0001",
+            "pinnedForUserId": None,
+            "viewOrder": [RAW_AIRTABLE_VIEW["id"], "viw00004"],
+        },
+    }
+
+    airtable_view_type = airtable_view_type_registry.get("grid")
+    import_report = AirtableImportReport()
+    serialized_view = airtable_view_type.to_serialized_baserow_view(
+        FIELD_MAPPING,
+        ROW_ID_MAPPING,
+        raw_airtable_table,
+        RAW_AIRTABLE_VIEW,
+        RAW_AIRTABLE_GRID_VIEW_DATA,
+        AirtableImportConfig(),
+        import_report,
+    )
+
+    assert serialized_view["name"] == "Section 2 / Grid view"
diff --git a/changelog/entries/unreleased/bug/fix_airtable_view_section_import_order_problem.json b/changelog/entries/unreleased/bug/fix_airtable_view_section_import_order_problem.json
new file mode 100644
index 000000000..6fca88c9a
--- /dev/null
+++ b/changelog/entries/unreleased/bug/fix_airtable_view_section_import_order_problem.json
@@ -0,0 +1,8 @@
+{
+  "type": "bug",
+  "message": "Fix bug where the Airtable import crashes if the table has sections.",
+  "domain": "database",
+  "issue_number": null,
+  "bullet_points": [],
+  "created_at": "2025-03-24"
+}