Repository: oppia/oppia

Issue:
The evaluation context is incorrectly constructed for backend evaluation of parameters
See: https://github.com/oppia/oppia/blob/develop/core/domain/platform_feature_services.py#L156. We're currently defaulting all values to None--this isn't correct for a few reasons:
1. It has disparity with Android & web clients
2. It actually will fail validation for EvaluationContext (which requires client_type and user_locale to be defined)
3. It will lead to false results when evaluating platform parameters that depend on client_type and user_locale properties

To fix, I suggest:
1. Rename 'client_type' to 'platform_type'
2. Remove 'user_locale' since it's currently wrong (it represents selected language code & that's not a good proxy for locale), and there isn't an obvious way to derive locale today--we can re-add this in the future once we have a specific need for it


Discussion:
Note that as part of this we may also want to rename 'locale' to actually be 'selected_language_code' since that's all we can really infer here, and it's what we currently use in the frontend.
Note that this was updated to include a more accurate list of "things to fix".

Resolving patch:
diff --git a/core/controllers/platform_feature.py b/core/controllers/platform_feature.py
--- a/core/controllers/platform_feature.py
+++ b/core/controllers/platform_feature.py
@@ -36,10 +36,9 @@ def get(self):
         the given client information.
         """
         context_dict = {
-            'client_type': self.request.get('client_type', None),
+            'platform_type': self.request.get('platform_type', None),
             'browser_type': self.request.get('browser_type', None),
             'app_version': self.request.get('app_version', None),
-            'user_locale': self.request.get('user_locale', None),
         }
         context = (
             platform_feature_services.create_evaluation_context_for_client(
@@ -66,7 +65,7 @@ def get(self):
         # This handler is gated by the dummy_feature flag, i.e. it's only
         # visible when the dummy_feature is enabled.
         if not platform_feature_services.is_feature_enabled(
-                platform_feature_list.PARAM_NAMES.dummy_feature, self.user_id):
+                platform_feature_list.PARAM_NAMES.dummy_feature):
             raise self.PageNotFoundException()
         self.render_json({
             'msg': 'ok'
diff --git a/core/domain/platform_feature_services.py b/core/domain/platform_feature_services.py
--- a/core/domain/platform_feature_services.py
+++ b/core/domain/platform_feature_services.py
@@ -38,7 +38,6 @@
 from core import platform_feature_list
 from core.domain import platform_parameter_domain
 from core.domain import platform_parameter_registry as registry
-from core.domain import user_services
 from core.platform import models
 
 current_user_services = models.Registry.import_current_user_services()
@@ -105,20 +104,18 @@ def evaluate_all_feature_flag_values_for_client(context):
         ALL_FEATURES_NAMES_SET, context)
 
 
-def is_feature_enabled(feature_name, user_id):
+def is_feature_enabled(feature_name):
     """A short-form method for server-side usage. This method evaluates and
     returns the values of the feature flag, using context from the server only.
 
     Args:
         feature_name: str. The name of the feature flag that needs to
             be evaluated.
-        user_id: str. The id of the user with which to evaluate the flag.
 
     Returns:
         bool. The value of the feature flag, True if it's enabled.
     """
-    user_settings = user_services.get_user_settings(user_id)
-    return _evaluate_feature_flag_value_for_server(feature_name, user_settings)
+    return _evaluate_feature_flag_value_for_server(feature_name)
 
 
 def update_feature_flag_rules(
@@ -160,30 +157,18 @@ def _get_server_mode():
         if constants.DEV_MODE else platform_parameter_domain.SERVER_MODES.prod)
 
 
-def _create_evaluation_context_for_server(user_settings):
+def _create_evaluation_context_for_server():
     """Returns evaluation context with information of the server.
 
-    Args:
-        user_settings: UserSettings|None. The settings of the current logged in
-            user, if any.
-
     Returns:
         EvaluationContext. The context for evaluation.
     """
-    language_code = 'en' # Default language code if no preference is set.
-    if (
-            user_settings is not None and
-            user_settings.preferred_site_language_code is not None):
-        language_code = user_settings.preferred_site_language_code
-
-    # TODO(#11073): Properly set app version below, and rename the user_locale
-    # to correspond to a selected language code. Consider also renaming
-    # client_type to something that better encapsulates frontends and backends.
+    # TODO(#11208): Properly set app version below using GAE app version as
+    # part of the server & client context.
     return platform_parameter_domain.EvaluationContext.from_dict(
         {
-            'client_type': 'Backend',
+            'platform_type': 'Backend',
             'app_version': None,
-            'user_locale': language_code,
         },
         {
             'server_mode': _get_server_mode()
@@ -220,20 +205,18 @@ def _evaluate_feature_flag_values_for_context(feature_names_set, context):
     return result_dict
 
 
-def _evaluate_feature_flag_value_for_server(feature_name, user_settings):
+def _evaluate_feature_flag_value_for_server(feature_name):
     """Evaluates and returns the values of the feature flag, using context
     from the server only.
 
     Args:
         feature_name: str. The name of the feature flag that needs to
             be evaluated.
-        user_settings: UserSettings|None. The settings of the current logged in
-            user, if any.
 
     Returns:
         bool. The value of the feature flag, True if it's enabled.
     """
-    context = _create_evaluation_context_for_server(user_settings)
+    context = _create_evaluation_context_for_server()
     values_dict = _evaluate_feature_flag_values_for_context(
         set([feature_name]), context)
     return values_dict[feature_name]
diff --git a/core/domain/platform_parameter_domain.py b/core/domain/platform_parameter_domain.py
--- a/core/domain/platform_parameter_domain.py
+++ b/core/domain/platform_parameter_domain.py
@@ -33,13 +33,11 @@
 FEATURE_STAGES = SERVER_MODES # pylint: disable=invalid-name
 DATA_TYPES = utils.create_enum('bool', 'string', 'number') # pylint: disable=invalid-name
 
-ALLOWED_USER_LOCALES = [
-    lang_dict['id'] for lang_dict in constants.SUPPORTED_SITE_LANGUAGES]
 ALLOWED_SERVER_MODES = [
     SERVER_MODES.dev, SERVER_MODES.test, SERVER_MODES.prod]
 ALLOWED_FEATURE_STAGES = [
     FEATURE_STAGES.dev, FEATURE_STAGES.test, FEATURE_STAGES.prod]
-ALLOWED_CLIENT_TYPES = constants.PLATFORM_PARAMETER_ALLOWED_CLIENT_TYPES
+ALLOWED_PLATFORM_TYPES = constants.PLATFORM_PARAMETER_ALLOWED_PLATFORM_TYPES
 ALLOWED_BROWSER_TYPES = constants.PLATFORM_PARAMETER_ALLOWED_BROWSER_TYPES
 ALLOWED_APP_VERSION_FLAVORS = (
     constants.PLATFORM_PARAMETER_ALLOWED_APP_VERSION_FLAVORS)
@@ -70,22 +68,20 @@ class EvaluationContext(python_utils.OBJECT):
     """Domain object representing the context for parameter evaluation."""
 
     def __init__(
-            self, client_type, browser_type,
-            app_version, user_locale, server_mode):
-        self._client_type = client_type
+            self, platform_type, browser_type, app_version, server_mode):
+        self._platform_type = platform_type
         self._browser_type = browser_type
         self._app_version = app_version
-        self._user_locale = user_locale
         self._server_mode = server_mode
 
     @property
-    def client_type(self):
-        """Returns client type.
+    def platform_type(self):
+        """Returns platform type.
 
         Returns:
-            str. The client type, e.g. 'Web', 'Android'.
+            str. The platform type, e.g. 'Web', 'Android', 'Backend'.
         """
-        return self._client_type
+        return self._platform_type
 
     @property
     def browser_type(self):
@@ -93,31 +89,22 @@ def browser_type(self):
 
         Returns:
             str|None. The client browser type, e.g. 'Chrome', 'FireFox',
-            'Edge'. None if the client type is native, i.e. Android app.
+            'Edge'. None if the platform type is not Web.
         """
         return self._browser_type
 
     @property
     def app_version(self):
+        # TODO(#11208): Update the documentation below to reflect the change
+        # when the GAE app version is used for web & backend.
         """Returns client application version.
 
         Returns:
             str|None. The version of native application, e.g. '1.0.0',
-            None if the client type is web.
+            None if the platform type is Web.
         """
         return self._app_version
 
-    @property
-    def user_locale(self):
-        """Returns client locale.
-
-        Returns:
-            str. The client locale, e.g. 'en', 'es', 'ar'. This must be the id
-            of a supported language specified in SUPPORTED_SITE_LANGUAGES in
-            constants.ts.
-        """
-        return self._user_locale
-
     @property
     def server_mode(self):
         """Returns the server mode of Oppia.
@@ -142,10 +129,8 @@ def is_valid(self):
             parameters.
         """
         return (
-            self._client_type is not None and
-            self._client_type in ALLOWED_CLIENT_TYPES and
-            self._user_locale is not None and
-            self._user_locale in ALLOWED_USER_LOCALES)
+            self._platform_type is not None and
+            self._platform_type in ALLOWED_PLATFORM_TYPES)
 
     def validate(self):
         """Validates the EvaluationContext domain object, raising an exception
@@ -191,10 +176,9 @@ def from_dict(cls, client_context_dict, server_context_dict):
             object.
         """
         return cls(
-            client_context_dict.get('client_type'),
+            client_context_dict.get('platform_type'),
             client_context_dict.get('browser_type'),
             client_context_dict.get('app_version'),
-            client_context_dict.get('user_locale'),
             server_context_dict.get('server_mode'),
         )
 
@@ -203,14 +187,13 @@ class PlatformParameterFilter(python_utils.OBJECT):
     """Domain object for filters in platform parameters."""
 
     SUPPORTED_FILTER_TYPES = [
-        'server_mode', 'user_locale', 'client_type', 'browser_type',
-        'app_version', 'app_version_flavor',
+        'server_mode', 'platform_type', 'browser_type', 'app_version',
+        'app_version_flavor',
     ]
 
     SUPPORTED_OP_FOR_FILTERS = {
         'server_mode': ['='],
-        'user_locale': ['='],
-        'client_type': ['='],
+        'platform_type': ['='],
         'browser_type': ['='],
         'app_version_flavor': ['=', '<', '<=', '>', '>='],
         'app_version': ['=', '<', '<=', '>', '>='],
@@ -276,10 +259,8 @@ def _evaluate_single_value(self, op, value, context):
         matched = False
         if self._type == 'server_mode' and op == '=':
             matched = context.server_mode == value
-        elif self._type == 'user_locale' and op == '=':
-            matched = context.user_locale == value
-        elif self._type == 'client_type' and op == '=':
-            matched = context.client_type == value
+        elif self._type == 'platform_type' and op == '=':
+            matched = context.platform_type == value
         elif self._type == 'browser_type' and op == '=':
             matched = context.browser_type == value
         elif self._type == 'app_version_flavor':
@@ -310,18 +291,12 @@ def validate(self):
                     raise utils.ValidationError(
                         'Invalid server mode \'%s\', must be one of %s.' % (
                             mode, ALLOWED_SERVER_MODES))
-        elif self._type == 'user_locale':
-            for _, locale in self._conditions:
-                if locale not in ALLOWED_USER_LOCALES:
-                    raise utils.ValidationError(
-                        'Invalid user locale \'%s\', must be one of %s.' % (
-                            locale, ALLOWED_USER_LOCALES))
-        elif self._type == 'client_type':
-            for _, client_type in self._conditions:
-                if client_type not in ALLOWED_CLIENT_TYPES:
+        elif self._type == 'platform_type':
+            for _, platform_type in self._conditions:
+                if platform_type not in ALLOWED_PLATFORM_TYPES:
                     raise utils.ValidationError(
-                        'Invalid client type \'%s\', must be one of %s.' % (
-                            client_type, ALLOWED_CLIENT_TYPES))
+                        'Invalid platform type \'%s\', must be one of %s.' % (
+                            platform_type, ALLOWED_PLATFORM_TYPES))
         elif self._type == 'app_version_flavor':
             for _, flavor in self._conditions:
                 if flavor not in ALLOWED_APP_VERSION_FLAVORS:


Test patch:
diff --git a/core/controllers/platform_feature_test.py b/core/controllers/platform_feature_test.py
--- a/core/controllers/platform_feature_test.py
+++ b/core/controllers/platform_feature_test.py
@@ -81,22 +81,20 @@ def test_get_dev_mode_android_client_returns_correct_flag_values(self):
             result = self.get_json(
                 '/platform_features_evaluation_handler',
                 params={
-                    'client_type': 'Android',
+                    'platform_type': 'Android',
                     'app_version': '1.0.0',
-                    'user_locale': 'en',
                 }
             )
             self.assertEqual(
                 result,
                 {self.dev_feature.name: False, self.prod_feature.name: True})
 
-    def test_get_features_invalid_client_type_returns_features_disabled(self):
+    def test_get_features_invalid_platform_type_returns_features_disabled(self):
         with self.swap(constants, 'DEV_MODE', True):
             result = self.get_json(
                 '/platform_features_evaluation_handler',
                 params={
-                    'client_type': 'invalid',
-                    'user_locale': 'en',
+                    'platform_type': 'invalid',
                 },
                 expected_status_int=200
             )
@@ -104,38 +102,11 @@ def test_get_features_invalid_client_type_returns_features_disabled(self):
                 result,
                 {self.dev_feature.name: False, self.prod_feature.name: False})
 
-    def test_get_features_missing_client_type_returns_features_disabled(self):
+    def test_get_features_missing_platform_type_returns_features_disabled(self):
         with self.swap(constants, 'DEV_MODE', True):
             result = self.get_json(
                 '/platform_features_evaluation_handler',
                 params={
-                    'user_locale': 'en',
-                }
-            )
-            self.assertEqual(
-                result,
-                {self.dev_feature.name: False, self.prod_feature.name: False})
-
-    def test_get_features_invalid_user_locale_returns_features_disabled(self):
-        with self.swap(constants, 'DEV_MODE', True):
-            result = self.get_json(
-                '/platform_features_evaluation_handler',
-                params={
-                    'client_type': 'Android',
-                    'user_locale': 'invalid',
-                },
-                expected_status_int=200
-            )
-            self.assertEqual(
-                result,
-                {self.dev_feature.name: False, self.prod_feature.name: False})
-
-    def test_get_features_missing_user_locale_returns_features_disabled(self):
-        with self.swap(constants, 'DEV_MODE', True):
-            result = self.get_json(
-                '/platform_features_evaluation_handler',
-                params={
-                    'client_type': 'Android',
                 }
             )
             self.assertEqual(
@@ -147,9 +118,8 @@ def test_get_features_invalid_version_flavor_raises_400(self):
             resp_dict = self.get_json(
                 '/platform_features_evaluation_handler',
                 params={
-                    'client_type': 'Android',
+                    'platform_type': 'Android',
                     'app_version': '1.0.0-abcdefg-invalid',
-                    'user_locale': 'en',
                 },
                 expected_status_int=400
             )
diff --git a/core/domain/platform_feature_services_test.py b/core/domain/platform_feature_services_test.py
--- a/core/domain/platform_feature_services_test.py
+++ b/core/domain/platform_feature_services_test.py
@@ -24,7 +24,6 @@
 from core.domain import platform_feature_services as feature_services
 from core.domain import platform_parameter_domain
 from core.domain import platform_parameter_registry as registry
-from core.domain import user_services
 from core.tests import test_utils
 import utils
 
@@ -117,19 +116,17 @@ def test_create_evaluation_context_for_client_returns_correct_context(self):
         with self.swap(constants, 'DEV_MODE', True):
             context = feature_services.create_evaluation_context_for_client(
                 {
-                    'client_type': 'Android',
+                    'platform_type': 'Android',
                     'browser_type': None,
                     'app_version': '1.0.0',
-                    'user_locale': 'en',
                 }
             )
             self.assertEqual(
                 context.server_mode,
                 platform_parameter_domain.FEATURE_STAGES.dev)
-            self.assertEqual(context.client_type, 'Android')
+            self.assertEqual(context.platform_type, 'Android')
             self.assertEqual(context.browser_type, None)
             self.assertEqual(context.app_version, '1.0.0')
-            self.assertEqual(context.user_locale, 'en')
 
     def test_get_all_feature_flag_dicts_returns_correct_dicts(self):
         expected_dicts = [
@@ -143,10 +140,9 @@ def test_get_all_feature_flag_dicts_returns_correct_dicts(self):
     def test_get_all_feature_flag_values_in_dev_returns_correct_values(self):
         with self.swap(constants, 'DEV_MODE', True):
             context = feature_services.create_evaluation_context_for_client({
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.0.0',
-                'user_locale': 'en',
             })
             self.assertEqual(
                 feature_services.evaluate_all_feature_flag_values_for_client(
@@ -159,10 +155,9 @@ def test_get_all_feature_flag_values_in_dev_returns_correct_values(self):
     def test_get_all_feature_flag_values_in_prod_returns_correct_values(self):
         with self.swap(constants, 'DEV_MODE', False):
             context = feature_services.create_evaluation_context_for_client({
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.0.0',
-                'user_locale': 'en',
             })
             self.assertEqual(
                 feature_services.evaluate_all_feature_flag_values_for_client(
@@ -175,27 +170,23 @@ def test_get_all_feature_flag_values_in_prod_returns_correct_values(self):
     def test_evaluate_dev_feature_for_dev_server_returns_true(self):
         with self.swap(constants, 'DEV_MODE', True):
             self.assertTrue(
-                feature_services.is_feature_enabled(
-                    self.dev_feature.name, self.user_id))
+                feature_services.is_feature_enabled(self.dev_feature.name))
 
     def test_evaluate_prod_feature_for_dev_server_returns_true(self):
         with self.swap(constants, 'DEV_MODE', True):
             self.assertTrue(
-                feature_services.is_feature_enabled(
-                    self.prod_feature.name, self.user_id))
+                feature_services.is_feature_enabled(self.prod_feature.name))
 
     def test_evaluate_dev_feature_for_prod_server_returns_false(self):
         with self.swap(constants, 'DEV_MODE', False):
             self.assertFalse(
-                feature_services.is_feature_enabled(
-                    self.dev_feature.name, self.user_id))
+                feature_services.is_feature_enabled(self.dev_feature.name))
 
     def test_evaluate_prod_feature_for_prod_server_returns_true(
             self):
         with self.swap(constants, 'DEV_MODE', False):
             self.assertTrue(
-                feature_services.is_feature_enabled(
-                    self.prod_feature.name, self.user_id))
+                feature_services.is_feature_enabled(self.prod_feature.name))
 
     def test_evaluate_feature_for_prod_server_matches_to_backend_filter(
             self):
@@ -214,7 +205,7 @@ def test_evaluate_feature_for_prod_server_matches_to_backend_filter(
                             ],
                         },
                         {
-                            'type': 'client_type',
+                            'type': 'platform_type',
                             'conditions': [
                                 [
                                     '=',
@@ -229,126 +220,12 @@ def test_evaluate_feature_for_prod_server_matches_to_backend_filter(
         )
         with self.swap(constants, 'DEV_MODE', False):
             self.assertTrue(
-                feature_services.is_feature_enabled(
-                    self.prod_feature.name, self.user_id))
-
-    def test_evaluate_feature_for_prod_server_no_user_defaults_match_to_en_lang(
-            self):
-        registry.Registry.update_platform_parameter(
-            self.prod_feature.name, self.user_id, 'edit rules',
-            [
-                {
-                    'filters': [
-                        {
-                            'type': 'server_mode',
-                            'conditions': [
-                                [
-                                    '=',
-                                    platform_parameter_domain.SERVER_MODES.prod
-                                ]
-                            ],
-                        },
-                        {
-                            'type': 'user_locale',
-                            'conditions': [
-                                [
-                                    '=',
-                                    'en'
-                                ]
-                            ],
-                        }
-                    ],
-                    'value_when_matched': True
-                }
-            ]
-        )
-        with self.swap(constants, 'DEV_MODE', False):
-            self.assertTrue(
-                feature_services.is_feature_enabled(
-                    self.prod_feature.name, self.user_id))
-
-    def test_evaluate_feature_for_en_user_defaults_match_to_en_lang(
-            self):
-        user_services.update_preferred_site_language_code(self.user_id, 'en')
-        self.login(self.OWNER_EMAIL)
-        registry.Registry.update_platform_parameter(
-            self.prod_feature.name, self.user_id, 'edit rules',
-            [
-                {
-                    'filters': [
-                        {
-                            'type': 'server_mode',
-                            'conditions': [
-                                [
-                                    '=',
-                                    platform_parameter_domain.SERVER_MODES.prod
-                                ]
-                            ],
-                        },
-                        {
-                            'type': 'user_locale',
-                            'conditions': [
-                                [
-                                    '=',
-                                    'en'
-                                ]
-                            ],
-                        }
-                    ],
-                    'value_when_matched': True
-                }
-            ]
-        )
-        with self.swap(constants, 'DEV_MODE', False):
-            self.assertTrue(
-                feature_services.is_feature_enabled(
-                    self.prod_feature.name, self.user_id))
-
-    def test_evaluate_feature_for_fr_user_defaults_does_not_match_to_en_lang(
-            self):
-        user_services.update_preferred_site_language_code(self.user_id, 'fr')
-        self.login(self.OWNER_EMAIL)
-        registry.Registry.update_platform_parameter(
-            self.prod_feature.name, self.user_id, 'edit rules',
-            [
-                {
-                    'filters': [
-                        {
-                            'type': 'server_mode',
-                            'conditions': [
-                                [
-                                    '=',
-                                    platform_parameter_domain.SERVER_MODES.prod
-                                ]
-                            ],
-                        },
-                        {
-                            'type': 'user_locale',
-                            'conditions': [
-                                [
-                                    '=',
-                                    'en'
-                                ]
-                            ],
-                        }
-                    ],
-                    'value_when_matched': True
-                }
-            ]
-        )
-        # Since the feature is only enable for users who have French selected as
-        # their preferred site language code, the feature shouldn't be enabled
-        # for the current logged in user.
-        with self.swap(constants, 'DEV_MODE', False):
-            self.assertFalse(
-                feature_services.is_feature_enabled(
-                    self.prod_feature.name, self.user_id))
+                feature_services.is_feature_enabled(self.prod_feature.name))
 
     def test_get_feature_flag_values_with_unknown_name_raises_error(self):
         with self.assertRaisesRegexp(
             Exception, 'Unknown feature flag'):
-            feature_services.is_feature_enabled(
-                'feature_that_does_not_exist', self.user_id)
+            feature_services.is_feature_enabled('feature_that_does_not_exist')
 
     def test_update_feature_flag_rules_successfully_updates_rules(self):
         feature_services.update_feature_flag_rules(
@@ -373,8 +250,7 @@ def test_update_feature_flag_rules_successfully_updates_rules(self):
 
         with self.swap(constants, 'DEV_MODE', True):
             self.assertFalse(
-                feature_services.is_feature_enabled(
-                    self.dev_feature.name, self.user_id))
+                feature_services.is_feature_enabled(self.dev_feature.name))
 
     def test_update_feature_flag_rules_with_unknown_name_raises_error(self):
         unknown_name = 'feature_that_does_not_exist'
@@ -396,8 +272,8 @@ def test_update_feature_flag_rules_with_invalid_rules_raises_error(self):
                     {
                         'filters': [
                             {
-                                'type': 'user_locale',
-                                'conditions': [['=', 'en']]
+                                'type': 'app_version',
+                                'conditions': [['=', '1.2.3']]
                             }
                         ],
                         'value_when_matched': True
diff --git a/core/domain/platform_parameter_domain_test.py b/core/domain/platform_parameter_domain_test.py
--- a/core/domain/platform_parameter_domain_test.py
+++ b/core/domain/platform_parameter_domain_test.py
@@ -90,42 +90,25 @@ class EvaluationContextTests(test_utils.GenericTestBase):
     def test_create_context_from_dict_returns_correct_instance(self):
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.0.0',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'dev',
             },
         )
-        self.assertEqual(context.client_type, 'Android')
+        self.assertEqual(context.platform_type, 'Android')
         self.assertEqual(context.browser_type, None)
         self.assertEqual(context.app_version, '1.0.0')
-        self.assertEqual(context.user_locale, 'en')
         self.assertEqual(context.server_mode, 'dev')
 
-    def test_is_valid_with_invalid_client_type_returns_false(self):
+    def test_is_valid_with_invalid_platform_type_returns_false(self):
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'invalid',
+                'platform_type': 'invalid',
                 'browser_type': None,
                 'app_version': '1.0.0',
-                'user_locale': 'en',
-            },
-            {
-                'server_mode': 'dev',
-            },
-        )
-        self.assertFalse(context.is_valid)
-
-    def test_is_valid_with_invalid_user_locale_returns_false(self):
-        context = parameter_domain.EvaluationContext.from_dict(
-            {
-                'client_type': 'Android',
-                'browser_type': None,
-                'app_version': '1.0.0',
-                'user_locale': 'invalid',
             },
             {
                 'server_mode': 'dev',
@@ -136,10 +119,9 @@ def test_is_valid_with_invalid_user_locale_returns_false(self):
     def test_is_valid_with_valid_android_context_returns_true(self):
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.0.0',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'dev',
@@ -150,9 +132,8 @@ def test_is_valid_with_valid_android_context_returns_true(self):
     def test_is_valid_with_valid_web_context_returns_true(self):
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Web',
+                'platform_type': 'Web',
                 'browser_type': 'Chrome',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'dev',
@@ -163,9 +144,8 @@ def test_is_valid_with_valid_web_context_returns_true(self):
     def test_is_valid_with_valid_backend_context_returns_true(self):
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Backend',
+                'platform_type': 'Backend',
                 'app_version': '3.0.0',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'dev',
@@ -176,10 +156,9 @@ def test_is_valid_with_valid_backend_context_returns_true(self):
     def test_validate_with_valid_context_passes_without_exception(self):
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.0.0',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'dev',
@@ -187,28 +166,27 @@ def test_validate_with_valid_context_passes_without_exception(self):
         )
         context.validate()
 
-    def test_validate_with_invalid_client_type_does_not_raise_exception(self):
+    def test_validate_with_invalid_platform_type_does_not_raise_exception(self):
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'invalid',
+                'platform_type': 'invalid',
                 'browser_type': None,
                 'app_version': '1.0.0',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'dev',
             },
         )
-        # No exception should be raised since invalid client types are ignored.
+        # No exception should be raised since invalid platform types are
+        # ignored.
         context.validate()
 
     def test_validate_with_invalid_browser_type_raises_exception(self):
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Web',
+                'platform_type': 'Web',
                 'browser_type': 'Invalid',
                 'app_version': '1.0.0',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'dev',
@@ -221,10 +199,9 @@ def test_validate_with_invalid_browser_type_raises_exception(self):
     def test_validate_with_invalid_app_version_raises_exception(self):
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': 'a.a.a',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'dev',
@@ -238,10 +215,9 @@ def test_validate_with_invalid_app_sub_version_numbers_raises_exception(
             self):
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.0.0.0',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'dev',
@@ -254,10 +230,9 @@ def test_validate_with_invalid_app_sub_version_numbers_raises_exception(
     def test_validate_with_invalid_app_version_flavor_raises_exception(self):
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.0.0-abcedef-invalid',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'dev',
@@ -267,28 +242,12 @@ def test_validate_with_invalid_app_version_flavor_raises_exception(self):
             utils.ValidationError, 'Invalid version flavor \'invalid\''):
             context.validate()
 
-    def test_validate_with_invalid_user_locale_does_not_raise_exception(self):
-        context = parameter_domain.EvaluationContext.from_dict(
-            {
-                'client_type': 'Android',
-                'browser_type': None,
-                'app_version': '1.0.0',
-                'user_locale': 'invalid',
-            },
-            {
-                'server_mode': 'dev',
-            },
-        )
-        # No exception should be raised since invalid user locales are ignored.
-        context.validate()
-
     def test_validate_with_invalid_server_mode_raises_exception(self):
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.0.0',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'invalid',
@@ -303,17 +262,16 @@ class PlatformParameterFilterTests(test_utils.GenericTestBase):
     """Test for the PlatformParameterFilter."""
 
     def _create_example_context(
-            self, client_type='Android', browser_type=None, app_version='1.2.3',
-            user_locale='en', mode='dev'):
+            self, platform_type='Android', browser_type=None,
+            app_version='1.2.3', mode='dev'):
         """Creates and returns an EvaluationContext using the given
         arguments.
         """
         return parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': client_type,
+                'platform_type': platform_type,
                 'browser_type': browser_type,
                 'app_version': app_version,
-                'user_locale': user_locale,
             },
             {
                 'server_mode': mode,
@@ -383,49 +341,34 @@ def test_evaluate_dev_server_mode_filter_with_prod_env_returns_false(self):
         prod_context = self._create_example_context(mode='prod')
         self.assertFalse(filter_domain.evaluate(prod_context))
 
-    def test_evaluate_en_user_locale_filter_with_en_locale_returns_true(self):
-        filter_dict = {'type': 'user_locale', 'conditions': [('=', 'en')]}
-        filter_domain = (
-            parameter_domain
-            .PlatformParameterFilter.from_dict(filter_dict))
-
-        en_context = self._create_example_context(user_locale='en')
-        self.assertTrue(filter_domain.evaluate(en_context))
-
-    def test_evaluate_en_user_locale_filter_with_zh_locale_returns_false(self):
-        filter_dict = {'type': 'user_locale', 'conditions': [('=', 'en')]}
-        filter_domain = (
-            parameter_domain
-            .PlatformParameterFilter.from_dict(filter_dict))
-
-        zh_context = self._create_example_context(user_locale='zh-hans')
-        self.assertFalse(filter_domain.evaluate(zh_context))
-
     def test_eval_backend_client_filter_with_backend_client_returns_true(self):
-        filter_dict = {'type': 'client_type', 'conditions': [('=', 'Backend')]}
+        filter_dict = {
+            'type': 'platform_type',
+            'conditions': [('=', 'Backend')]
+        }
         filter_domain = (
             parameter_domain
             .PlatformParameterFilter.from_dict(filter_dict))
 
-        web_context = self._create_example_context(client_type='Backend')
+        web_context = self._create_example_context(platform_type='Backend')
         self.assertTrue(filter_domain.evaluate(web_context))
 
     def test_evaluate_web_client_filter_with_web_client_returns_true(self):
-        filter_dict = {'type': 'client_type', 'conditions': [('=', 'Web')]}
+        filter_dict = {'type': 'platform_type', 'conditions': [('=', 'Web')]}
         filter_domain = (
             parameter_domain
             .PlatformParameterFilter.from_dict(filter_dict))
 
-        web_context = self._create_example_context(client_type='Web')
+        web_context = self._create_example_context(platform_type='Web')
         self.assertTrue(filter_domain.evaluate(web_context))
 
     def test_evaluate_web_client_filter_with_native_client_returns_false(self):
-        filter_dict = {'type': 'client_type', 'conditions': [('=', 'Web')]}
+        filter_dict = {'type': 'platform_type', 'conditions': [('=', 'Web')]}
         filter_domain = (
             parameter_domain
             .PlatformParameterFilter.from_dict(filter_dict))
 
-        native_context = self._create_example_context(client_type='Android')
+        native_context = self._create_example_context(platform_type='Android')
         self.assertFalse(filter_domain.evaluate(native_context))
 
     def test_evaluate_chrome_browser_filter_with_chrome_returns_true(self):
@@ -1177,24 +1120,14 @@ def test_validate_filter_with_invalid_server_mode_raises_exception(self):
             utils.ValidationError, 'Invalid server mode \'invalid\''):
             filter_domain.validate()
 
-    def test_validate_filter_with_invalid_user_locale_raises_exception(self):
-        filter_domain = (
-            parameter_domain
-            .PlatformParameterFilter.from_dict(
-                {'type': 'user_locale', 'conditions': [('=', 'invalid')]}
-            ))
-        with self.assertRaisesRegexp(
-            utils.ValidationError, 'Invalid user locale \'invalid\''):
-            filter_domain.validate()
-
-    def test_validate_filter_with_invalid_client_type_raises_exception(self):
+    def test_validate_filter_with_invalid_platform_type_raises_exception(self):
         filter_domain = (
             parameter_domain
             .PlatformParameterFilter.from_dict(
-                {'type': 'client_type', 'conditions': [('=', 'invalid')]}
+                {'type': 'platform_type', 'conditions': [('=', 'invalid')]}
             ))
         with self.assertRaisesRegexp(
-            utils.ValidationError, 'Invalid client type \'invalid\''):
+            utils.ValidationError, 'Invalid platform type \'invalid\''):
             filter_domain.validate()
 
     def test_validate_filter_with_invalid_version_expr_raises_exception(self):
@@ -1290,17 +1223,16 @@ def test_evaluation_with_matching_context_returns_true(self):
             {
                 'filters': [
                     {'type': 'app_version', 'conditions': [('=', '1.2.3')]},
-                    {'type': 'user_locale', 'conditions': [('=', 'en')]},
+                    {'type': 'platform_type', 'conditions': [('=', 'Android')]},
                 ],
                 'value_when_matched': 'matched_val',
             },
         )
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.2.3',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'dev',
@@ -1313,17 +1245,16 @@ def test_evaluation_with_unmatching_context_returns_false(self):
             {
                 'filters': [
                     {'type': 'app_version', 'conditions': [('=', '1.2.3')]},
-                    {'type': 'user_locale', 'conditions': [('=', 'en-UK')]},
+                    {'type': 'platform_type', 'conditions': [('=', 'Web')]},
                 ],
                 'value_when_matched': 'matched_val',
             },
         )
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.2.3',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'dev',
@@ -1660,10 +1591,9 @@ def test_evaluate_with_matched_rule_returns_correct_value(self):
 
         dev_context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.2.3',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'dev',
@@ -1696,10 +1626,9 @@ def test_evaluate_without_matched_rule_returns_default_value(self):
 
         prod_context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.2.3',
-                'user_locale': 'en',
             },
             {
                 'server_mode': 'prod',
@@ -1707,78 +1636,7 @@ def test_evaluate_without_matched_rule_returns_default_value(self):
         )
         self.assertEqual(parameter.evaluate(prod_context), '111')
 
-    def test_evaluate_matching_feature_invalid_client_type_returns_def(self):
-        parameter = parameter_domain.PlatformParameter.from_dict({
-            'name': 'parameter_a',
-            'description': 'for test',
-            'data_type': 'string',
-            'rules': [
-                {
-                    'filters': [
-                        {
-                            'type': 'server_mode',
-                            'conditions': [('=', 'dev')]
-                        }
-                    ],
-                    'value_when_matched': '222'
-                }
-            ],
-            'rule_schema_version': (
-                feconf.CURRENT_PLATFORM_PARAMETER_RULE_SCHEMA_VERSION),
-            'default_value': '111',
-            'is_feature': False,
-            'feature_stage': None,
-        })
-
-        dev_context = parameter_domain.EvaluationContext.from_dict(
-            {
-                'client_type': 'invalid',
-                'browser_type': None,
-                'app_version': '1.2.3',
-                'user_locale': 'en',
-            },
-            {
-                'server_mode': 'dev',
-            },
-        )
-        self.assertEqual(parameter.evaluate(dev_context), '111')
-
-    def test_evaluate_matching_feature_missing_client_type_returns_def(self):
-        parameter = parameter_domain.PlatformParameter.from_dict({
-            'name': 'parameter_a',
-            'description': 'for test',
-            'data_type': 'string',
-            'rules': [
-                {
-                    'filters': [
-                        {
-                            'type': 'server_mode',
-                            'conditions': [('=', 'dev')]
-                        }
-                    ],
-                    'value_when_matched': '222'
-                }
-            ],
-            'rule_schema_version': (
-                feconf.CURRENT_PLATFORM_PARAMETER_RULE_SCHEMA_VERSION),
-            'default_value': '111',
-            'is_feature': False,
-            'feature_stage': None,
-        })
-
-        dev_context = parameter_domain.EvaluationContext.from_dict(
-            {
-                'browser_type': None,
-                'app_version': '1.2.3',
-                'user_locale': 'en',
-            },
-            {
-                'server_mode': 'dev',
-            },
-        )
-        self.assertEqual(parameter.evaluate(dev_context), '111')
-
-    def test_evaluate_matching_feature_invalid_user_locale_returns_def(self):
+    def test_evaluate_matching_feature_invalid_platform_type_returns_def(self):
         parameter = parameter_domain.PlatformParameter.from_dict({
             'name': 'parameter_a',
             'description': 'for test',
@@ -1803,10 +1661,9 @@ def test_evaluate_matching_feature_invalid_user_locale_returns_def(self):
 
         dev_context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
+                'platform_type': 'invalid',
                 'browser_type': None,
                 'app_version': '1.2.3',
-                'user_locale': 'invalid',
             },
             {
                 'server_mode': 'dev',
@@ -1814,7 +1671,7 @@ def test_evaluate_matching_feature_invalid_user_locale_returns_def(self):
         )
         self.assertEqual(parameter.evaluate(dev_context), '111')
 
-    def test_evaluate_matching_feature_missing_user_locale_returns_def(self):
+    def test_evaluate_matching_feature_missing_platform_type_returns_def(self):
         parameter = parameter_domain.PlatformParameter.from_dict({
             'name': 'parameter_a',
             'description': 'for test',
@@ -1839,7 +1696,6 @@ def test_evaluate_matching_feature_missing_user_locale_returns_def(self):
 
         dev_context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.2.3',
             },
diff --git a/core/domain/platform_parameter_registry_test.py b/core/domain/platform_parameter_registry_test.py
--- a/core/domain/platform_parameter_registry_test.py
+++ b/core/domain/platform_parameter_registry_test.py
@@ -347,10 +347,9 @@ def test_updated_parameter_is_saved_in_storage(self):
     def test_evaluate_all_parameters(self):
         context = parameter_domain.EvaluationContext.from_dict(
             {
-                'client_type': 'Android',
+                'platform_type': 'Android',
                 'browser_type': None,
                 'app_version': '1.2.3',
-                'user_locale': 'en',
             },
             {
                 'server_mode': FEATURE_STAGES.dev,