Repository: SFDO-Tooling/CumulusCI
Pull request #3305 (2022-07-25 05:20:06)

Fix `load_dataset` `set_recently_viewed` feature
Fix two issues which prevented the `set_recently_viewed` feature from working, and add better testing.

Recreating the issues by hand is roughly a 4 step process:
1. Delete the appropriate org_cache in the .cci directory. (or create a new org so it has no client-side cache)
2. Recreate the cache incompletely e.g. by using a mapping file with Account and Contact but not Opportunity.
3. Try again with a mapping file that uses an extra object like Opportunity.
4. Look closely at the warnings in the log for a warning about set_viewed.

All of these steps are automated like this:

`
git fetch && git branch -D temp-test-bug; git switch -c temp-test-bug origin/main && git cherry-pick cf35fee5d2648b2c9487fac445c0e056a1fc0622 && pytest -k test_recreate_set_recent_bug --org qa
`
Whereas this one succeeds:

`
git switch feature/set-recently-viewed-correct-objects && pytest -k test_recreate_set_recent_bug --org qa
`


Patch:
diff --git a/cumulusci/tasks/bulkdata/load.py b/cumulusci/tasks/bulkdata/load.py
index 94ee789fa..db649b806 100644
--- a/cumulusci/tasks/bulkdata/load.py
+++ b/cumulusci/tasks/bulkdata/load.py
@@ -157,16 +157,21 @@ class LoadData(SqlAlchemyMixin, BaseSalesforceApiTask):
         if self.options["set_recently_viewed"]:
             try:
                 self.logger.info("Setting records to 'recently viewed'.")
-                self._set_viewed()
+                set_recently_viewed = self._set_viewed()
             except Exception as e:
                 self.logger.warning(f"Could not set recently viewed because {e}")
+                set_recently_viewed = [SetRecentlyViewedInfo("ALL", e)]
+        else:
+            set_recently_viewed = False
 
         self.return_values = {
             "step_results": {
                 step_name: result_info.simplify()
                 for step_name, result_info in results.items()
-            }
+            },
         }
+        if set_recently_viewed is not False:
+            self.return_values["set_recently_viewed"] = set_recently_viewed
 
     def _execute_step(
         self, mapping: MappingStep
@@ -683,10 +688,11 @@ class LoadData(SqlAlchemyMixin, BaseSalesforceApiTask):
                 # Join maps together to get tuple (Contact ID, Contact SF ID) to insert into step's ID Table.
                 yield (contact_id, contact_sf_id)
 
-    def _set_viewed(self):
+    def _set_viewed(self) -> T.List["SetRecentlyViewedInfo"]:
         """Set items as recently viewed. Filter out custom objects without custom tabs."""
         object_names = set()
         custom_objects = set()
+        results = []
 
         # Separate standard and custom objects
         for mapping in self.mapping.values():
@@ -710,7 +716,7 @@ class LoadData(SqlAlchemyMixin, BaseSalesforceApiTask):
                     f"Cannot get the list of custom tabs to set recently viewed status on them. Error: {e}"
                 )
         with get_org_schema(
-            self.sf, self.org_config, object_names & custom_objects
+            self.sf, self.org_config, included_objects=object_names, force_recache=True
         ) as org_schema:
             for mapped_item in sorted(object_names):
                 if org_schema[mapped_item].mruEnabled:
@@ -718,10 +724,13 @@ class LoadData(SqlAlchemyMixin, BaseSalesforceApiTask):
                         self.sf.query_all(
                             f"SELECT Id FROM {mapped_item} ORDER BY CreatedDate DESC LIMIT 1000 FOR VIEW"
                         )
+                        results.append(SetRecentlyViewedInfo(mapped_item, None))
                     except Exception as e:
                         self.logger.warning(
                             f"Cannot set recently viewed status for {mapped_item}. Error: {e}"
                         )
+                        results.append(SetRecentlyViewedInfo(mapped_item, e))
+        return results
 
 
 class StepResultInfo(T.NamedTuple):
@@ -737,3 +746,10 @@ class StepResultInfo(T.NamedTuple):
             "record_type": self.record_type,
             **self.result.simplify(),
         }
+
+
+class SetRecentlyViewedInfo(T.NamedTuple):
+    """Did the set recently succeed or fail?"""
+
+    sobject: str
+    error: T.Optional[Exception]
diff --git a/datasets/sample.sql b/datasets/sample.sql
new file mode 100644
index 000000000..dc9bc8fdd
--- /dev/null
+++ b/datasets/sample.sql
@@ -0,0 +1,53 @@
+BEGIN TRANSACTION;
+CREATE TABLE "Account" (
+	id INTEGER NOT NULL,
+	"Name" VARCHAR(255),
+	"Description" VARCHAR(255),
+	"NumberOfEmployees" VARCHAR(255),
+	"BillingStreet" VARCHAR(255),
+	"BillingCity" VARCHAR(255),
+	"BillingState" VARCHAR(255),
+	"BillingPostalCode" VARCHAR(255),
+	"BillingCountry" VARCHAR(255),
+	"ShippingStreet" VARCHAR(255),
+	"ShippingCity" VARCHAR(255),
+	"ShippingState" VARCHAR(255),
+	"ShippingPostalCode" VARCHAR(255),
+	"ShippingCountry" VARCHAR(255),
+	"Phone" VARCHAR(255),
+	"Fax" VARCHAR(255),
+	"Website" VARCHAR(255),
+	"AccountNumber" VARCHAR(255),
+	PRIMARY KEY (id)
+);
+INSERT INTO "Account" VALUES(1,'Sample Account for Entitlements','','','','','','','','','','','','','','','','');
+INSERT INTO "Account" VALUES(2,'The Bluth Company','Solid as a rock','6','','','','','','','','','','','','','','');
+INSERT INTO "Account" VALUES(3,'Camacho PLC','Total logistical task-force','59908','2852 Caleb Village Suite 428','Porterside','Maryland','14525','Canada','6070 Davidson Rapids','Gibsonland','North Dakota','62676','Lithuania','221.285.1033','+1-081-230-6073x31438','http://jenkins.info/category/tag/tag/terms/','2679965');
+CREATE TABLE "Contact" (
+	id INTEGER NOT NULL,
+	"FirstName" VARCHAR(255),
+	"LastName" VARCHAR(255),
+	"Salutation" VARCHAR(255),
+	"Email" VARCHAR(255),
+	"Phone" VARCHAR(255),
+	"MobilePhone" VARCHAR(255),
+	"Title" VARCHAR(255),
+	"Birthdate" VARCHAR(255),
+	"AccountId" VARCHAR(255),
+	PRIMARY KEY (id)
+);
+INSERT INTO "Contact" VALUES(1,'Michael','Bluth','','','','','','','2');
+INSERT INTO "Contact" VALUES(2,'Jared','Burnett','Ms.','ja-burnett2011@example.net','372.865.5762x5990','033.134.7156x7943','Systems analyst','2000-04-18','3');
+CREATE TABLE "Opportunity" (
+	id INTEGER NOT NULL,
+	"Name" VARCHAR(255),
+	"CloseDate" VARCHAR(255),
+	"Amount" VARCHAR(255),
+	"StageName" VARCHAR(255),
+	PRIMARY KEY (id)
+);
+INSERT INTO "Opportunity" VALUES(1,'democratic Opportunity','2022-07-27','69.0','In Progress');
+INSERT INTO "Opportunity" VALUES(2,'your Opportunity','2022-10-09','76.0','Closed Won');
+INSERT INTO "Opportunity" VALUES(3,'heart Opportunity','2022-11-04','32.0','Closed Won');
+INSERT INTO "Opportunity" VALUES(4,'treat Opportunity','2022-12-12','137.0','Closed Won');
+COMMIT;


Test patch:
diff --git a/cumulusci/tasks/bulkdata/tests/test_load.py b/cumulusci/tasks/bulkdata/tests/test_load.py
index 491ae9705..13759454d 100644
--- a/cumulusci/tasks/bulkdata/tests/test_load.py
+++ b/cumulusci/tasks/bulkdata/tests/test_load.py
@@ -15,6 +15,7 @@ import responses
 from sqlalchemy import Column, Table, Unicode, create_engine
 
 from cumulusci.core.exceptions import BulkDataException, TaskOptionsError
+from cumulusci.salesforce_api.org_schema import get_org_schema
 from cumulusci.tasks.bulkdata import LoadData
 from cumulusci.tasks.bulkdata.mapping_parser import MappingLookup, MappingStep
 from cumulusci.tasks.bulkdata.step import (
@@ -2192,8 +2193,8 @@ FROM accounts LEFT OUTER JOIN accounts_sf_ids AS accounts_sf_ids_1 ON accounts_s
             ):
                 task()
 
-    @mock.patch("cumulusci.tasks.bulkdata.load.get_org_schema", mock.MagicMock())
-    def test_set_viewed(self):
+    @mock.patch("cumulusci.tasks.bulkdata.load.get_org_schema")
+    def test_set_viewed(self, get_org_schema):
         base_path = os.path.dirname(__file__)
         task = _make_task(
             LoadData,
@@ -2227,6 +2228,13 @@ FROM accounts LEFT OUTER JOIN accounts_sf_ids AS accounts_sf_ids_1 ON accounts_s
 
         task._set_viewed()
 
+        get_org_schema.assert_called_with(
+            task.sf,
+            task.org_config,
+            included_objects={"Account", "Custom__c"},
+            force_recache=mock.ANY,
+        )
+
         assert queries == [
             "SELECT SObjectName FROM TabDefinition WHERE IsCustom = true AND SObjectName IN ('Custom__c')",
             "SELECT Id FROM Account ORDER BY CreatedDate DESC LIMIT 1000 FOR VIEW",
@@ -2418,6 +2426,29 @@ class TestLoadDataIntegrationTests:
             task()
             assert counts == {"Account": [10000], "Contact": [1]}
 
+    @pytest.mark.needs_org()
+    def test_recreate_set_recent_bug(
+        self, sf, create_task, cumulusci_test_repo_root, org_config
+    ):
+        with get_org_schema(sf, org_config, included_objects=["Account"]):
+            pass
+
+        task = create_task(
+            LoadData,
+            {
+                "sql_path": cumulusci_test_repo_root / "datasets/sample.sql",
+                "mapping": cumulusci_test_repo_root / "datasets/mapping.yml",
+                "ignore_row_errors": True,
+            },
+        )
+        task.logger = mock.Mock()
+        results = task()
+        assert results["set_recently_viewed"] == [
+            ("Account", None),
+            ("Contact", None),
+            ("Opportunity", None),
+        ]
+
 
 def _validate_query_for_mapping_step(sql_path, mapping, mapping_step_name, expected):
     """Validate the text of a SQL query"""