Repository: voxel51/fiftyone
Issue: [BUG] SortBy does not create indexes on embedded fields

It looks like some validation logic when creating indexes has regressed some sorting functionality. Sorting no longer works on embedded fields.

```py
import fiftyone.zoo as foz

dataset = foz.load_zoo_dataset("cifar100")
view = dataset.sort_by("ground_truth.label")
```

Error
```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-7-0ff0c1e85a06> in <module>
----> 1 d.sort_by("ground_truth.label")

~/code/fiftyone/fiftyone/core/collections.py in sort_by(self, field_or_expr, reverse)
   1516             a :class:`fiftyone.core.view.DatasetView`
   1517         """
-> 1518         return self._add_view_stage(fos.SortBy(field_or_expr, reverse=reverse))
   1519 
   1520     @view_stage

~/code/fiftyone/fiftyone/core/dataset.py in _add_view_stage(self, stage)
   2253 
   2254     def _add_view_stage(self, stage):
-> 2255         return self.view().add_stage(stage)
   2256 
   2257     def _pipeline(

~/code/fiftyone/fiftyone/core/collections.py in add_stage(self, stage)
    532                 a valid stage for this collection
    533         """
--> 534         return self._add_view_stage(stage)
    535 
    536     @view_stage

~/code/fiftyone/fiftyone/core/view.py in _add_view_stage(self, stage)
    467 
    468     def _add_view_stage(self, stage):
--> 469         stage.validate(self)
    470 
    471         view = copy(self)

~/code/fiftyone/fiftyone/core/stages.py in validate(self, sample_collection)
   2069             # Create an index on the field, if necessary, to make sorting
   2070             # more efficient
-> 2071             sample_collection.create_index(field_or_expr)
   2072 
   2073 

~/code/fiftyone/fiftyone/core/view.py in create_index(self, field, unique)
    265             unique (False): whether to add a uniqueness constraint to the index
    266         """
--> 267         self._dataset.create_index(field, unique=unique)
    268 
    269     def drop_index(self, field):

~/code/fiftyone/fiftyone/core/dataset.py in create_index(self, field, unique)
   2112         """
   2113         if field not in self.get_field_schema():
-> 2114             raise ValueError("Dataset has no field '%s'" % field)
   2115 
   2116         index_info = self._sample_collection.index_information()

ValueError: Dataset has no field 'ground_truth.label'
```

Changed files:
tests/unittests/view_tests.py, fiftyone/core/view.py, fiftyone/core/dataset.py, fiftyone/core/collections.py

diff --git a/fiftyone/core/collections.py b/fiftyone/core/collections.py
index e43d75c3f..d69050611 100644
--- a/fiftyone/core/collections.py
+++ b/fiftyone/core/collections.py
@@ -1795,7 +1795,7 @@ class SampleCollection(object):
         Indexes enable efficient sorting, merging, and other such operations.
 
         Args:
-            field: the field name
+            field: the field name or ``embedded.field.name``
             unique (False): whether to add a uniqueness constraint to the index
         """
         raise NotImplementedError("Subclass must implement create_index()")
@@ -1804,7 +1804,7 @@ class SampleCollection(object):
         """Drops the index on the given field.
 
         Args:
-            field: the field name
+            field: the field name or ``embedded.field.name``
         """
         raise NotImplementedError("Subclass must implement drop_index()")
 
diff --git a/fiftyone/core/dataset.py b/fiftyone/core/dataset.py
index 0d94702ec..74e5e6463 100644
--- a/fiftyone/core/dataset.py
+++ b/fiftyone/core/dataset.py
@@ -2107,10 +2107,10 @@ class Dataset(foc.SampleCollection, metaclass=DatasetSingleton):
         Indexes enable efficient sorting, merging, and other such operations.
 
         Args:
-            field: the field name
+            field: the field name or ``embedded.field.name``
             unique (False): whether to add a uniqueness constraint to the index
         """
-        if field not in self.get_field_schema():
+        if ("." not in field) and (field not in self.get_field_schema()):
             raise ValueError("Dataset has no field '%s'" % field)
 
         index_info = self._sample_collection.index_information()
@@ -2132,15 +2132,15 @@ class Dataset(foc.SampleCollection, metaclass=DatasetSingleton):
         """Drops the index on the given field.
 
         Args:
-            field: the field name
+            field: the field name or ``embedded.field.name``
         """
-        if field not in self.get_field_schema():
-            raise ValueError("Dataset has no field '%s'" % field)
-
         index_info = self._sample_collection.index_information()
         index_map = {v["key"][0][0]: k for k, v in index_info.items()}
 
         if field not in index_map:
+            if ("." not in field) and (field not in self.get_field_schema()):
+                raise ValueError("Dataset has no field '%s'" % field)
+
             raise ValueError("Dataset field '%s' is not indexed" % field)
 
         self._sample_collection.drop_index(index_map[field])
diff --git a/fiftyone/core/view.py b/fiftyone/core/view.py
index 4355e74a5..1e2c28a62 100644
--- a/fiftyone/core/view.py
+++ b/fiftyone/core/view.py
@@ -261,7 +261,7 @@ class DatasetView(foc.SampleCollection):
         Indexes enable efficient sorting, merging, and other such operations.
 
         Args:
-            field: the field name
+            field: the field name or ``embedded.field.name``
             unique (False): whether to add a uniqueness constraint to the index
         """
         self._dataset.create_index(field, unique=unique)
@@ -270,7 +270,7 @@ class DatasetView(foc.SampleCollection):
         """Drops the index on the given field.
 
         Args:
-            field: the field name
+            field: the field name or ``embedded.field.name``
         """
         self._dataset.drop_index(field)
 
diff --git a/tests/unittests/view_tests.py b/tests/unittests/view_tests.py
index ef9448897..2c698233b 100644
--- a/tests/unittests/view_tests.py
+++ b/tests/unittests/view_tests.py
@@ -690,6 +690,16 @@ class ViewStageTests(unittest.TestCase):
         self.dataset.add_sample(self.sample1)
         self.dataset.add_sample(self.sample2)
 
+    def _setUp_classification(self):
+        self.sample1["test_clf"] = fo.Classification(
+            label="friend", confidence=0.9
+        )
+        self.sample1.save()
+        self.sample2["test_clf"] = fo.Classification(
+            label="enemy", confidence=0.99
+        )
+        self.sample2.save()
+
     def _setUp_classifications(self):
         self.sample1["test_clfs"] = fo.Classifications(
             classifications=[
@@ -930,6 +940,13 @@ class ViewStageTests(unittest.TestCase):
         self.assertIs(len(result), 2)
         self.assertEqual(result[0].id, self.sample2.id)
 
+    def test_sort_by_embedded(self):
+        self._setUp_classification()
+
+        result = list(self.dataset.sort_by("test_clf.label"))
+        self.assertEqual(result[0]["test_clf"].label, "enemy")
+        self.assertEqual(result[1]["test_clf"].label, "friend")
+
     def test_take(self):
         result = list(self.dataset.take(1))
         self.assertIs(len(result), 1)