Repository: elastic/ecs

Issue:
Add `scaled_float` numeric type support
Add support in ECS for `scaled_float` as an allowed [numeric type](https://www.elastic.co/guide/en/elasticsearch/reference/master/number.html) in the schema field definitions.

A `scaled_float` has an additional parameter, the [`scaling_factor`](https://www.elastic.co/guide/en/elasticsearch/reference/master/number.html). `scaling_factor` is always required for a `scaled_float`.

```json
{
  "my-index-0002" : {
    "mappings" : {
      "properties" : {
        "my_scaled_float_field" : {
          "type" : "scaled_float",
          "scaling_factor" : 1000.0
        }
      }
    }
  }
}
```


Discussion:

Resolving patch:
diff --git a/scripts/generators/beats.py b/scripts/generators/beats.py
--- a/scripts/generators/beats.py
+++ b/scripts/generators/beats.py
@@ -34,7 +34,7 @@ def fieldset_field_array(source_fields, df_whitelist, fieldset_prefix):
     allowed_keys = ['name', 'level', 'required', 'type', 'object_type',
                     'ignore_above', 'multi_fields', 'format', 'input_format',
                     'output_format', 'output_precision', 'description',
-                    'example', 'enabled', 'index', 'path']
+                    'example', 'enabled', 'index', 'path', 'scaling_factor']
     multi_fields_allowed_keys = ['name', 'type', 'norms', 'default_field', 'normalizer', 'ignore_above']
 
     fields = []
diff --git a/scripts/generators/es_template.py b/scripts/generators/es_template.py
--- a/scripts/generators/es_template.py
+++ b/scripts/generators/es_template.py
@@ -61,6 +61,8 @@ def entry_for(field):
             ecs_helpers.dict_copy_existing_keys(field, field_entry, ['norms'])
         elif field['type'] == 'alias':
             ecs_helpers.dict_copy_existing_keys(field, field_entry, ['path'])
+        elif field['type'] == 'scaled_float':
+            ecs_helpers.dict_copy_existing_keys(field, field_entry, ['scaling_factor'])
 
         if 'multi_fields' in field:
             field_entry['fields'] = {}
diff --git a/scripts/schema/cleaner.py b/scripts/schema/cleaner.py
--- a/scripts/schema/cleaner.py
+++ b/scripts/schema/cleaner.py
@@ -159,10 +159,12 @@ def field_mandatory_attributes(field):
     current_field_attributes = sorted(field['field_details'].keys())
     missing_attributes = ecs_helpers.list_subtract(FIELD_MANDATORY_ATTRIBUTES, current_field_attributes)
 
-    # The `alias` type requires a target path.
-    # https://github.com/elastic/ecs/issues/876
+    # `alias` fields require a target `path` attribute.
     if field['field_details'].get('type') == 'alias' and 'path' not in current_field_attributes:
         missing_attributes.append('path')
+    # `scaled_float` fields require a `scaling_factor` attribute.
+    if field['field_details'].get('type') == 'scaled_float' and 'scaling_factor' not in current_field_attributes:
+        missing_attributes.append('scaling_factor')
 
     if len(missing_attributes) > 0:
         msg = "Field is missing the following mandatory attributes: {}.\nFound these: {}.\nField details: {}"


Test patch:
diff --git a/scripts/tests/test_es_template.py b/scripts/tests/test_es_template.py
--- a/scripts/tests/test_es_template.py
+++ b/scripts/tests/test_es_template.py
@@ -122,6 +122,19 @@ def test_entry_for_alias(self):
         }
         self.assertEqual(es_template.entry_for(test_map), exp)
 
+    def test_entry_for_scaled_float(self):
+        test_map = {
+            'name': 'test.scaled_float',
+            'type': 'scaled_float',
+            'scaling_factor': 1000
+        }
+
+        exp = {
+            'type': 'scaled_float',
+            'scaling_factor': 1000
+        }
+        self.assertEqual(es_template.entry_for(test_map), exp)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/scripts/tests/unit/test_schema_cleaner.py b/scripts/tests/unit/test_schema_cleaner.py
--- a/scripts/tests/unit/test_schema_cleaner.py
+++ b/scripts/tests/unit/test_schema_cleaner.py
@@ -164,6 +164,13 @@ def test_field_raises_on_alias_missing_path_attribute(self):
                                     "mandatory attributes: {}".format("path")):
             cleaner.field_mandatory_attributes(field)
 
+    def test_raises_on_missing_scaling_factor(self):
+        field = self.schema_process()['process']['fields']['pid']
+        field['field_details']['type'] = "scaled_float"
+        with self.assertRaisesRegex(ValueError,
+                                    "mandatory attributes: {}".format("scaling_factor")):
+            cleaner.field_mandatory_attributes(field)
+
     def test_field_simple_cleanup(self):
         my_field = {
             'field_details': {