# Copyright 2014 The Oppia Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Controllers for the creator dashboard, notifications, and creating new
activities.
"""

from __future__ import absolute_import  # pylint: disable=import-only-modules
from __future__ import unicode_literals  # pylint: disable=import-only-modules

import logging

from constants import constants
from core.controllers import acl_decorators
from core.controllers import base
from core.domain import collection_domain
from core.domain import collection_services
from core.domain import exp_domain
from core.domain import exp_fetchers
from core.domain import exp_services
from core.domain import feedback_services
from core.domain import role_services
from core.domain import subscription_services
from core.domain import suggestion_services
from core.domain import summary_services
from core.domain import topic_services
from core.domain import user_jobs_continuous
from core.domain import user_services
from core.platform import models
import feconf
import python_utils
import utils

(feedback_models, suggestion_models) = models.Registry.import_models(
    [models.NAMES.feedback, models.NAMES.suggestion])

EXPLORATION_ID_KEY = 'exploration_id'
COLLECTION_ID_KEY = 'collection_id'


class OldNotificationsDashboardRedirectPage(base.BaseHandler):
    """Redirects the old notifications dashboard URL to the new one."""

    @acl_decorators.open_access
    def get(self):
        """Handles GET requests."""
        self.redirect(feconf.NOTIFICATIONS_DASHBOARD_URL, permanent=True)


class OldCommunityDashboardRedirectPage(base.BaseHandler):
    """Redirects the old community dashboard URL to the new one."""

    @acl_decorators.open_access
    def get(self):
        """Handles GET requests."""
        self.redirect('/community-dashboard', permanent=True)


class NotificationsDashboardPage(base.BaseHandler):
    """Page with notifications for the user."""

    @acl_decorators.can_access_creator_dashboard
    def get(self):
        self.render_template(
            'notifications-dashboard-page.mainpage.html')


class NotificationsDashboardHandler(base.BaseHandler):
    """Provides data for the user notifications dashboard."""

    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON

    @acl_decorators.can_access_creator_dashboard
    def get(self):
        """Handles GET requests."""
        job_queued_msec, recent_notifications = (
            user_jobs_continuous.DashboardRecentUpdatesAggregator
            .get_recent_user_changes(self.user_id))

        last_seen_msec = (
            subscription_services.get_last_seen_notifications_msec(
                self.user_id))

        # Replace author_ids with their usernames.
        author_ids = [
            notification['author_id'] for notification in recent_notifications
            if notification['author_id']]
        author_usernames = user_services.get_usernames(author_ids)

        author_id_to_username = {
            None: '',
        }
        for ind, author_id in enumerate(author_ids):
            author_id_to_username[author_id] = author_usernames[ind]
        for notification in recent_notifications:
            notification['author_username'] = (
                author_id_to_username[notification['author_id']])
            del notification['author_id']

        subscription_services.record_user_has_seen_notifications(
            self.user_id, job_queued_msec if job_queued_msec else 0.0)

        self.values.update({
            # This may be None if no job has ever run for this user.
            'job_queued_msec': job_queued_msec,
            # This may be None if this is the first time the user has seen
            # the dashboard.
            'last_seen_msec': last_seen_msec,
            'recent_notifications': recent_notifications,
        })
        self.render_json(self.values)


class OldCreatorDashboardRedirectPage(base.BaseHandler):
    """Redirects the old creator dashboard URL to the new one."""

    @acl_decorators.open_access
    def get(self):
        """Handles GET requests."""
        self.redirect(feconf.CREATOR_DASHBOARD_URL, permanent=True)


class CreatorDashboardPage(base.BaseHandler):
    """Page showing the user's creator dashboard."""

    ADDITIONAL_DEPENDENCY_IDS = ['codemirror']

    @acl_decorators.can_access_creator_dashboard
    def get(self):

        self.render_template('creator-dashboard-page.mainpage.html')


class CreatorDashboardHandler(base.BaseHandler):
    """Provides data for the user's creator dashboard page."""

    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON

    @acl_decorators.can_access_creator_dashboard
    def get(self):
        """Handles GET requests."""

        def _round_average_ratings(rating):
            """Returns the rounded average rating to display on the creator
            dashboard.

            Args:
                rating: float. The rating of the lesson.

            Returns:
                float. The rounded average value of rating.
            """
            return python_utils.ROUND(
                rating, feconf.AVERAGE_RATINGS_DASHBOARD_PRECISION)

        subscribed_exploration_summaries = (
            exp_fetchers.get_exploration_summaries_subscribed_to(
                self.user_id))
        subscribed_collection_summaries = (
            collection_services.get_collection_summaries_subscribed_to(
                self.user_id))

        exploration_ids_subscribed_to = [
            summary.id for summary in subscribed_exploration_summaries]

        exp_summary_dicts = summary_services.get_displayable_exp_summary_dicts(
            subscribed_exploration_summaries)
        collection_summary_dicts = []

        feedback_thread_analytics = (
            feedback_services.get_thread_analytics_multi(
                exploration_ids_subscribed_to))

        # TODO(bhenning): Update this to use unresolved answers from
        # stats_services once the training interface is enabled and it's cheaper
        # to retrieve top answers from stats_services.
        for ind, exploration in enumerate(exp_summary_dicts):
            exploration.update(feedback_thread_analytics[ind].to_dict())

        exp_summary_dicts = sorted(
            exp_summary_dicts,
            key=lambda x: (x['num_open_threads'], x['last_updated_msec']),
            reverse=True)

        if constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            topic_summaries = topic_services.get_all_topic_summaries()
            topic_summary_dicts = [
                summary.to_dict() for summary in topic_summaries]

        if role_services.ACTION_CREATE_COLLECTION in self.user.actions:
            for collection_summary in subscribed_collection_summaries:
                # TODO(sll): Reuse _get_displayable_collection_summary_dicts()
                # in summary_services, instead of replicating it like this.
                collection_summary_dicts.append({
                    'id': collection_summary.id,
                    'title': collection_summary.title,
                    'category': collection_summary.category,
                    'objective': collection_summary.objective,
                    'language_code': collection_summary.language_code,
                    'last_updated_msec': utils.get_time_in_millisecs(
                        collection_summary.collection_model_last_updated),
                    'created_on': utils.get_time_in_millisecs(
                        collection_summary.collection_model_created_on),
                    'status': collection_summary.status,
                    'node_count': collection_summary.node_count,
                    'community_owned': collection_summary.community_owned,
                    'thumbnail_icon_url': (
                        utils.get_thumbnail_icon_url_for_category(
                            collection_summary.category)),
                    'thumbnail_bg_color': utils.get_hex_color_for_category(
                        collection_summary.category),
                })

        dashboard_stats = (
            user_jobs_continuous.UserStatsAggregator.get_dashboard_stats(
                self.user_id))
        dashboard_stats.update({
            'total_open_feedback': feedback_services.get_total_open_threads(
                feedback_thread_analytics)
        })
        if dashboard_stats and dashboard_stats.get('average_ratings'):
            dashboard_stats['average_ratings'] = (
                _round_average_ratings(dashboard_stats['average_ratings']))

        last_week_stats = (
            user_services.get_last_week_dashboard_stats(self.user_id))

        if last_week_stats and len(list(last_week_stats.keys())) != 1:
            logging.error(
                '\'last_week_stats\' should contain only one key-value pair'
                ' denoting last week dashboard stats of the user keyed by a'
                ' datetime string.')
            last_week_stats = None

        if last_week_stats:
            # 'last_week_stats' is a dict with only one key-value pair denoting
            # last week dashboard stats of the user keyed by a datetime string.
            datetime_of_stats = list(last_week_stats.keys())[0]
            last_week_stats_average_ratings = (
                list(last_week_stats.values())[0].get('average_ratings'))
            if last_week_stats_average_ratings:
                last_week_stats[datetime_of_stats]['average_ratings'] = (
                    _round_average_ratings(last_week_stats_average_ratings))

        subscriber_ids = subscription_services.get_all_subscribers_of_creator(
            self.user_id)
        subscribers_settings = user_services.get_users_settings(subscriber_ids)
        subscribers_list = []
        for index, subscriber_settings in enumerate(subscribers_settings):
            subscriber_summary = {
                'subscriber_picture_data_url': (
                    subscriber_settings.profile_picture_data_url),
                'subscriber_username': subscriber_settings.username,
                'subscriber_impact': (
                    user_services.get_user_impact_score(subscriber_ids[index]))
            }

            subscribers_list.append(subscriber_summary)

        user_settings = user_services.get_user_settings(
            self.user_id, strict=False)
        creator_dashboard_display_pref = (
            user_settings.creator_dashboard_display_pref)

        suggestions_created_by_user = suggestion_services.query_suggestions(
            [('author_id', self.user_id),
             (
                 'suggestion_type',
                 suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT)])
        suggestions_which_can_be_reviewed = (
            suggestion_services
            .get_all_suggestions_that_can_be_reviewed_by_user(self.user_id))

        for s in suggestions_created_by_user:
            s.populate_old_value_of_change()

        for s in suggestions_which_can_be_reviewed:
            s.populate_old_value_of_change()

        suggestion_dicts_created_by_user = (
            [s.to_dict() for s in suggestions_created_by_user])
        suggestion_dicts_which_can_be_reviewed = (
            [s.to_dict() for s in suggestions_which_can_be_reviewed])

        ids_of_suggestions_created_by_user = (
            [s['suggestion_id'] for s in suggestion_dicts_created_by_user])
        ids_of_suggestions_which_can_be_reviewed = (
            [s['suggestion_id']
             for s in suggestion_dicts_which_can_be_reviewed])

        threads_linked_to_suggestions_by_user = (
            [t.to_dict() for t in feedback_services.get_multiple_threads(
                ids_of_suggestions_created_by_user)])
        threads_linked_to_suggestions_which_can_be_reviewed = (
            [t.to_dict() for t in feedback_services.get_multiple_threads(
                ids_of_suggestions_which_can_be_reviewed)])

        self.values.update({
            'explorations_list': exp_summary_dicts,
            'collections_list': collection_summary_dicts,
            'dashboard_stats': dashboard_stats,
            'last_week_stats': last_week_stats,
            'subscribers_list': subscribers_list,
            'display_preference': creator_dashboard_display_pref,
            'threads_for_created_suggestions_list': (
                threads_linked_to_suggestions_by_user),
            'threads_for_suggestions_to_review_list': (
                threads_linked_to_suggestions_which_can_be_reviewed),
            'created_suggestions_list': suggestion_dicts_created_by_user,
            'suggestions_to_review_list': suggestion_dicts_which_can_be_reviewed
        })
        if constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            self.values.update({
                'topic_summary_dicts': topic_summary_dicts
            })
        self.render_json(self.values)

    @acl_decorators.can_access_creator_dashboard
    def post(self):
        creator_dashboard_display_pref = self.payload.get('display_preference')
        user_services.update_user_creator_dashboard_display(
            self.user_id, creator_dashboard_display_pref)
        self.render_json({})


class NotificationsHandler(base.BaseHandler):
    """Provides data about unseen notifications."""

    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON

    @acl_decorators.can_access_creator_dashboard
    def get(self):
        """Handles GET requests."""
        num_unseen_notifications = 0
        last_seen_msec = (
            subscription_services.get_last_seen_notifications_msec(
                self.user_id))
        _, recent_notifications = (
            user_jobs_continuous.DashboardRecentUpdatesAggregator
            .get_recent_user_changes(self.user_id))
        for notification in recent_notifications:
            if (notification['last_updated_ms'] > last_seen_msec and
                    notification['author_id'] != self.user_id):
                num_unseen_notifications += 1

        self.render_json({
            'num_unseen_notifications': num_unseen_notifications,
        })


class NewExplorationHandler(base.BaseHandler):
    """Creates a new exploration."""

    @acl_decorators.can_create_exploration
    def post(self):
        """Handles POST requests."""
        title = self.payload.get('title', feconf.DEFAULT_EXPLORATION_TITLE)

        new_exploration_id = exp_fetchers.get_new_exploration_id()
        exploration = exp_domain.Exploration.create_default_exploration(
            new_exploration_id, title=title)
        exp_services.save_new_exploration(self.user_id, exploration)

        self.render_json({
            EXPLORATION_ID_KEY: new_exploration_id
        })


class NewCollectionHandler(base.BaseHandler):
    """Creates a new collection."""

    @acl_decorators.can_create_collection
    def post(self):
        """Handles POST requests."""
        new_collection_id = collection_services.get_new_collection_id()
        collection = collection_domain.Collection.create_default_collection(
            new_collection_id)
        collection_services.save_new_collection(self.user_id, collection)

        self.render_json({
            COLLECTION_ID_KEY: new_collection_id
        })


class UploadExplorationHandler(base.BaseHandler):
    """Uploads a new exploration."""

    @acl_decorators.can_upload_exploration
    def post(self):
        """Handles POST requests."""
        yaml_content = self.request.get('yaml_file')

        new_exploration_id = exp_fetchers.get_new_exploration_id()
        if constants.ALLOW_YAML_FILE_UPLOAD:
            exp_services.save_new_exploration_from_yaml_and_assets(
                self.user_id, yaml_content, new_exploration_id, [],
                strip_voiceovers=True)
            self.render_json({
                EXPLORATION_ID_KEY: new_exploration_id
            })
        else:
            raise self.InvalidInputException(
                'This server does not allow file uploads.')

core/controllers/creator_dashboard.py
# Copyright 2014 The Oppia Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Controllers for the creator dashboard, notifications, and creating new
activities.
"""

from __future__ import absolute_import  # pylint: disable=import-only-modules
from __future__ import unicode_literals  # pylint: disable=import-only-modules

import logging

from constants import constants
from core.controllers import acl_decorators
from core.controllers import base
from core.domain import collection_domain
from core.domain import collection_services
from core.domain import exp_domain
from core.domain import exp_fetchers
from core.domain import exp_services
from core.domain import feedback_services
from core.domain import role_services
from core.domain import subscription_services
from core.domain import suggestion_services
from core.domain import summary_services
from core.domain import topic_services
from core.domain import user_jobs_continuous
from core.domain import user_services
from core.platform import models
import feconf
import python_utils
import utils

(feedback_models, suggestion_models) = models.Registry.import_models(
    [models.NAMES.feedback, models.NAMES.suggestion])

EXPLORATION_ID_KEY = 'exploration_id'
COLLECTION_ID_KEY = 'collection_id'


class OldNotificationsDashboardRedirectPage(base.BaseHandler):
    """Redirects the old notifications dashboard URL to the new one."""

    @acl_decorators.open_access
    def get(self):
        """Handles GET requests."""
        self.redirect(feconf.NOTIFICATIONS_DASHBOARD_URL, permanent=True)


class OldCommunityDashboardRedirectPage(base.BaseHandler):
    """Redirects the old community dashboard URL to the new one."""

    @acl_decorators.open_access
    def get(self):
        """Handles GET requests."""
        self.redirect('/community-dashboard', permanent=True)


class NotificationsDashboardPage(base.BaseHandler):
    """Page with notifications for the user."""

    @acl_decorators.can_access_creator_dashboard
    def get(self):
        self.render_template(
            'notifications-dashboard-page.mainpage.html')


class NotificationsDashboardHandler(base.BaseHandler):
    """Provides data for the user notifications dashboard."""

    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON

    @acl_decorators.can_access_creator_dashboard
    def get(self):
        """Handles GET requests."""
        job_queued_msec, recent_notifications = (
            user_jobs_continuous.DashboardRecentUpdatesAggregator
            .get_recent_user_changes(self.user_id))

        last_seen_msec = (
            subscription_services.get_last_seen_notifications_msec(
                self.user_id))

        # Replace author_ids with their usernames.
        author_ids = [
            notification['author_id'] for notification in recent_notifications
            if notification['author_id']]
        author_usernames = user_services.get_usernames(author_ids)

        author_id_to_username = {
            None: '',
        }
        for ind, author_id in enumerate(author_ids):
            author_id_to_username[author_id] = author_usernames[ind]
        for notification in recent_notifications:
            notification['author_username'] = (
                author_id_to_username[notification['author_id']])
            del notification['author_id']

        subscription_services.record_user_has_seen_notifications(
            self.user_id, job_queued_msec if job_queued_msec else 0.0)

        self.values.update({
            # This may be None if no job has ever run for this user.
            'job_queued_msec': job_queued_msec,
            # This may be None if this is the first time the user has seen
            # the dashboard.
            'last_seen_msec': last_seen_msec,
            'recent_notifications': recent_notifications,
        })
        self.render_json(self.values)


class OldCreatorDashboardRedirectPage(base.BaseHandler):
    """Redirects the old creator dashboard URL to the new one."""

    @acl_decorators.open_access
    def get(self):
        """Handles GET requests."""
        self.redirect(feconf.CREATOR_DASHBOARD_URL, permanent=True)


class CreatorDashboardPage(base.BaseHandler):
    """Page showing the user's creator dashboard."""

    ADDITIONAL_DEPENDENCY_IDS = ['codemirror']

    @acl_decorators.can_access_creator_dashboard
    def get(self):

        self.render_template('creator-dashboard-page.mainpage.html')


class CreatorDashboardHandler(base.BaseHandler):
    """Provides data for the user's creator dashboard page."""

    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON

    @acl_decorators.can_access_creator_dashboard
    def get(self):
        """Handles GET requests."""

        def _round_average_ratings(rating):
            """Returns the rounded average rating to display on the creator
            dashboard.

            Args:
                rating: float. The rating of the lesson.

            Returns:
                float. The rounded average value of rating.
            """
            return python_utils.ROUND(
                rating, feconf.AVERAGE_RATINGS_DASHBOARD_PRECISION)

        subscribed_exploration_summaries = (
            exp_fetchers.get_exploration_summaries_subscribed_to(
                self.user_id))
        subscribed_collection_summaries = (
            collection_services.get_collection_summaries_subscribed_to(
                self.user_id))

        exploration_ids_subscribed_to = [
            summary.id for summary in subscribed_exploration_summaries]

        exp_summary_dicts = summary_services.get_displayable_exp_summary_dicts(
            subscribed_exploration_summaries)
        collection_summary_dicts = []

        feedback_thread_analytics = (
            feedback_services.get_thread_analytics_multi(
                exploration_ids_subscribed_to))

        # TODO(bhenning): Update this to use unresolved answers from
        # stats_services once the training interface is enabled and it's cheaper
        # to retrieve top answers from stats_services.
        for ind, exploration in enumerate(exp_summary_dicts):
            exploration.update(feedback_thread_analytics[ind].to_dict())

        exp_summary_dicts = sorted(
            exp_summary_dicts,
            key=lambda x: (x['num_open_threads'], x['last_updated_msec']),
            reverse=True)

        if constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            topic_summaries = topic_services.get_all_topic_summaries()
            topic_summary_dicts = [
                summary.to_dict() for summary in topic_summaries]

        if role_services.ACTION_CREATE_COLLECTION in self.user.actions:
            for collection_summary in subscribed_collection_summaries:
                # TODO(sll): Reuse _get_displayable_collection_summary_dicts()
                # in summary_services, instead of replicating it like this.
                collection_summary_dicts.append({
                    'id': collection_summary.id,
                    'title': collection_summary.title,
                    'category': collection_summary.category,
                    'objective': collection_summary.objective,
                    'language_code': collection_summary.language_code,
                    'last_updated_msec': utils.get_time_in_millisecs(
                        collection_summary.collection_model_last_updated),
                    'created_on': utils.get_time_in_millisecs(
                        collection_summary.collection_model_created_on),
                    'status': collection_summary.status,
                    'node_count': collection_summary.node_count,
                    'community_owned': collection_summary.community_owned,
                    'thumbnail_icon_url': (
                        utils.get_thumbnail_icon_url_for_category(
                            collection_summary.category)),
                    'thumbnail_bg_color': utils.get_hex_color_for_category(
                        collection_summary.category),
                })

        dashboard_stats = (
            user_jobs_continuous.UserStatsAggregator.get_dashboard_stats(
                self.user_id))
        dashboard_stats.update({
            'total_open_feedback': feedback_services.get_total_open_threads(
                feedback_thread_analytics)
        })
        if dashboard_stats and dashboard_stats.get('average_ratings'):
            dashboard_stats['average_ratings'] = (
                _round_average_ratings(dashboard_stats['average_ratings']))

        last_week_stats = (
            user_services.get_last_week_dashboard_stats(self.user_id))

        if last_week_stats and len(list(last_week_stats.keys())) != 1:
            logging.error(
                '\'last_week_stats\' should contain only one key-value pair'
                ' denoting last week dashboard stats of the user keyed by a'
                ' datetime string.')
            last_week_stats = None

        if last_week_stats:
            # 'last_week_stats' is a dict with only one key-value pair denoting
            # last week dashboard stats of the user keyed by a datetime string.
            datetime_of_stats = list(last_week_stats.keys())[0]
            last_week_stats_average_ratings = (
                list(last_week_stats.values())[0].get('average_ratings'))
            if last_week_stats_average_ratings:
                last_week_stats[datetime_of_stats]['average_ratings'] = (
                    _round_average_ratings(last_week_stats_average_ratings))

        subscriber_ids = subscription_services.get_all_subscribers_of_creator(
            self.user_id)
        subscribers_settings = user_services.get_users_settings(subscriber_ids)
        subscribers_list = []
        for index, subscriber_settings in enumerate(subscribers_settings):
            subscriber_summary = {
                'subscriber_picture_data_url': (
                    subscriber_settings.profile_picture_data_url),
                'subscriber_username': subscriber_settings.username,
                'subscriber_impact': (
                    user_services.get_user_impact_score(subscriber_ids[index]))
            }

            subscribers_list.append(subscriber_summary)

        user_settings = user_services.get_user_settings(
            self.user_id, strict=False)
        creator_dashboard_display_pref = (
            user_settings.creator_dashboard_display_pref)

        suggestions_created_by_user = suggestion_services.query_suggestions(
            [('author_id', self.user_id),
             (
                 'suggestion_type',
                 suggestion_models.SUGGESTION_TYPE_EDIT_STATE_CONTENT)])
        suggestions_which_can_be_reviewed = (
            suggestion_services
            .get_all_suggestions_that_can_be_reviewed_by_user(self.user_id))

        for s in suggestions_created_by_user:
            s.populate_old_value_of_change()

        for s in suggestions_which_can_be_reviewed:
            s.populate_old_value_of_change()

        suggestion_dicts_created_by_user = (
            [s.to_dict() for s in suggestions_created_by_user])
        suggestion_dicts_which_can_be_reviewed = (
            [s.to_dict() for s in suggestions_which_can_be_reviewed])

        ids_of_suggestions_created_by_user = (
            [s['suggestion_id'] for s in suggestion_dicts_created_by_user])
        ids_of_suggestions_which_can_be_reviewed = (
            [s['suggestion_id']
             for s in suggestion_dicts_which_can_be_reviewed])

        threads_linked_to_suggestions_by_user = (
            [t.to_dict() for t in feedback_services.get_multiple_threads(
                ids_of_suggestions_created_by_user)])
        threads_linked_to_suggestions_which_can_be_reviewed = (
            [t.to_dict() for t in feedback_services.get_multiple_threads(
                ids_of_suggestions_which_can_be_reviewed)])

        self.values.update({
            'explorations_list': exp_summary_dicts,
            'collections_list': collection_summary_dicts,
            'dashboard_stats': dashboard_stats,
            'last_week_stats': last_week_stats,
            'subscribers_list': subscribers_list,
            'display_preference': creator_dashboard_display_pref,
            'threads_for_created_suggestions_list': (
                threads_linked_to_suggestions_by_user),
            'threads_for_suggestions_to_review_list': (
                threads_linked_to_suggestions_which_can_be_reviewed),
            'created_suggestions_list': suggestion_dicts_created_by_user,
            'suggestions_to_review_list': suggestion_dicts_which_can_be_reviewed
        })
        if constants.ENABLE_NEW_STRUCTURE_PLAYERS:
            self.values.update({
                'topic_summary_dicts': topic_summary_dicts
            })
        else:
            self.values.update({
                'topic_summary_dicts': []
            })

        self.render_json(self.values)

    @acl_decorators.can_access_creator_dashboard
    def post(self):
        creator_dashboard_display_pref = self.payload.get('display_preference')
        user_services.update_user_creator_dashboard_display(
            self.user_id, creator_dashboard_display_pref)
        self.render_json({})


class NotificationsHandler(base.BaseHandler):
    """Provides data about unseen notifications."""

    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON

    @acl_decorators.can_access_creator_dashboard
    def get(self):
        """Handles GET requests."""
        num_unseen_notifications = 0
        last_seen_msec = (
            subscription_services.get_last_seen_notifications_msec(
                self.user_id))
        _, recent_notifications = (
            user_jobs_continuous.DashboardRecentUpdatesAggregator
            .get_recent_user_changes(self.user_id))
        for notification in recent_notifications:
            if (notification['last_updated_ms'] > last_seen_msec and
                    notification['author_id'] != self.user_id):
                num_unseen_notifications += 1

        self.render_json({
            'num_unseen_notifications': num_unseen_notifications,
        })


class NewExplorationHandler(base.BaseHandler):
    """Creates a new exploration."""

    @acl_decorators.can_create_exploration
    def post(self):
        """Handles POST requests."""
        title = self.payload.get('title', feconf.DEFAULT_EXPLORATION_TITLE)

        new_exploration_id = exp_fetchers.get_new_exploration_id()
        exploration = exp_domain.Exploration.create_default_exploration(
            new_exploration_id, title=title)
        exp_services.save_new_exploration(self.user_id, exploration)

        self.render_json({
            EXPLORATION_ID_KEY: new_exploration_id
        })


class NewCollectionHandler(base.BaseHandler):
    """Creates a new collection."""

    @acl_decorators.can_create_collection
    def post(self):
        """Handles POST requests."""
        new_collection_id = collection_services.get_new_collection_id()
        collection = collection_domain.Collection.create_default_collection(
            new_collection_id)
        collection_services.save_new_collection(self.user_id, collection)

        self.render_json({
            COLLECTION_ID_KEY: new_collection_id
        })


class UploadExplorationHandler(base.BaseHandler):
    """Uploads a new exploration."""

    @acl_decorators.can_upload_exploration
    def post(self):
        """Handles POST requests."""
        yaml_content = self.request.get('yaml_file')

        new_exploration_id = exp_fetchers.get_new_exploration_id()
        if constants.ALLOW_YAML_FILE_UPLOAD:
            exp_services.save_new_exploration_from_yaml_and_assets(
                self.user_id, yaml_content, new_exploration_id, [],
                strip_voiceovers=True)
            self.render_json({
                EXPLORATION_ID_KEY: new_exploration_id
            })
        else:
            raise self.InvalidInputException(
                'This server does not allow file uploads.')

core/controllers/creator_dashboard.py
diff --git a/core/controllers/creator_dashboard.py b/core/controllers/creator_dashboard.py
index 71747fdfff8ca..582281dd05e30 100644
--- a/core/controllers/creator_dashboard.py
+++ b/core/controllers/creator_dashboard.py
@@ -324,6 +324,11 @@ def _round_average_ratings(rating):
             self.values.update({
                 'topic_summary_dicts': topic_summary_dicts
             })
+        else:
+            self.values.update({
+                'topic_summary_dicts': []
+            })
+
         self.render_json(self.values)
 
     @acl_decorators.can_access_creator_dashboard
diff --git a/core/controllers/creator_dashboard_test.py b/core/controllers/creator_dashboard_test.py
index eece08e55ace6..175c01e940df3 100644
--- a/core/controllers/creator_dashboard_test.py
+++ b/core/controllers/creator_dashboard_test.py
@@ -776,7 +776,7 @@ def test_get_no_topic_summary_dicts_with_new_structure_players_disabled(
         self.login(self.OWNER_EMAIL)
         with self.swap(constants, 'ENABLE_NEW_STRUCTURE_PLAYERS', False):
             response = self.get_json(feconf.CREATOR_DASHBOARD_DATA_URL)
-            self.assertIsNone(response.get('topic_summary_dicts'))
+            self.assertEqual(response.get('topic_summary_dicts'), [])
         self.logout()
 
     def test_can_update_display_preference(self):
diff --git a/core/templates/components/forms/custom-forms-directives/apply-validation.directive.ts b/core/templates/components/forms/custom-forms-directives/apply-validation.directive.ts
index 483a846c18dac..c2002728dd59e 100644
--- a/core/templates/components/forms/custom-forms-directives/apply-validation.directive.ts
+++ b/core/templates/components/forms/custom-forms-directives/apply-validation.directive.ts
@@ -20,13 +20,13 @@ require('filters/string-utility-filters/underscores-to-camel-case.filter.ts');
 
 interface InteractionValidator {
   'id': string;
-  'min_value'?: number;
-  'max_value'?: number;
+  'min_value': number;
+  'max_value': number;
 }
 
 interface ApplyValidationCustomScope extends ng.IScope {
-  $ctrl?: {
-    validators?: () => InteractionValidator[];
+  $ctrl: {
+    validators: () => InteractionValidator[];
   }
 }
 
diff --git a/core/templates/domain/admin/job-data-object.factory.ts b/core/templates/domain/admin/job-data-object.factory.ts
index 66bb29846734d..237a9025687e7 100644
--- a/core/templates/domain/admin/job-data-object.factory.ts
+++ b/core/templates/domain/admin/job-data-object.factory.ts
@@ -25,11 +25,14 @@ export interface JobDataBackendDict {
   'job_type': string;
   'status_code': string;
   'error': string;
-  'can_be_canceled'?: boolean;
   'is_cancelable': boolean;
   'id': string;
   'time_started_msec': number;
   'human_readable_time_started': string;
+  // This property is optional because this object factory is used for
+  // both 'recent_job_data' and 'unfinished_job_data' in admin page.
+  // 'recent_job_data' doesn't have this property.
+  'can_be_canceled'?: boolean;
 }
 
 export class Job {
diff --git a/core/templates/domain/collection/CollectionObjectFactorySpec.ts b/core/templates/domain/collection/CollectionObjectFactorySpec.ts
index c2710513e2ae0..6516f506e05eb 100644
--- a/core/templates/domain/collection/CollectionObjectFactorySpec.ts
+++ b/core/templates/domain/collection/CollectionObjectFactorySpec.ts
@@ -58,7 +58,30 @@ describe('Collection object factory', () => {
   var _addCollectionNode = function(explorationId) {
     var collectionNodeBackendObject = {
       exploration_id: explorationId,
-      exploration: {}
+      exploration_summary: {
+        last_updated_msec: 1591296737470.528,
+        community_owned: false,
+        objective: 'Test Objective',
+        id: '44LKoKLlIbGe',
+        num_views: 0,
+        thumbnail_icon_url: '/subjects/Algebra.svg',
+        human_readable_contributors_summary: {},
+        language_code: 'en',
+        thumbnail_bg_color: '#cd672b',
+        created_on_msec: 1591296635736.666,
+        ratings: {
+          1: 0,
+          2: 0,
+          3: 0,
+          4: 0,
+          5: 0
+        },
+        status: 'public',
+        tags: [],
+        activity_type: 'exploration',
+        category: 'Algebra',
+        title: 'Test Title'
+      }
     };
     return _sampleCollection.addCollectionNode(
       collectionNodeObjectFactory.create(collectionNodeBackendObject));
@@ -88,7 +111,30 @@ describe('Collection object factory', () => {
     () => {
       var collectionNodeBackendObject = {
         exploration_id: 'exp_id0',
-        exploration: {}
+        exploration_summary: {
+          last_updated_msec: 1591296737470.528,
+          community_owned: false,
+          objective: 'Test Objective',
+          id: '44LKoKLlIbGe',
+          num_views: 0,
+          thumbnail_icon_url: '/subjects/Algebra.svg',
+          human_readable_contributors_summary: {},
+          language_code: 'en',
+          thumbnail_bg_color: '#cd672b',
+          created_on_msec: 1591296635736.666,
+          ratings: {
+            1: 0,
+            2: 0,
+            3: 0,
+            4: 0,
+            5: 0
+          },
+          status: 'public',
+          tags: [],
+          activity_type: 'exploration',
+          category: 'Algebra',
+          title: 'Test Title'
+        }
       };
       var collection = collectionObjectFactory.create({
         id: 'collection_id',
@@ -119,7 +165,30 @@ describe('Collection object factory', () => {
 
       var collectionNodeBackendObject = {
         exploration_id: 'exp_id0',
-        exploration: {}
+        exploration_summary: {
+          last_updated_msec: 1591296737470.528,
+          community_owned: false,
+          objective: 'Test Objective',
+          id: '44LKoKLlIbGe',
+          num_views: 0,
+          thumbnail_icon_url: '/subjects/Algebra.svg',
+          human_readable_contributors_summary: {},
+          language_code: 'en',
+          thumbnail_bg_color: '#cd672b',
+          created_on_msec: 1591296635736.666,
+          ratings: {
+            1: 0,
+            2: 0,
+            3: 0,
+            4: 0,
+            5: 0
+          },
+          status: 'public',
+          tags: [],
+          activity_type: 'exploration',
+          category: 'Algebra',
+          title: 'Test Title'
+        }
       };
       var collectionNode = collectionNodeObjectFactory.create(
         collectionNodeBackendObject);
@@ -140,7 +209,30 @@ describe('Collection object factory', () => {
   it('should not add duplicate explorations', () => {
     var collectionNodeBackendObject = {
       exploration_id: 'exp_id0',
-      exploration: {}
+      exploration_summary: {
+        last_updated_msec: 1591296737470.528,
+        community_owned: false,
+        objective: 'Test Objective',
+        id: '44LKoKLlIbGe',
+        num_views: 0,
+        thumbnail_icon_url: '/subjects/Algebra.svg',
+        human_readable_contributors_summary: {},
+        language_code: 'en',
+        thumbnail_bg_color: '#cd672b',
+        created_on_msec: 1591296635736.666,
+        ratings: {
+          1: 0,
+          2: 0,
+          3: 0,
+          4: 0,
+          5: 0
+        },
+        status: 'public',
+        tags: [],
+        activity_type: 'exploration',
+        category: 'Algebra',
+        title: 'Test Title'
+      }
     };
     var collectionNode = collectionNodeObjectFactory.create(
       collectionNodeBackendObject);
@@ -158,11 +250,57 @@ describe('Collection object factory', () => {
 
     var collectionNodeBackendObject1 = {
       exploration_id: 'exp_id0',
-      exploration: {}
+      exploration_summary: {
+        last_updated_msec: 1591296737470.528,
+        community_owned: false,
+        objective: 'Test Objective',
+        id: '44LKoKLlIbGe',
+        num_views: 0,
+        thumbnail_icon_url: '/subjects/Algebra.svg',
+        human_readable_contributors_summary: {},
+        language_code: 'en',
+        thumbnail_bg_color: '#cd672b',
+        created_on_msec: 1591296635736.666,
+        ratings: {
+          1: 0,
+          2: 0,
+          3: 0,
+          4: 0,
+          5: 0
+        },
+        status: 'public',
+        tags: [],
+        activity_type: 'exploration',
+        category: 'Algebra',
+        title: 'Test Title'
+      }
     };
     var collectionNodeBackendObject2 = {
       exploration_id: 'exp_id1',
-      exploration: {}
+      exploration_summary: {
+        last_updated_msec: 1591296737470.528,
+        community_owned: false,
+        objective: 'Test Objective',
+        id: '44LKoKLlIbGe',
+        num_views: 0,
+        thumbnail_icon_url: '/subjects/Algebra.svg',
+        human_readable_contributors_summary: {},
+        language_code: 'en',
+        thumbnail_bg_color: '#cd672b',
+        created_on_msec: 1591296635736.666,
+        ratings: {
+          1: 0,
+          2: 0,
+          3: 0,
+          4: 0,
+          5: 0
+        },
+        status: 'public',
+        tags: [],
+        activity_type: 'exploration',
+        category: 'Algebra',
+        title: 'Test Title'
+      }
     };
     var collectionNode1 = collectionNodeObjectFactory.create(
       collectionNodeBackendObject1);
@@ -188,7 +326,30 @@ describe('Collection object factory', () => {
       expect(_getCollectionNode('exp_id0')).toBeUndefined();
       var collectionNodeBackendObject = {
         exploration_id: 'exp_id0',
-        exploration: {}
+        exploration_summary: {
+          last_updated_msec: 1591296737470.528,
+          community_owned: false,
+          objective: 'Test Objective',
+          id: '44LKoKLlIbGe',
+          num_views: 0,
+          thumbnail_icon_url: '/subjects/Algebra.svg',
+          human_readable_contributors_summary: {},
+          language_code: 'en',
+          thumbnail_bg_color: '#cd672b',
+          created_on_msec: 1591296635736.666,
+          ratings: {
+            1: 0,
+            2: 0,
+            3: 0,
+            4: 0,
+            5: 0
+          },
+          status: 'public',
+          tags: [],
+          activity_type: 'exploration',
+          category: 'Algebra',
+          title: 'Test Title'
+        }
       };
       _sampleCollection.addCollectionNode(
         collectionNodeObjectFactory.create(collectionNodeBackendObject));
diff --git a/core/templates/domain/collection/CollectionRightsObjectFactory.ts b/core/templates/domain/collection/CollectionRightsObjectFactory.ts
index 10f2a9504e94e..03c0a9f7878e0 100644
--- a/core/templates/domain/collection/CollectionRightsObjectFactory.ts
+++ b/core/templates/domain/collection/CollectionRightsObjectFactory.ts
@@ -23,10 +23,10 @@ import { downgradeInjectable } from '@angular/upgrade/static';
 import { Injectable } from '@angular/core';
 
 export interface CollectionRightsBackendDict {
-  'collection_id'?: number;
-  'can_edit'?: boolean;
-  'can_unpublish'?: boolean;
-  'is_private'?: boolean;
+  'collection_id': number;
+  'can_edit': boolean;
+  'can_unpublish': boolean;
+  'is_private': boolean;
   'owner_names': string[];
 }
 
@@ -135,7 +135,11 @@ export class CollectionRightsObjectFactory {
   // pass validation tests.
   createEmptyCollectionRights(): CollectionRights {
     return new CollectionRights({
-      owner_names: []
+      owner_names: [],
+      collection_id: null,
+      can_edit: null,
+      can_unpublish: null,
+      is_private: null
     });
   }
 }
diff --git a/core/templates/domain/collection/CollectionRightsObjectFactorySpec.ts b/core/templates/domain/collection/CollectionRightsObjectFactorySpec.ts
index 9e1051e810689..791bed04750fe 100644
--- a/core/templates/domain/collection/CollectionRightsObjectFactorySpec.ts
+++ b/core/templates/domain/collection/CollectionRightsObjectFactorySpec.ts
@@ -152,10 +152,10 @@ describe('Collection rights object factory', () => {
       collectionRightsObjectFactory.createEmptyCollectionRights());
 
     expect(
-      emptyCollectionRightsBackendObject.getCollectionId()).toBeUndefined();
-    expect(emptyCollectionRightsBackendObject.canEdit()).toBeUndefined();
-    expect(emptyCollectionRightsBackendObject.canUnpublish()).toBeUndefined();
-    expect(emptyCollectionRightsBackendObject.isPrivate()).toBeUndefined();
+      emptyCollectionRightsBackendObject.getCollectionId()).toBeNull();
+    expect(emptyCollectionRightsBackendObject.canEdit()).toBeNull();
+    expect(emptyCollectionRightsBackendObject.canUnpublish()).toBeNull();
+    expect(emptyCollectionRightsBackendObject.isPrivate()).toBeNull();
     expect(emptyCollectionRightsBackendObject.getOwnerNames()).toEqual([]);
   });
 
diff --git a/core/templates/domain/collection/collection-node-object.factory.ts b/core/templates/domain/collection/collection-node-object.factory.ts
index 104e42bc0a0cd..b996b57436753 100644
--- a/core/templates/domain/collection/collection-node-object.factory.ts
+++ b/core/templates/domain/collection/collection-node-object.factory.ts
@@ -27,8 +27,8 @@ import { LearnerExplorationSummaryBackendDict } from
   'domain/summary/learner-exploration-summary-object.factory';
 
 export interface CollectionNodeBackendDict {
-  'exploration_id'?: string;
-  'exploration_summary'?: LearnerExplorationSummaryBackendDict;
+  'exploration_id': string;
+  'exploration_summary': LearnerExplorationSummaryBackendDict;
 }
 
 export class CollectionNode {
diff --git a/core/templates/domain/collection/editable-collection-backend-api.service.spec.ts b/core/templates/domain/collection/editable-collection-backend-api.service.spec.ts
index 0064d01c65332..bae264d6ae348 100644
--- a/core/templates/domain/collection/editable-collection-backend-api.service.spec.ts
+++ b/core/templates/domain/collection/editable-collection-backend-api.service.spec.ts
@@ -38,7 +38,31 @@ describe('Editable collection backend API service', () => {
       objective: 'To pass',
       version: 1,
       nodes: [{
-        exploration_id: '0'
+        exploration_id: '0',
+        exploration_summary: {
+          last_updated_msec: 1591296737470.528,
+          community_owned: false,
+          objective: 'Test Objective',
+          id: '44LKoKLlIbGe',
+          num_views: 0,
+          thumbnail_icon_url: '/subjects/Algebra.svg',
+          human_readable_contributors_summary: {},
+          language_code: 'en',
+          thumbnail_bg_color: '#cd672b',
+          created_on_msec: 1591296635736.666,
+          ratings: {
+            1: 0,
+            2: 0,
+            3: 0,
+            4: 0,
+            5: 0
+          },
+          status: 'public',
+          tags: [],
+          activity_type: 'exploration',
+          category: 'Algebra',
+          title: 'Test Title'
+        }
       }],
       playthrough_dict: {
         next_exploration_id: 'expId',
diff --git a/core/templates/domain/collection/read-only-collection-backend-api.service.spec.ts b/core/templates/domain/collection/read-only-collection-backend-api.service.spec.ts
index 977c587e8ef60..566129f69a236 100644
--- a/core/templates/domain/collection/read-only-collection-backend-api.service.spec.ts
+++ b/core/templates/domain/collection/read-only-collection-backend-api.service.spec.ts
@@ -38,7 +38,31 @@ describe('Read only collection backend API service', () => {
       objective: 'To pass',
       schema_version: 1,
       nodes: [{
-        exploration_id: '0'
+        exploration_id: '0',
+        exploration_summary: {
+          last_updated_msec: 1591296737470.528,
+          community_owned: false,
+          objective: 'Test Objective',
+          id: '44LKoKLlIbGe',
+          num_views: 0,
+          thumbnail_icon_url: '/subjects/Algebra.svg',
+          human_readable_contributors_summary: {},
+          language_code: 'en',
+          thumbnail_bg_color: '#cd672b',
+          created_on_msec: 1591296635736.666,
+          ratings: {
+            1: 0,
+            2: 0,
+            3: 0,
+            4: 0,
+            5: 0
+          },
+          status: 'public',
+          tags: [],
+          activity_type: 'exploration',
+          category: 'Algebra',
+          title: 'Test Title'
+        }
       }],
       playthrough_dict: {
         next_exploration_id: 'expId',
diff --git a/core/templates/domain/creator_dashboard/creator-dashboard-backend-api.service.ts b/core/templates/domain/creator_dashboard/creator-dashboard-backend-api.service.ts
index 694cdbd684a20..6d3bd9c5085c0 100644
--- a/core/templates/domain/creator_dashboard/creator-dashboard-backend-api.service.ts
+++ b/core/templates/domain/creator_dashboard/creator-dashboard-backend-api.service.ts
@@ -75,9 +75,7 @@ interface CreatorDashboardDataBackendDict {
   'suggestions_to_review_list': SuggestionBackendDict[];
   'explorations_list': CreatorExplorationSummaryBackendDict[];
   'collections_list': CollectionSummaryBackendDict[];
-  // Here topic summary dicts are optional because they are present in response
-  // only if new structrures are enabled.
-  'topic_summary_dicts'?: TopicSummaryBackendDict[];
+  'topic_summary_dicts': TopicSummaryBackendDict[];
 }
 
 interface CreatorDashboardData {
@@ -93,7 +91,7 @@ interface CreatorDashboardData {
   suggestionThreadsToReviewList: SuggestionThread[];
   explorationsList: CreatorExplorationSummary[];
   collectionsList: CollectionSummary[];
-  topicSummaries?: TopicSummary[];
+  topicSummaries: TopicSummary[];
 }
 
 @Injectable({
diff --git a/core/templates/domain/editor/undo_redo/ChangeObjectFactory.ts b/core/templates/domain/editor/undo_redo/ChangeObjectFactory.ts
index d44f0b3a9ea9e..bde6039901cee 100644
--- a/core/templates/domain/editor/undo_redo/ChangeObjectFactory.ts
+++ b/core/templates/domain/editor/undo_redo/ChangeObjectFactory.ts
@@ -28,44 +28,573 @@ import { Injectable } from '@angular/core';
 
 import { MisconceptionBackendDict } from
   'domain/skill/MisconceptionObjectFactory';
+import { RecordedVoiceOverBackendDict } from
+  'domain/exploration/RecordedVoiceoversObjectFactory';
+import { StateBackendDict } from
+  'domain/state/StateObjectFactory';
+import { SubtitledHtmlBackendDict } from
+  'domain/exploration/SubtitledHtmlObjectFactory';
+import { WorkedExampleBackendDict } from
+  'domain/skill/WorkedExampleObjectFactory';
 
-interface BackendChangeObject {
-  // This interface may not have all the possible values.
-  // Add properties with a '?' if you find any that needs to be here.
-  'cmd'?: string;
-  'property_name'?: string;
+interface CollectionTitleChange {
+  'cmd': 'edit_collection_property';
+  'property_name': 'title';
+  'new_value': string;
+  'old_value': string;
+}
+
+interface CollectionCategoryChange {
+  'cmd': 'edit_collection_property';
+  'property_name': 'category';
+  'new_value': string;
+  'old_value': string;
+}
+
+interface CollectionObjectiveChange {
+  'cmd': 'edit_collection_property';
+  'property_name': 'objective';
+  'new_value': string;
+  'old_value': string;
+}
+
+interface CollectionLanguageCodeChange {
+  'cmd': 'edit_collection_property';
+  'property_name': 'language_code';
+  'new_value': string;
+  'old_value': string;
+}
+
+interface CollectionTagsChange {
+  'cmd': 'edit_collection_property';
+  'property_name': 'tags';
+  'new_value': string[];
+  'old_value': string[];
+}
+
+type CollectionPropertyChange = (
+  CollectionTitleChange |
+  CollectionCategoryChange |
+  CollectionObjectiveChange |
+  CollectionLanguageCodeChange |
+  CollectionTagsChange);
+
+interface CollectionAddNodeChange {
+  'cmd': 'add_collection_node';
+  'exploration_id': string;
+}
+
+interface CollectionSwapNodeChange {
+  'cmd': 'swap_nodes';
+  'first_index': number;
+  'second_index': number;
+}
+
+interface CollectionDeleteNodeChange {
+  'cmd': 'delete_collection_node';
+  'exploration_id': string;
+}
+
+type CollectionChange = (
+  CollectionPropertyChange |
+  CollectionAddNodeChange |
+  CollectionSwapNodeChange |
+  CollectionDeleteNodeChange);
+
+interface QuestionLanguageCodeChange {
+  'cmd': 'update_question_property';
+  'property_name': 'language_code';
+  'new_value': string;
+  'old_value': string;
+}
+
+interface QuestionStateDataChange {
+  'cmd': 'update_question_property';
+  'property_name': 'question_state_data';
+  'new_value': StateBackendDict;
+  'old_value': StateBackendDict;
+}
+
+type QuestionPropertyChange = (
+  QuestionLanguageCodeChange |
+  QuestionStateDataChange);
+
+type QuestionChange = QuestionPropertyChange;
+
+interface SkillDescriptionChange {
+  'cmd': 'update_skill_property';
+  'property_name': 'description';
+  'new_value': string;
+  'old_value': string;
+}
+
+type SkillPropertyChange = SkillDescriptionChange;
+
+interface SkillMisconceptionNameChange {
+  'cmd': 'update_skill_misconceptions_property';
+  'property_name': 'name';
+  'new_value': string;
+  'old_value': string;
+  'misconception_id': string;
+}
+
+interface SkillMisconceptionMustBeAddressedChange {
+  'cmd': 'update_skill_misconceptions_property';
+  'property_name': 'must_be_addressed';
+  'new_value': boolean;
+  'old_value': boolean;
+  'misconception_id': string;
+}
+
+interface SkillMisconceptionsNotesChange {
+  'cmd': 'update_skill_misconceptions_property';
+  'property_name': 'notes';
+  'new_value': string;
+  'old_value': string;
+  'misconception_id': string;
+}
+
+interface SkillMisconceptionsFeedbackChange {
+  'cmd': 'update_skill_misconceptions_property';
+  'property_name': 'feedback';
+  'new_value': string;
+  'old_value': string;
+  'misconception_id': string;
+}
 
-  'state_name'?: string;
-  'old_state_name'?: string;
-  'new_state_name'?: string;
+type SkillMisconceptionPropertyChange = (
+  SkillMisconceptionNameChange |
+  SkillMisconceptionMustBeAddressedChange |
+  SkillMisconceptionsNotesChange |
+  SkillMisconceptionsFeedbackChange);
 
-  'new_value'?: Object;
-  'old_value'?: Object;
+interface SkillRubricsChange {
+  cmd: 'update_rubrics';
+  difficulty: string;
+  explanations: string[];
+}
+
+interface SkillContentsExplanationChange {
+  'cmd': 'update_skill_contents_property';
+  'property_name': 'explanation';
+  'new_value': SubtitledHtmlBackendDict;
+  'old_value': SubtitledHtmlBackendDict;
+}
+
+interface SkillContentsWorkedExamplesChange {
+  'cmd': 'update_skill_contents_property';
+  'property_name': 'worked_examples';
+  'new_value': WorkedExampleBackendDict;
+  'old_value': WorkedExampleBackendDict;
+}
+
+type SkillContentsChange = (
+  SkillContentsExplanationChange |
+  SkillContentsWorkedExamplesChange);
+
+interface SkillAddMisconceptionChange {
+  'cmd': 'add_skill_misconception';
+  'new_misconception_dict': MisconceptionBackendDict;
+}
+
+interface SkillDeleteMisconceptionChange {
+  'cmd': 'delete_skill_misconception';
+  'misconception_id': string;
+}
+
+interface SkillAddPrerequisiteChange {
+  'cmd': 'add_prerequisite_skill';
+  'skill_id': string;
+}
+
+interface SkillDeletePrerequisiteChange {
+  'cmd': 'delete_prerequisite_skill';
+  'skill_id': string;
+}
+
+type SkillChange = (
+  SkillPropertyChange |
+  SkillMisconceptionPropertyChange |
+  SkillRubricsChange |
+  SkillContentsChange |
+  SkillAddMisconceptionChange |
+  SkillDeleteMisconceptionChange |
+  SkillAddPrerequisiteChange |
+  SkillDeletePrerequisiteChange);
+
+interface StoryTitleChange {
+  'cmd': 'update_story_property';
+  'property_name': 'title';
+  'new_value': string;
+  'old_value': string;
+}
 
-  'exploration_id'?: string;
+interface StoryUrlFragmentChange {
+  'cmd': 'update_story_property';
+  'property_name': 'url_fragment';
+  'new_value': string;
+  'old_value': string;
+}
 
-  'new_misconception_dict'?: MisconceptionBackendDict;
-  'misconception_id'?: string;
+interface StoryThumbnailFilenameChange {
+  'cmd': 'update_story_property';
+  'property_name': 'thumbnail_filename';
+  'new_value': string;
+  'old_value': string;
+}
 
-  'skill_id'?: string;
-  'uncategorized_skill_id'?: string;
-  'new_uncategorized_skill_id'?: string;
-  'skill_difficulty'?: number;
+interface StoryThumbnailBgColorChange {
+  'cmd': 'update_story_property';
+  'property_name': 'thumbnail_bg_color';
+  'new_value': string;
+  'old_value': string;
+}
 
-  'explanations'?: string[];
-  'difficulty'?: string;
+interface StoryDescriptionChange {
+  'cmd': 'update_story_property';
+  'property_name': 'description';
+  'new_value': string;
+  'old_value': string;
+}
 
-  'node_id'?: string;
+interface StoryNotesChange {
+  'cmd': 'update_story_property';
+  'property_name': 'notes';
+  'new_value': string;
+  'old_value': string;
+}
 
-  'story_id'?: string;
+interface StoryLanguageCodeChange {
+  'cmd': 'update_story_property';
+  'property_name': 'language_code';
+  'new_value': string;
+  'old_value': string;
+}
+
+type StoryPropertyChange = (
+  StoryTitleChange |
+  StoryUrlFragmentChange |
+  StoryThumbnailFilenameChange |
+  StoryThumbnailBgColorChange |
+  StoryDescriptionChange |
+  StoryNotesChange |
+  StoryLanguageCodeChange);
+
+interface StoryInitialNodeIdChange {
+  'cmd': 'update_story_contents_property';
+  'property_name': 'initial_node_id';
+  'new_value': string;
+  'old_value': string;
+}
 
-  'subtopic_id'?: number;
-  'new_subtopic_id'?: number;
-  'old_subtopic_id'?: number;
+interface StoryNodesChange {
+  'cmd': 'update_story_contents_property';
+  'property_name': 'node';
+  'new_value': number;
+  'old_value': number;
+}
+
+type StoryContentsChange = (
+  StoryInitialNodeIdChange |
+  StoryNodesChange);
+
+interface StoryNodeOutlineChange {
+  'cmd': 'update_story_node_property';
+  'property_name': 'outline';
+  'new_value': string;
+  'old_value': string;
+  'node_id': string;
+}
+
+interface StoryNodeTitleChange {
+  'cmd': 'update_story_node_property';
+  'property_name': 'title';
+  'new_value': string;
+  'old_value': string;
+  'node_id': string;
+}
+
+interface StoryNodeDescriptionChange {
+  'cmd': 'update_story_node_property';
+  'property_name': 'description';
+  'new_value': string;
+  'old_value': string;
+  'node_id': string;
+}
 
-  'version_number'?: number;
+interface StoryNodeThumbnailFilenameChange {
+  'cmd': 'update_story_node_property';
+  'property_name': 'thumbnail_filename';
+  'new_value': string;
+  'old_value': string;
+  'node_id': string;
 }
 
+interface StoryNodeThumbnailBgColorChange {
+  'cmd': 'update_story_node_property';
+  'property_name': 'thumbnail_bg_color';
+  'new_value': string;
+  'old_value': string;
+  'node_id': string;
+}
+
+interface StoryNodeExplorationIdChange {
+  'cmd': 'update_story_node_property';
+  'property_name': 'exploration_id';
+  'new_value': string;
+  'old_value': string;
+  'node_id': string;
+}
+
+interface StoryNodeDestinationIdsChange {
+  'cmd': 'update_story_node_property';
+  'property_name': 'destination_node_ids';
+  'new_value': string[];
+  'old_value': string[];
+  'node_id': string;
+}
+
+interface StoryNodePrerequisiteSkillsChange {
+  'cmd': 'update_story_node_property';
+  'property_name': 'prerequisite_skill_ids';
+  'new_value': string[];
+  'old_value': string[];
+  'node_id': string;
+}
+
+interface StoryNodeAcequiredSkillsChange {
+  'cmd': 'update_story_node_property';
+  'property_name': 'acquired_skill_ids';
+  'new_value': string[];
+  'old_value': string[];
+  'node_id': string;
+}
+
+type StoryNodePropertyChange = (
+  StoryNodeOutlineChange |
+  StoryNodeTitleChange |
+  StoryNodeDescriptionChange |
+  StoryNodeThumbnailFilenameChange |
+  StoryNodeThumbnailBgColorChange |
+  StoryNodeExplorationIdChange |
+  StoryNodeDestinationIdsChange |
+  StoryNodePrerequisiteSkillsChange |
+  StoryNodeAcequiredSkillsChange);
+
+interface StoryAddNodeChange {
+  'cmd': 'add_story_node';
+  'node_id': string;
+  'title': string;
+}
+
+interface StoryDeleteNodeChange {
+  'cmd': 'delete_story_node';
+  'node_id': string;
+}
+
+interface StoryNodeOutlineStatusChange {
+  'cmd': 'update_story_node_outline_status';
+  'node_id': string;
+  'old_value': boolean;
+  'new_value': boolean;
+}
+
+type StoryChange = (
+  StoryPropertyChange |
+  StoryContentsChange |
+  StoryNodePropertyChange |
+  StoryAddNodeChange |
+  StoryDeleteNodeChange |
+  StoryNodeOutlineStatusChange);
+
+interface TopicNameChange {
+  'cmd': 'update_topic_property';
+  'property_name': 'name';
+  'new_value': string;
+  'old_value': string;
+}
+
+interface TopicAbbreviatedNameChange {
+  'cmd': 'update_topic_property';
+  'property_name': 'abbreviated_name';
+  'new_value': string;
+  'old_value': string;
+}
+
+interface TopicThumbnailFilenameChange {
+  'cmd': 'update_topic_property';
+  'property_name': 'thumbnail_filename';
+  'new_value': string;
+  'old_value': string;
+}
+
+interface TopicThumbnailBgColorChange {
+  'cmd': 'update_topic_property';
+  'property_name': 'thumbnail_bg_color';
+  'new_value': string;
+  'old_value': string;
+}
+
+interface TopicDescriptionChange {
+  'cmd': 'update_topic_property';
+  'property_name': 'description';
+  'new_value': string;
+  'old_value': string;
+}
+
+interface TopicLanguageCodeChange {
+  'cmd': 'update_topic_property';
+  'property_name': 'language_code';
+  'new_value': string;
+  'old_value': string;
+}
+
+type TopicPropertyChange = (
+  TopicNameChange |
+  TopicAbbreviatedNameChange |
+  TopicThumbnailFilenameChange |
+  TopicThumbnailBgColorChange |
+  TopicDescriptionChange |
+  TopicLanguageCodeChange);
+
+interface TopicSubtopicThumbnailFilenameChange {
+  'cmd': 'update_subtopic_property';
+  'property_name': 'thumbnail_filename';
+  'new_value': string;
+  'old_value': string;
+  'subtopic_id': string;
+}
+
+interface TopicSubtopicThumbnailBgColorChange {
+  'cmd': 'update_subtopic_property';
+  'property_name': 'thumbnail_bg_color';
+  'new_value': string;
+  'old_value': string;
+  'subtopic_id': string;
+}
+
+interface TopicSubtopicTitleChange {
+  'cmd': 'update_subtopic_property';
+  'property_name': 'title';
+  'new_value': string;
+  'old_value': string;
+  'subtopic_id': string;
+}
+
+interface TopicSubtopicUrlFragmentChange {
+  'cmd': 'update_subtopic_property';
+  'property_name': 'url_fragment';
+  'new_value': string;
+  'old_value': string;
+  'subtopic_id': string;
+}
+
+type TopicSubtopicPropertyChange = (
+  TopicSubtopicThumbnailFilenameChange |
+  TopicSubtopicThumbnailBgColorChange |
+  TopicSubtopicTitleChange |
+  TopicSubtopicUrlFragmentChange);
+
+interface TopicSubtopicPageHtmlChange {
+  'cmd': 'update_subtopic_page_property';
+  'property_name': 'page_contents_html';
+  'new_value': SubtitledHtmlBackendDict;
+  'old_value': SubtitledHtmlBackendDict;
+  'subtopic_id': string;
+}
+
+interface TopicSubtopicPageAudioChange {
+  'cmd': 'update_subtopic_page_property';
+  'property_name': 'page_contents_audio';
+  'new_value': RecordedVoiceOverBackendDict;
+  'old_value': RecordedVoiceOverBackendDict;
+  'subtopic_id': string;
+}
+
+type TopicSubtopicPagePropertyChange = (
+  TopicSubtopicPageHtmlChange |
+  TopicSubtopicPageAudioChange);
+
+interface TopicAddSubtopicChange {
+  'cmd': 'add_subtopic';
+  'subtopic_id': string;
+  'title': string;
+}
+
+interface TopicDeleteSubtopicChange {
+  'cmd': 'delete_subtopic';
+  'subtopic_id': string;
+}
+
+interface TopicMoveSkillToSubtopicChange {
+  'cmd': 'move_skill_id_to_subtopic';
+  'old_subtopic_id': string;
+  'new_subtopic_id': string;
+  'skill_id': string;
+}
+
+interface TopicRemoveSkillFromSubtopicChange {
+  'cmd': 'remove_skill_id_from_subtopic';
+  'subtopic_id': string;
+  'skill_id': string;
+}
+
+interface TopicDeleteAdditionalStoryChange {
+  'cmd': 'delete_additional_story';
+  'story_id': string;
+}
+
+interface TopicDeleteCanonicalStoryChange {
+  'cmd': 'delete_canonical_story';
+  'story_id': string;
+}
+
+interface TopicRearrangeCanonicalStoryChange {
+  'cmd': 'rearrange_canonical_story';
+  'from_index': number;
+  'to_index': number;
+}
+
+interface TopicRearrangeSkillInSubtopicChange {
+  'cmd': 'rearrange_skill_in_subtopic';
+  'subtopic_id': string;
+  'from_index': number;
+  'to_index': number;
+}
+
+interface TopicRearrangeSubtopicChange {
+  'cmd': 'rearrange_subtopic';
+  'from_index': number;
+  'to_index': number;
+}
+
+interface TopicRemoveUncategorizedSkillChange {
+  'cmd': 'remove_uncategorized_skill_id';
+  'uncategorized_skill_id': string;
+}
+
+type TopicChange = (
+  TopicPropertyChange |
+  TopicSubtopicPropertyChange |
+  TopicSubtopicPagePropertyChange |
+  TopicAddSubtopicChange |
+  TopicDeleteSubtopicChange |
+  TopicMoveSkillToSubtopicChange |
+  TopicRemoveSkillFromSubtopicChange |
+  TopicDeleteAdditionalStoryChange |
+  TopicDeleteCanonicalStoryChange |
+  TopicRearrangeCanonicalStoryChange |
+  TopicRearrangeSkillInSubtopicChange |
+  TopicRearrangeSubtopicChange |
+  TopicRemoveUncategorizedSkillChange);
+
+export type BackendChangeObject = (
+  CollectionChange |
+  QuestionChange |
+  SkillChange |
+  StoryChange |
+  TopicChange);
+
 export class Change {
   _backendChangeObject: BackendChangeObject;
   _applyChangeToObject: Function;
diff --git a/core/templates/domain/editor/undo_redo/ChangeObjectFactorySpec.ts b/core/templates/domain/editor/undo_redo/ChangeObjectFactorySpec.ts
index 8cae5073e6379..3376fff0b23ee 100644
--- a/core/templates/domain/editor/undo_redo/ChangeObjectFactorySpec.ts
+++ b/core/templates/domain/editor/undo_redo/ChangeObjectFactorySpec.ts
@@ -16,7 +16,7 @@
  * @fileoverview Tests for ChangeObjectFactory.
  */
 
-import { ChangeObjectFactory } from
+import { BackendChangeObject, ChangeObjectFactory } from
   'domain/editor/undo_redo/ChangeObjectFactory';
 
 describe('Factory for Change domain objects', () => {
@@ -30,8 +30,11 @@ describe('Factory for Change domain objects', () => {
     const applyFunc = jasmine.createSpy('applyChange');
     const reverseFunc = jasmine.createSpy('reverseChange');
 
-    const backendChangeObject = {
-      property_name: 'value'
+    const backendChangeObject: BackendChangeObject = {
+      cmd: 'update_question_property',
+      property_name: 'language_code',
+      new_value: 'newVal',
+      old_value: 'oldVal'
     };
     changeObjectFactory.create(backendChangeObject, applyFunc, reverseFunc);
 
@@ -43,14 +46,20 @@ describe('Factory for Change domain objects', () => {
     const applyFunc = jasmine.createSpy('applyChange');
     const reverseFunc = jasmine.createSpy('reverseChange');
 
-    const backendChangeObject = {
-      property_name: 'value'
+    const backendChangeObject: BackendChangeObject = {
+      cmd: 'update_question_property',
+      property_name: 'language_code',
+      new_value: 'newVal',
+      old_value: 'oldVal'
     };
     const changeDomainObject = changeObjectFactory.create(
       backendChangeObject, applyFunc, reverseFunc);
 
-    const fakeDomainObject = {
-      property_name: 'fake value'
+    const fakeDomainObject: BackendChangeObject = {
+      cmd: 'update_question_property',
+      property_name: 'language_code',
+      new_value: 'newVal',
+      old_value: 'oldVal'
     };
     changeDomainObject.applyChange(fakeDomainObject);
 
@@ -63,14 +72,20 @@ describe('Factory for Change domain objects', () => {
     const applyFunc = jasmine.createSpy('applyChange');
     const reverseFunc = jasmine.createSpy('reverseChange');
 
-    const backendChangeObject = {
-      property_name: 'value'
+    const backendChangeObject: BackendChangeObject = {
+      cmd: 'update_question_property',
+      property_name: 'language_code',
+      new_value: 'newVal',
+      old_value: 'oldVal'
     };
     const changeDomainObject = changeObjectFactory.create(
       backendChangeObject, applyFunc, reverseFunc);
 
-    const fakeDomainObject = {
-      property_name: 'fake value'
+    const fakeDomainObject: BackendChangeObject = {
+      cmd: 'update_question_property',
+      property_name: 'language_code',
+      new_value: 'newVal',
+      old_value: 'oldVal'
     };
     changeDomainObject.reverseChange(fakeDomainObject);
 
@@ -81,35 +96,54 @@ describe('Factory for Change domain objects', () => {
 
   it('should not receive changes to the provided change backend object',
     () => {
-      const backendChangeObject = {
-        property_name: 'value'
+      const backendChangeObject: BackendChangeObject = {
+        cmd: 'update_question_property',
+        property_name: 'language_code',
+        new_value: 'newVal',
+        old_value: 'oldVal'
       };
       const changeDomainObject = changeObjectFactory.create(
         backendChangeObject, () => {}, () => {});
 
       const returnedBackendObject = changeDomainObject.getBackendChangeObject();
-      returnedBackendObject.property_name = 'new value';
+      (<typeof backendChangeObject> returnedBackendObject)
+        .property_name = 'language_code';
 
       expect(changeDomainObject.getBackendChangeObject()).toEqual({
-        property_name: 'value'
+        cmd: 'update_question_property',
+        property_name: 'language_code',
+        new_value: 'newVal',
+        old_value: 'oldVal'
       });
     });
 
   it('should set new backend change object when using specific method', () => {
     const changeDomainObject = changeObjectFactory.create({
-      property_name: 'value'
+      cmd: 'update_question_property',
+      property_name: 'language_code',
+      new_value: 'newVal',
+      old_value: 'oldVal'
     }, () => {}, () => {});
 
     expect(changeDomainObject.getBackendChangeObject()).toEqual({
-      property_name: 'value'
+      cmd: 'update_question_property',
+      property_name: 'language_code',
+      new_value: 'newVal',
+      old_value: 'oldVal'
     });
 
     changeDomainObject.setBackendChangeObject({
-      property_name: 'new value'
+      cmd: 'update_question_property',
+      property_name: 'language_code',
+      new_value: 'newVal',
+      old_value: 'oldVal'
     });
 
     expect(changeDomainObject.getBackendChangeObject()).toEqual({
-      property_name: 'new value'
+      cmd: 'update_question_property',
+      property_name: 'language_code',
+      new_value: 'newVal',
+      old_value: 'oldVal'
     });
   });
 });
diff --git a/core/templates/domain/email-dashboard/email-dashboard-backend-api.service.ts b/core/templates/domain/email-dashboard/email-dashboard-backend-api.service.ts
index fb4a51578916b..6d2ee383c50fd 100644
--- a/core/templates/domain/email-dashboard/email-dashboard-backend-api.service.ts
+++ b/core/templates/domain/email-dashboard/email-dashboard-backend-api.service.ts
@@ -32,12 +32,12 @@ import {
 } from 'domain/email-dashboard/email-dashboard-query-object.factory';
 
 export interface QueryData {
-  hasNotLoggedInForNDays?: string;
-  inactiveInLastNDays?: string;
-  createdAtLeastNExps?: string;
-  createdFewerThanNExps?: string;
-  editedAtLeastNExps?: string;
-  editedFewerThanNExps?: string;
+  hasNotLoggedInForNDays: string;
+  inactiveInLastNDays: string;
+  createdAtLeastNExps: string;
+  createdFewerThanNExps: string;
+  editedAtLeastNExps: string;
+  editedFewerThanNExps: string;
 }
 
 @Injectable({
@@ -55,7 +55,12 @@ export class EmailDashboardBackendApiService {
 
   fetchQueriesPage(
       pageSize: number, cursor: string): Promise<EmailDashboardQueryResults> {
-    let params: {'cursor'?: string; 'num_queries_to_fetch'?: string;} = {
+    // Here 'cursor' property is optional because it is present only if this
+    // function is called with a non-null value to 'cursor' arg.
+    // If we send a null value of 'cursor' the request URL would have
+    // something like '?cursor=null' and the backend would start looking for
+    // cursor with value 'null' which is not correct.
+    let params: {'cursor'?: string; 'num_queries_to_fetch': string;} = {
       num_queries_to_fetch: String(pageSize)
     };
     if (cursor) {
diff --git a/core/templates/domain/exploration/exploration-permissions-object.factory.ts b/core/templates/domain/exploration/exploration-permissions-object.factory.ts
index d60b150e4b8a1..1d39f1d0bcacc 100644
--- a/core/templates/domain/exploration/exploration-permissions-object.factory.ts
+++ b/core/templates/domain/exploration/exploration-permissions-object.factory.ts
@@ -21,13 +21,13 @@ import { downgradeInjectable } from '@angular/upgrade/static';
 import { Injectable } from '@angular/core';
 
 export interface ExplorationPermissionsBackendDict {
-  'can_unpublish'?: boolean;
-  'can_release_ownership'?: boolean;
-  'can_publish'?: boolean;
-  'can_voiceover'?: boolean;
-  'can_delete'?: boolean;
-  'can_modify_roles'?: boolean;
-  'can_edit'?: boolean;
+  'can_unpublish': boolean;
+  'can_release_ownership': boolean;
+  'can_publish': boolean;
+  'can_voiceover': boolean;
+  'can_delete': boolean;
+  'can_modify_roles': boolean;
+  'can_edit': boolean;
 }
 
 export class ExplorationPermissions {
diff --git a/core/templates/domain/recommendations/exploration-recommendations-backend-api.service.ts b/core/templates/domain/recommendations/exploration-recommendations-backend-api.service.ts
index 0b4b969c62a78..467d9d06bfe48 100644
--- a/core/templates/domain/recommendations/exploration-recommendations-backend-api.service.ts
+++ b/core/templates/domain/recommendations/exploration-recommendations-backend-api.service.ts
@@ -26,12 +26,17 @@ import {
   LearnerExplorationSummaryObjectFactory
 } from 'domain/summary/learner-exploration-summary-object.factory';
 
+// This is the type used for params that are sent to the backend.
+// This type has optional properties because they may not be present in the URL.
+// If we send these params always, the request URL would have something like
+// '?collection_id=null' and the backend would start looking for a collection
+// with id "null" which is not correct.
 type RecommendationsUrlParams = {
-  'stringified_author_recommended_ids'?: string;
+  'stringified_author_recommended_ids': string;
   'collection_id'?: string;
   'story_id'?: string;
   'current_node_id'?: string;
-  'include_system_recommendations'?: string;
+  'include_system_recommendations': string;
 };
 
 export interface RecommendedExplorationSummariesBackendDict {
diff --git a/core/templates/domain/skill/concept-card-backend-api.service.ts b/core/templates/domain/skill/concept-card-backend-api.service.ts
index da739ccd1dd0f..ce5784dc8c9e2 100644
--- a/core/templates/domain/skill/concept-card-backend-api.service.ts
+++ b/core/templates/domain/skill/concept-card-backend-api.service.ts
@@ -48,8 +48,8 @@ export class ConceptCardBackendApiService {
 
   private _fetchConceptCards(
       skillIds: string[],
-      successCallback: (value?: Object | PromiseLike<Object>) => void,
-      errorCallback: (reason?: string) => void): void {
+      successCallback: (value: ConceptCard[]) => void,
+      errorCallback: (reason: string) => void): void {
     var conceptCardDataUrl = this.urlInterpolation.interpolateUrl(
       SkillDomainConstants.CONCEPT_CARD_DATA_URL_TEMPLATE, {
         comma_separated_skill_ids: skillIds.join(',')
diff --git a/core/templates/expressions/expression-syntax-tree.service.ts b/core/templates/expressions/expression-syntax-tree.service.ts
index 3eb7de9a17d5f..933d3c7e0ba50 100644
--- a/core/templates/expressions/expression-syntax-tree.service.ts
+++ b/core/templates/expressions/expression-syntax-tree.service.ts
@@ -35,6 +35,9 @@ interface EnvDict {
 }
 
 export class ExpressionError extends Error {
+  // 'message' is optional beacuse it is optional in the actual 'Error'
+  // constructor object. Also, we may not want a custom error message
+  // while throwing 'ExpressionError'.
   constructor(message?: string) {
     super(message);
     // NOTE TO DEVELOPERS: In order to properly extend Error, we must manually
diff --git a/core/templates/pages/collection-editor-page/services/collection-editor-state.service.spec.ts b/core/templates/pages/collection-editor-page/services/collection-editor-state.service.spec.ts
index 8bb57877a68c6..8f070d15e8690 100644
--- a/core/templates/pages/collection-editor-page/services/collection-editor-state.service.spec.ts
+++ b/core/templates/pages/collection-editor-page/services/collection-editor-state.service.spec.ts
@@ -311,10 +311,10 @@ describe('Collection editor state service', function() {
 
   it('should initially return an empty collection rights', function() {
     var collectionRights = CollectionEditorStateService.getCollectionRights();
-    expect(collectionRights.getCollectionId()).toBeUndefined();
-    expect(collectionRights.canEdit()).toBeUndefined();
-    expect(collectionRights.canUnpublish()).toBeUndefined();
-    expect(collectionRights.isPrivate()).toBeUndefined();
+    expect(collectionRights.getCollectionId()).toBeNull();
+    expect(collectionRights.canEdit()).toBeNull();
+    expect(collectionRights.canUnpublish()).toBeNull();
+    expect(collectionRights.isPrivate()).toBeNull();
     expect(collectionRights.getOwnerNames()).toEqual([]);
   });
 
diff --git a/core/templates/pages/email-dashboard-pages/email-dashboard-data.service.spec.ts b/core/templates/pages/email-dashboard-pages/email-dashboard-data.service.spec.ts
index bd99943a6f205..8fef0bde7a42f 100644
--- a/core/templates/pages/email-dashboard-pages/email-dashboard-data.service.spec.ts
+++ b/core/templates/pages/email-dashboard-pages/email-dashboard-data.service.spec.ts
@@ -90,7 +90,12 @@ describe('Email Dashboard Services', () => {
     it('should post correct data to backend',
       fakeAsync(() => {
         var data = {
-          hasNotLoggedInForNDays: 'value1'
+          hasNotLoggedInForNDays: 'value1',
+          inactiveInLastNDays: null,
+          createdAtLeastNExps: null,
+          createdFewerThanNExps: null,
+          editedAtLeastNExps: null,
+          editedFewerThanNExps: null
         };
         var queryData = emailDashboardQueryObjectFactory.createFromQueryDict({
           id: 'qnew',
@@ -197,7 +202,12 @@ describe('Email Dashboard Services', () => {
         expect(emailDashboardDataService.getCurrentPageIndex()).toEqual(0);
 
         var data = {
-          hasNotLoggedInForNDays: 'value1'
+          hasNotLoggedInForNDays: 'value1',
+          inactiveInLastNDays: null,
+          createdAtLeastNExps: null,
+          createdFewerThanNExps: null,
+          editedAtLeastNExps: null,
+          editedFewerThanNExps: null
         };
         // Maintain list of all submitted queries for cross checking.
         var totalQueries = [];
diff --git a/core/templates/pages/exploration-editor-page/services/user-exploration-permissions.service.spec.ts b/core/templates/pages/exploration-editor-page/services/user-exploration-permissions.service.spec.ts
index 11115f6694972..a915fb4eacb8b 100644
--- a/core/templates/pages/exploration-editor-page/services/user-exploration-permissions.service.spec.ts
+++ b/core/templates/pages/exploration-editor-page/services/user-exploration-permissions.service.spec.ts
@@ -37,6 +37,11 @@ describe('User Exploration Permissions Service', () => {
   let samplePermissionsData = {
     can_edit: false,
     can_voiceover: true,
+    can_unpublish: false,
+    can_release_ownership: false,
+    can_publish: false,
+    can_delete: false,
+    can_modify_roles: false,
   };
   let permissionsResponse: ExplorationPermissions;
 
diff --git a/core/templates/pages/exploration-player-page/services/exploration-recommendations.service.ts b/core/templates/pages/exploration-player-page/services/exploration-recommendations.service.ts
index 5959fd2b8ba40..23ddfb544c405 100644
--- a/core/templates/pages/exploration-player-page/services/exploration-recommendations.service.ts
+++ b/core/templates/pages/exploration-player-page/services/exploration-recommendations.service.ts
@@ -26,6 +26,8 @@ import { UrlService } from 'services/contextual/url.service';
 
 import { ExplorationRecommendationsBackendApiService } from
   'domain/recommendations/exploration-recommendations-backend-api.service';
+import { LearnerExplorationSummary } from
+  'domain/summary/learner-exploration-summary-object.factory';
 
 @Injectable({
   providedIn: 'root'
@@ -54,7 +56,7 @@ export class ExplorationRecommendationsService {
   getRecommendedSummaryDicts(
       authorRecommendedExpIds: string[],
       includeAutogeneratedRecommendations: boolean,
-      successCallback: (value?: Object | PromiseLike<Object>) => void): void {
+      successCallback: (value: LearnerExplorationSummary[]) => void): void {
     let collectionId = this.urlService.getCollectionIdFromExplorationUrl();
     let storyId = this.urlService.getUrlParams().story_id;
     let currentNodeId = this.urlService.getUrlParams().node_id;
diff --git a/core/templates/services/schema-default-value.service.ts b/core/templates/services/schema-default-value.service.ts
index b115395a427f9..17aec212f78a7 100644
--- a/core/templates/services/schema-default-value.service.ts
+++ b/core/templates/services/schema-default-value.service.ts
@@ -34,21 +34,25 @@ interface BoolSchema {
 
 interface UnicodeSchema {
   type: 'unicode';
+  // 'choices' are optional because they may not be present in the schema.
   choices?: string[];
 }
 
 interface HtmlSchema {
   type: 'html';
+  // 'choices' are optional because they may not be present in the schema.
   choices?: string[];
 }
 
 interface IntSchema {
   type: 'int';
+  // 'choices' are optional because they may not be present in the schema.
   choices?: number[];
 }
 
 interface FloatSchema {
   type: 'float';
+  // 'choices' are optional because they may not be present in the schema.
   choices?: number[];
 }
 
diff --git a/core/templates/services/suggestion-modal.service.ts b/core/templates/services/suggestion-modal.service.ts
index a5b76d849cbfb..d2d1c75929e64 100644
--- a/core/templates/services/suggestion-modal.service.ts
+++ b/core/templates/services/suggestion-modal.service.ts
@@ -21,8 +21,8 @@ import { downgradeInjectable } from '@angular/upgrade/static';
 
 export interface ParamDict {
   action: string;
-  audioUpdateRequired?: boolean;
-  commitMessage?: string;
+  audioUpdateRequired: boolean;
+  commitMessage: string;
   reviewMessage: string;
 }
 
diff --git a/extensions/interactions/Continue/directives/continue-validation.service.spec.ts b/extensions/interactions/Continue/directives/continue-validation.service.spec.ts
index 9d88edf8d3e8e..fe0f71e8159bb 100644
--- a/extensions/interactions/Continue/directives/continue-validation.service.spec.ts
+++ b/extensions/interactions/Continue/directives/continue-validation.service.spec.ts
@@ -88,6 +88,11 @@ describe('ContinueValidationService', () => {
 
       expect(() => {
         validatorService.getAllWarnings(
+          // This throws "Argument of type '{}' is not assignable to
+          // parameter of type 'ContinueCustomizationArgs'." We are purposely
+          // assigning the wrong type of customization args in order to test
+          // validations.
+          // @ts-expect-error
           currentState, {}, [], goodDefaultOutcome);
       }).toThrowError(
         'Expected customization arguments to have property: buttonText');
diff --git a/extensions/interactions/EndExploration/directives/end-exploration-validation.service.spec.ts b/extensions/interactions/EndExploration/directives/end-exploration-validation.service.spec.ts
index f3a3500659861..2726834e819b3 100644
--- a/extensions/interactions/EndExploration/directives/end-exploration-validation.service.spec.ts
+++ b/extensions/interactions/EndExploration/directives/end-exploration-validation.service.spec.ts
@@ -113,6 +113,11 @@ describe('EndExplorationValidationService', () => {
 
   it('should throw for missing recommendations argument', () => {
     expect(() => {
+      // This throws "Argument of type '{}' is not assignable to
+      // parameter of type 'EndExplorationCustomizationArgs'." We are purposely
+      // assigning the wrong type of customization args in order to test
+      // validations.
+      // @ts-expect-error
       validatorService.getAllWarnings(currentState, {}, [], null);
     }).toThrowError(
       'Expected customization arguments to have property: ' +
diff --git a/extensions/interactions/GraphInput/directives/graph-input-validation.service.spec.ts b/extensions/interactions/GraphInput/directives/graph-input-validation.service.spec.ts
index f39690ae1ff96..0b3adeb7c526a 100644
--- a/extensions/interactions/GraphInput/directives/graph-input-validation.service.spec.ts
+++ b/extensions/interactions/GraphInput/directives/graph-input-validation.service.spec.ts
@@ -133,8 +133,8 @@ describe('GraphInputValidationService', () => {
     expect(() => {
       validatorService.getAllWarnings(
         // This throws "Argument of type '{}' is not assignable to
-        // parameter of type 'GraphInputCustomizationArgs'." We did not
-        // assign the correct type of customization args in order to test
+        // parameter of type 'GraphInputCustomizationArgs'." We are purposely
+        // assigning the wrong type of customization args in order to test
         // validations.
         // @ts-expect-error
         currentState, {}, answerGroups, goodDefaultOutcome);
diff --git a/extensions/interactions/ImageClickInput/directives/image-click-input-validation.service.spec.ts b/extensions/interactions/ImageClickInput/directives/image-click-input-validation.service.spec.ts
index 1a521d0c005ba..33291b1e38392 100644
--- a/extensions/interactions/ImageClickInput/directives/image-click-input-validation.service.spec.ts
+++ b/extensions/interactions/ImageClickInput/directives/image-click-input-validation.service.spec.ts
@@ -96,6 +96,9 @@ describe('ImageClickInputValidationService', () => {
             }
           }]
         }
+      },
+      highlightRegionsOnHover: {
+        value: true
       }
     };
     goodAnswerGroups = [agof.createNew(
@@ -115,6 +118,11 @@ describe('ImageClickInputValidationService', () => {
       goodAnswerGroups[0].rules = [];
       expect(() => {
         validatorService.getAllWarnings(
+          // This throws "Argument of type '{}' is not assignable to
+          // parameter of type 'ImageClickInputCustomizationArgs'." We are
+          // purposely assigning the wrong type of customization args in
+          // order to test validations.
+          // @ts-expect-error
           currentState, {}, goodAnswerGroups, goodDefaultOutcome);
       }).toThrowError(
         'Expected customization arguments to have property: imageAndRegions');
diff --git a/extensions/interactions/InteractiveMap/directives/interactive-map-validation.service.spec.ts b/extensions/interactions/InteractiveMap/directives/interactive-map-validation.service.spec.ts
index 0a9cd60cbafff..8ea0dd57dba9a 100644
--- a/extensions/interactions/InteractiveMap/directives/interactive-map-validation.service.spec.ts
+++ b/extensions/interactions/InteractiveMap/directives/interactive-map-validation.service.spec.ts
@@ -70,6 +70,9 @@ describe('InteractiveMapValidationService', () => {
       },
       longitude: {
         value: 0
+      },
+      zoom: {
+        value: 0
       }
     };
     goodAnswerGroups = [agof.createNew(
@@ -101,6 +104,11 @@ describe('InteractiveMapValidationService', () => {
     () => {
       expect(() => {
         validatorService.getAllWarnings(
+          // This throws "Argument of type '{}' is not assignable to
+          // parameter of type 'InteractiveMapCustomizationArgs'." We are
+          // purposely assigning the wrong type of customization args in
+          // order to test validations.
+          // @ts-expect-error
           currentState, {}, goodAnswerGroups, goodDefaultOutcome);
       }).toThrowError(
         'Expected customization arguments to have properties: ' +
diff --git a/extensions/interactions/ItemSelectionInput/directives/item-selection-input-validation.service.spec.ts b/extensions/interactions/ItemSelectionInput/directives/item-selection-input-validation.service.spec.ts
index e184d8914f6f4..9e9dd047a315e 100644
--- a/extensions/interactions/ItemSelectionInput/directives/item-selection-input-validation.service.spec.ts
+++ b/extensions/interactions/ItemSelectionInput/directives/item-selection-input-validation.service.spec.ts
@@ -158,6 +158,11 @@ describe('ItemSelectionInputValidationService', () => {
   it('should expect a choices customization argument', () => {
     expect(() => {
       validatorService.getAllWarnings(
+        // This throws "Argument of type '{}' is not assignable to
+        // parameter of type 'ItemSelectionInputCustomizationArgs'." We are
+        // purposely assigning the wrong type of customization args in order
+        // to test validations.
+        // @ts-expect-error
         currentState, {}, goodAnswerGroups, goodDefaultOutcome);
     }).toThrowError(
       'Expected customization arguments to have property: choices');
diff --git a/extensions/interactions/LogicProof/directives/logic-proof-validation.service.spec.ts b/extensions/interactions/LogicProof/directives/logic-proof-validation.service.spec.ts
index ff7a2a95c865c..5f0ffd5dfb2b3 100644
--- a/extensions/interactions/LogicProof/directives/logic-proof-validation.service.spec.ts
+++ b/extensions/interactions/LogicProof/directives/logic-proof-validation.service.spec.ts
@@ -74,13 +74,49 @@ describe('LogicProofValidationService', () => {
 
   it('should be able to perform basic validation', () => {
     var warnings = validatorService.getAllWarnings(
-      currentState, {}, goodAnswerGroups, goodDefaultOutcome);
+      currentState, {
+        question: {
+          value: {
+            assumptions: [{
+              top_kind_name: 'variable',
+              top_operator_name: 'p',
+              arguments: [],
+              dummies: []
+            }],
+            results: [{
+              top_kind_name: 'variable',
+              top_operator_name: 'p',
+              arguments: [],
+              dummies: []
+            }],
+            default_proof_string: ''
+          }
+        }
+      }, goodAnswerGroups, goodDefaultOutcome);
     expect(warnings).toEqual([]);
   });
 
   it('should not have warnings for a confusing default outcome', () => {
     var warnings = validatorService.getAllWarnings(
-      currentState, {}, [], badOutcome);
+      currentState, {
+        question: {
+          value: {
+            assumptions: [{
+              top_kind_name: 'variable',
+              top_operator_name: 'p',
+              arguments: [],
+              dummies: []
+            }],
+            results: [{
+              top_kind_name: 'variable',
+              top_operator_name: 'p',
+              arguments: [],
+              dummies: []
+            }],
+            default_proof_string: ''
+          }
+        }
+      }, [], badOutcome);
     expect(warnings).toEqual([]);
   });
 });
diff --git a/extensions/interactions/MultipleChoiceInput/directives/multiple-choice-input-validation.service.spec.ts b/extensions/interactions/MultipleChoiceInput/directives/multiple-choice-input-validation.service.spec.ts
index 7e2b471ce897d..62bd508546f5d 100644
--- a/extensions/interactions/MultipleChoiceInput/directives/multiple-choice-input-validation.service.spec.ts
+++ b/extensions/interactions/MultipleChoiceInput/directives/multiple-choice-input-validation.service.spec.ts
@@ -88,6 +88,9 @@ describe('MultipleChoiceInputValidationService', () => {
           new SubtitledHtml('Option 1', ''),
           new SubtitledHtml('Option 2', '')
         ]
+      },
+      showChoicesInShuffledOrder: {
+        value: true
       }
     };
 
@@ -118,6 +121,11 @@ describe('MultipleChoiceInputValidationService', () => {
   it('should expect a choices customization argument', () => {
     expect(() => {
       validatorService.getAllWarnings(
+      // This throws "Argument of type '{}' is not assignable to
+      // parameter of type 'MultipleChoiceInputCustomizationArgs'." We are
+      // purposely assigning the wrong type of customization args in order
+      // to test validations.
+      // @ts-expect-error
         currentState, {}, goodAnswerGroups, goodDefaultOutcome);
     }).toThrowError(
       'Expected customization arguments to have property: choices');
diff --git a/extensions/interactions/MusicNotesInput/directives/music-notes-input-validation.service.spec.ts b/extensions/interactions/MusicNotesInput/directives/music-notes-input-validation.service.spec.ts
index 4551b1887e40c..c3cfdac10a53f 100644
--- a/extensions/interactions/MusicNotesInput/directives/music-notes-input-validation.service.spec.ts
+++ b/extensions/interactions/MusicNotesInput/directives/music-notes-input-validation.service.spec.ts
@@ -61,7 +61,14 @@ describe('MusicNotesInputValidationService', () => {
 
   it('should be able to perform basic validation', () => {
     var warnings = validatorService.getAllWarnings(
-      currentState, {}, goodAnswerGroups, goodDefaultOutcome);
+      currentState, {
+        sequenceToGuess: {
+          value: []
+        },
+        initialSequence: {
+          value: []
+        }
+      }, goodAnswerGroups, goodDefaultOutcome);
     expect(warnings).toEqual([]);
   });
 });
diff --git a/extensions/interactions/SetInput/directives/set-input-validation.service.spec.ts b/extensions/interactions/SetInput/directives/set-input-validation.service.spec.ts
index 45ecdd318d3b6..4e82e964625f7 100644
--- a/extensions/interactions/SetInput/directives/set-input-validation.service.spec.ts
+++ b/extensions/interactions/SetInput/directives/set-input-validation.service.spec.ts
@@ -104,6 +104,11 @@ describe('SetInputValidationService', () => {
 
       let warnings = validatorService.getAllWarnings(
         currentState,
+        // This throws "Argument of type '{}' is not assignable to
+        // parameter of type 'SetInputCustomizationArgs'." We are purposely
+        // assigning the wrong type of customization args in order to test
+        // validations.
+        // @ts-expect-error
         badCustomizationArgs,
         goodAnswerGroups,
         goodDefaultOutcome
diff --git a/extensions/interactions/customization-args-defs.ts b/extensions/interactions/customization-args-defs.ts
index 82cf7803cab9a..b7a9872a9814d 100644
--- a/extensions/interactions/customization-args-defs.ts
+++ b/extensions/interactions/customization-args-defs.ts
@@ -45,49 +45,49 @@ interface AlgebraicExpressionInputCustomizationArgsBackendDict { }
 
 
 interface CodeReplCustomizationArgsBackendDict {
-  language?: {
+  language: {
     value: string;
   };
-  placeholder?: {
+  placeholder: {
     value: string;
   };
-  preCode?: {
+  preCode: {
     value: string;
   };
-  postCode?: {
+  postCode: {
     value: string;
   };
 }
 export interface CodeReplCustomizationArgs {
-  language?: {
+  language: {
     value: string;
   };
-  placeholder?: {
+  placeholder: {
     value: string;
   };
-  preCode?: {
+  preCode: {
     value: string;
   };
-  postCode?: {
+  postCode: {
     value: string;
   };
 }
 
 
 export interface ContinueCustomizationArgsBackendDict {
-  buttonText?: {
+  buttonText: {
     value: SubtitledUnicodeBackendDict;
   };
 }
 export interface ContinueCustomizationArgs {
-  buttonText?: {
+  buttonText: {
     value: SubtitledUnicode;
   };
 }
 
 
 export interface DragAndDropSortInputCustomizationArgsBackendDict {
-  choices?: {
+  choices: {
     value: SubtitledHtmlBackendDict[];
   };
   allowMultipleItemsInSamePosition: {
@@ -95,7 +95,7 @@ export interface DragAndDropSortInputCustomizationArgsBackendDict {
   }
 }
 export interface DragAndDropSortInputCustomizationArgs {
-  choices?: {
+  choices: {
     value: SubtitledHtml[];
   };
   allowMultipleItemsInSamePosition: {
@@ -105,49 +105,49 @@ export interface DragAndDropSortInputCustomizationArgs {
 
 
 interface EndExplorationCustomizationArgsBackendDict {
-  recommendedExplorationIds?: {
+  recommendedExplorationIds: {
     value: string[];
   };
 }
 export interface EndExplorationCustomizationArgs {
-  recommendedExplorationIds?: {
+  recommendedExplorationIds: {
     value: string[];
   };
 }
 
 
 export interface FractionInputCustomizationArgsBackendDict {
-  requireSimplestForm?: {
+  requireSimplestForm: {
     value: string;
   };
-  allowImproperFraction?: {
+  allowImproperFraction: {
     value: string;
   };
-  allowNonzeroIntegerPart?: {
+  allowNonzeroIntegerPart: {
     value: string;
   };
-  customPlaceholder?: {
+  customPlaceholder: {
     value: SubtitledUnicodeBackendDict;
   };
 }
 export interface FractionInputCustomizationArgs {
-  requireSimplestForm?: {
+  requireSimplestForm: {
     value: string;
   };
-  allowImproperFraction?: {
+  allowImproperFraction: {
     value: string;
   };
-  allowNonzeroIntegerPart?: {
+  allowNonzeroIntegerPart: {
     value: string;
   };
-  customPlaceholder?: {
+  customPlaceholder: {
     value: SubtitledUnicode;
   };
 }
 
 
 interface GraphInputCustomizationArgsBackendDict {
-  graph?: {
+  graph: {
     value: GraphAnswer;
   };
   canAddVertex: {
@@ -173,7 +173,7 @@ interface GraphInputCustomizationArgsBackendDict {
   };
 }
 export interface GraphInputCustomizationArgs {
-  graph?: {
+  graph: {
     value: GraphAnswer;
   };
   canAddVertex: {
@@ -201,78 +201,78 @@ export interface GraphInputCustomizationArgs {
 
 
 interface ImageClickInputCustomizationArgsBackendDict {
-  imageAndRegions?: {
+  imageAndRegions: {
     value: ImageWithRegions;
   };
-  highlightRegionsOnHover?: {
+  highlightRegionsOnHover: {
     value: string;
   };
 }
 export interface ImageClickInputCustomizationArgs {
-  imageAndRegions?: {
+  imageAndRegions: {
     value: ImageWithRegions;
   };
-  highlightRegionsOnHover?: {
-    value: string;
+  highlightRegionsOnHover: {
+    value: boolean;
   };
 }
 
 
 interface InteractiveMapCustomizationArgsBackendDict {
-  latitude?: {
+  latitude: {
     value: number;
   };
-  longitude?: {
+  longitude: {
     value: number;
   };
-  zoom?: {
+  zoom: {
     value: string;
   };
 }
 export interface InteractiveMapCustomizationArgs {
-  latitude?: {
+  latitude: {
     value: number;
   };
-  longitude?: {
+  longitude: {
     value: number;
   };
-  zoom?: {
-    value: string;
+  zoom: {
+    value: number;
   };
 }
 
 
 export interface ItemSelectionInputCustomizationArgsBackendDict {
-  choices?: {
+  choices: {
     value: SubtitledHtmlBackendDict[];
   };
-  maxAllowableSelectionCount?: {
+  maxAllowableSelectionCount: {
     value: number;
   };
-  minAllowableSelectionCount?: {
+  minAllowableSelectionCount: {
     value: number;
   };
 }
 export interface ItemSelectionInputCustomizationArgs {
-  choices?: {
+  choices: {
     value: SubtitledHtml[];
   };
-  maxAllowableSelectionCount?: {
+  maxAllowableSelectionCount: {
     value: number;
   };
-  minAllowableSelectionCount?: {
+  minAllowableSelectionCount: {
     value: number;
   };
 }
 
 
 interface LogicProofCustomizationArgsBackendDict {
-  question?: {
+  question: {
     value: Object;
   };
 }
 export interface LogicProofCustomizationArgs {
-  question?: {
+  question: {
     value: Object;
   };
 }
@@ -283,78 +283,78 @@ export interface MathEquationInputCustomizationArgs { }
 
 
 export interface MultipleChoiceInputCustomizationArgsBackendDict {
-  showChoicesInShuffledOrder?: {
-    value: string;
+  showChoicesInShuffledOrder: {
+    value: boolean;
   };
-  choices?: {
+  choices: {
     value: SubtitledHtmlBackendDict[];
   };
 }
 export interface MultipleChoiceInputCustomizationArgs {
-  showChoicesInShuffledOrder?: {
-    value: string;
+  showChoicesInShuffledOrder: {
+    value: boolean;
   };
-  choices?: {
+  choices: {
     value: SubtitledHtml[];
   };
 }
 
 
 interface MusicNotesInputCustomizationArgsBackendDict {
-  sequenceToGuess?: {
+  sequenceToGuess: {
     value: ReadableMusicNote[];
   };
-  initialSequence?: {
+  initialSequence: {
     value: ReadableMusicNote[];
   };
 }
 export interface MusicNotesInputCustomizationArgs {
-  sequenceToGuess?: {
+  sequenceToGuess: {
     value: ReadableMusicNote[];
   };
-  initialSequence?: {
+  initialSequence: {
     value: ReadableMusicNote[];
   };
 }
 
 
 interface PencilCodeEditorCustomizationArgsBackendDict {
-  initialCode?: {
+  initialCode: {
     value: string;
   };
 }
 export interface PencilCodeEditorCustomizationArgs {
-  initialCode?: {
+  initialCode: {
     value: string;
   };
 }
 
 
 export interface SetInputCustomizationArgsBackendDict {
-  buttonText?: {
+  buttonText: {
     value: SubtitledUnicodeBackendDict;
   };
 }
 export interface SetInputCustomizationArgs {
-  buttonText?: {
+  buttonText: {
     value: SubtitledUnicode;
   };
 }
 
 
 export interface TextInputCustomizationArgsBackendDict {
-  placeholder?: {
+  placeholder: {
     value: SubtitledUnicodeBackendDict;
   };
-  rows?: {
+  rows: {
     value: number;
   };
 }
 export interface TextInputCustomizationArgs {
-  placeholder?: {
+  placeholder: {
     value: SubtitledUnicode;
   };
-  rows?: {
+  rows: {
     value: number;
   };
 }

Get rid of unnecessary optional properties
There are a lot of `?` in the types that are not required. They should be removed.
Get rid of unnecessary optional properties
There are a lot of `?` in the types that are not required. They should be removed.
