Delete visibilitylib directory
This commit is contained in:
parent
f9ece9ef6e
commit
5861bd41bd
|
@ -1,29 +0,0 @@
|
|||
target(
|
||||
dependencies = [
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility",
|
||||
],
|
||||
)
|
||||
|
||||
target(
|
||||
name = "conversations",
|
||||
dependencies = [
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/tweets",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/conversations",
|
||||
],
|
||||
)
|
||||
|
||||
target(
|
||||
name = "tweets",
|
||||
dependencies = [
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/generators",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/tweets",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/tweets",
|
||||
],
|
||||
)
|
||||
|
||||
target(
|
||||
name = "users",
|
||||
dependencies = [
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/users",
|
||||
],
|
||||
)
|
|
@ -1,51 +0,0 @@
|
|||
Overview
|
||||
========
|
||||
|
||||
Visibility Filtering is a centralized rule engine that instructs clients how to alter the display of certain Twitter content on read time. The Visibility Filtering library is responsible for filtering Twitter content to support legal compliance, improve product quality, increase user trust, protect revenue through the use of hard-filtering, visible product treatments, and coarse-grained downranking.
|
||||
|
||||
Notice
|
||||
======
|
||||
|
||||
Visibility Filtering library is currently being reviewed and rebuilt, and part of the code has been removed and is not ready to be shared yet. The remaining part of the code needs further review and will be shared once it’s ready. Also code comments have been sanitized.
|
||||
|
||||
SafetyLevel
|
||||
===========
|
||||
|
||||
Represents the product context in which the Viewer is requesting to view the Content (e.g. Timeline, Profile).
|
||||
|
||||
Features
|
||||
========
|
||||
|
||||
Include safety labels and other metadata of a Tweet, flags set on a User (including the Viewer), relationships between Users (e.g. block, follow), User settings, relationships between Users and Content (e.g. reported for spam).
|
||||
|
||||
Action
|
||||
======
|
||||
|
||||
The way the Visibility Framework instructs the client to respond to the Viewer’s request for Content, and can include hard filtering (e.g. Drop), soft filtering (e.g. Labels and Interstitials), ranking clues, etc.
|
||||
|
||||
Condition
|
||||
=========
|
||||
|
||||
Returns a boolean when given map of Features. Conditions can be combined to determine if a Rule should return an Action or the default (Allow).
|
||||
|
||||
Policy
|
||||
======
|
||||
|
||||
Rules are expressed as a sequence in priority order to create a Visibility Policy. The library has one policy
|
||||
per SafetyLevel.
|
||||
|
||||
RuleEngine
|
||||
===========
|
||||
|
||||
Evaluates the Action for a Request.
|
||||
|
||||
SafetyLabel
|
||||
===========
|
||||
|
||||
A primary labeling mechanism for Safety. A labeled entity associates with tweet, user, Direct Message, media, space etc. Safety labels power different ways of remediations (e.g. applying a SafetyLabel that results in tweet interstitial or notice).
|
||||
|
||||
SafetyLabelType
|
||||
===============
|
||||
|
||||
Describes a particular policy violation for a given noun instance, and usually leads to reduced visibility of the
|
||||
labeled entity in product surfaces. There are many deprecated, and experimental safety label types. Labels with these safety label types have no effect on VF. Additionally, some safety label types are not used, and not designed for VF.
|
|
@ -1,6 +0,0 @@
|
|||
resources(
|
||||
sources = [
|
||||
"com/twitter/visibility/*.csv",
|
||||
"com/twitter/visibility/*.yml",
|
||||
],
|
||||
)
|
|
@ -1,909 +0,0 @@
|
|||
|
||||
visibility_library_enable_all_subscribed_lists_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_ads_business_settings_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_ads_campaign_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_ads_manager_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_appeals_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_article_tweet_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_birdwatch_note_author_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_birdwatch_note_tweets_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_birdwatch_needs_your_help_notifications_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_block_mute_users_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_brand_safety_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_card_poll_voting_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_cards_service_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_communities_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_conversation_focal_prehydration_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_conversation_focal_tweet_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_conversation_injected_tweet_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_conversation_reply_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_curated_trends_representative_tweet:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_curation_policy_violations:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_deprecated_safety_level_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_dev_platform_get_list_tweets_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_des_following_and_followers_user_list_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_des_home_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_des_quote_tweet_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_des_realtime_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_des_realtime_spam_enrichment_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_des_realtime_tweet_filter_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_des_retweeting_users_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_des_tweet_detail_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_des_tweet_liking_users_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_des_user_bookmarks_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_des_user_liked_tweets_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_des_user_mentions_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_des_user_tweets_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_dev_platform_compliance_stream_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_direct_messages_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_direct_messages_conversation_list_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_direct_messages_conversation_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_direct_messages_inbox_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_direct_messages_muted_users_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_direct_messages_pinned_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_elevated_quote_tweet_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_direct_messages_search_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_embedded_tweet_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_embeds_public_interest_notice_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_embed_tweet_markup_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_write_path_limited_actions_enforcement_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_filter_all_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_filter_all_placeholder_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_filter_default_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_filter_none_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_followed_topics_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_follower_connections_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_following_and_followers_user_list_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_for_development_only_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_friends_following_list_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_graphql_default_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_gryphon_decks_and_columns_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_humanization_nudge_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_kitchen_sink_development_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_list_header_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_list_memberships_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_list_ownerships_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_list_recommendations_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_list_search_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_list_subscriptions_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_live_pipeline_engagement_counts_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_live_video_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_magic_recs_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_magic_recs_aggressive_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_magic_recs_aggressive_v2_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_magic_recs_v2_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_minimal_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_moderated_tweets_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_moments_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_nearby_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_new_user_experience_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_notifications_ibis_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_notifications_platform_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_notifications_platform_push_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_notifications_read_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_notifications_timeline_device_follow_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_notifications_write_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_notification_writer_v2_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_notifications_writer_tweet_hydrator_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_quick_promote_tweet_eligibility_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_quote_tweet_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_quoted_tweet_rules_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_recommendations_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_recos_video_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_recos_write_path_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_replies_grouping_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_report_center_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_returning_user_experience_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_returning_user_experience_focal_tweet_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_revenue_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_rito_actioned_tweet_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_safe_search_minimal_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_safe_search_strict_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_hydration_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_latest_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_mixer_srp_minimal_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_mixer_srp_strict_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_user_search_srp_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_user_search_typeahead_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_people_srp_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_people_typeahead_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_photo_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_top_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_trend_takeover_promoted_tweet_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_video_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_latest_user_rules_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_shopping_manager_spy_mode_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_signals_reactions_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_signals_tweet_reacting_users_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_social_proof_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_soft_intervention_pivot_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_space_fleetline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_space_home_timeline_upranking_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_space_join_screen_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_space_notifications_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_spaces_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_spaces_participants_safety_level:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_spaces_seller_application_status_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_spaces_sharing_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_space_tweet_avatar_home_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_stickers_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_strato_ext_limited_engagements_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_stream_services_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_test_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_bookmark_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_content_controls_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_conversations_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_conversations_downranking_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_conversations_downranking_minimal_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_favorites_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_self_view_timeline_favorites_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_focal_tweet_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_following_activity_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_home_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_home_communities_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_home_hydration_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_home_latest_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_home_recommendations_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_home_topic_follow_recommendations_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_scorer_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_topics_landing_page_topic_recommendations_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_explore_recommendations_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_moderated_tweets_hydration_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_injection_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_liked_by_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_lists_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_media_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_mentions_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_profile_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_profile_all_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_profile_spaces_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_profile_super_follows_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_reactive_blending_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_retweeted_by_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_super_liked_by_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_tombstoning_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_trends_representative_tweet_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_trusted_friends_user_list_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_twitter_delegate_user_list_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_tweet_detail_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_tweet_detail_non_too_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_tweet_detail_with_injections_hydration_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_tweet_engagers_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_tweet_reply_nudge_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_tweet_scoped_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_tweet_writes_api_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_twitter_article_compose_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_twitter_article_profile_tab_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_twitter_article_read_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_user_profile_header_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_user_milestone_recommendation_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_user_scoped_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_user_settings_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_video_ads_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_home_promoted_hydration_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_super_follower_connnections_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_super_like_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_topic_recommendations_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_ads_reporting_dashboard_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_top_qig_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_content_control_tool_install_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_conversation_control_rules:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_community_tweets_rules:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_drop_community_tweet_with_undefined_community_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_p_spammy_tweet_downrank_convos_low_quality:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_high_p_spammy_tweet_score_search_tweet_label_drop_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_rito_actioned_tweet_downrank_convos_low_quality:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_toxic_reply_filter_conversation:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_toxic_reply_filter_notifications:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_new_sensitive_media_settings_interstitial_rules_home_timeline:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_legacy_sensitive_media_rules_home_timeline:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_new_sensitive_media_settings_interstitial_rules_conversation:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_legacy_sensitive_media_rules_conversation:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_new_sensitive_media_settings_interstitials_rules_profile_timeline:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_legacy_sensitive_media_rules_profile_timeline:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_new_sensitive_media_settings_interstitials_rules_tweet_detail:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_legacy_sensitive_media_rules_tweet_detail:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_legacy_sensitive_media_rules_direct_messages:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_smyte_spam_tweet_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_high_spammy_tweet_content_score_search_latest_tweet_label_drop_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_high_spammy_tweet_content_score_search_top_tweet_label_drop_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_high_spammy_tweet_content_score_convo_downrank_abusive_quality_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_high_cryptospam_score_convo_downrank_abusive_quality_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_high_spammy_tweet_content_score_trends_top_tweet_label_drop_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_high_spammy_tweet_content_score_trends_latest_tweet_label_drop_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_gore_and_violence_topic_high_recall_tweet_label_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_limit_replies_followers_conversation_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_blink_bad_downranking_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_blink_worst_downranking_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_copypasta_spam_downrank_convos_abusive_quality_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_copypasta_spam_search_drop_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_spammy_user_model_high_precision_drop_tweet_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_avoid_nsfw_rules:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_reported_tweet_interstitial_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_reported_tweet_interstitial_search_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_drop_exclusive_tweet_content_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_drop_exclusive_tweet_content_rule_fail_closed:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_drop_all_exclusive_tweets_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_drop_all_exclusive_tweets_rule_fail_closed:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_tombstone_exclusive_quoted_tweet_content_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_downrank_spam_reply_sectioning_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_nsfw_text_sectioning_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_ipi_safe_search_without_user_in_query_drop_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_timeline_home_promoted_tweet_health_enforcement_rules:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_muted_keyword_filtering_space_title_notifications_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_drop_tweets_with_georestricted_media_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_drop_all_trusted_friends_tweets_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_drop_all_trusted_friends_tweet_content_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_drop_all_collab_invitation_tweets_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_fetch_tweet_reported_perspective:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_fetch_tweet_media_metadata:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_follow_check_in_mutedkeyword:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_media_interstitial_composition:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_verdict_scribing_from_tweet_visibility_library:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_verdict_logger_event_publisher_instantiation_from_tweet_visibility_library:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_verdict_scribing_from_timeline_conversations_visibility_library:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_verdict_logger_event_publisher_instantiation_from_timeline_conversations_visibility_library:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_verdict_scribing_from_blender_visibility_library:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_verdict_logger_event_publisher_instantiation_from_blender_visibility_library:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_verdict_scribing_from_search_visibility_library:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_verdict_logger_event_publisher_instantiation_from_search_visibility_library:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_localized_tombstones_on_visibility_results:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_short_circuiting_from_tweet_visibility_library:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_card_visibility_library_card_uri_parsing:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_short_circuiting_from_timeline_conversations_visibility_library:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_short_circuiting_from_blender_visibility_library:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_short_circuiting_from_search_visibility_library:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_nsfw_text_high_precision_drop_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_spammy_tweet_rule_verdict_logging:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_downlevel_rule_verdict_logging:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_likely_likely_ivs_user_label_drop_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_card_uri_root_domain_deny_list_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_community_non_member_poll_card_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_community_non_member_poll_card_rule_fail_closed:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_experimental_nudge_label_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_user_self_view_only_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_nsfw_high_precision_user_label_avoid_tweet_rule_enabled:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_new_ad_avoidance_rules:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_nsfa_high_recall_ad_avoidance_rules:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_nsfa_keywords_high_precision_ad_avoidance_rules:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_stale_tweet_drop_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_stale_tweet_drop_rule_fail_closed:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_edit_history_timeline_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_delete_state_tweet_rules:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_spaces_sharing_nsfw_drop_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_viewer_is_soft_user_drop_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_backend_limited_actions:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_base_qig_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_notifications_qig_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_access_internal_promoted_content_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_pdna_quoted_tweet_tombstone_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_spam_quoted_tweet_tombstone_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_nsfw_hp_quoted_tweet_drop_experiment_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_nsfw_hp_quoted_tweet_tombstone_experiment_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_inner_quoted_tweet_viewer_blocks_author_interstitial_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_inner_quoted_tweet_viewer_mutes_author_interstitial_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_experimental_rule_engine:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_fosnr_rules:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_localized_interstitial_generator:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_convos_enable_legacy_interstitial:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_convos_enable_localized_interstitial:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_profile_mixer_media_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_profile_mixer_favorites_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_zipbird_consumer_archives_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_tweet_award_safety_level:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_disable_legacy_interstitial_filtered_reason:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_search_basic_block_mute_rules:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_localized_interstitial_user_state_lib:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_abusive_behavior_drop_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_abusive_behavior_interstitial_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_abusive_behavior_limited_engagements_rule:
|
||||
default_availability: 10000
|
||||
|
||||
visibility_library_enable_not_graduated_downrank_convos_abusive_quality_rule:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_not_graduated_search_drop_rule:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_not_graduated_drop_rule:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_memoize_safety_level_params:
|
||||
default_availability: 0
|
||||
|
||||
visibility_library_enable_author_blocks_viewer_drop_rule:
|
||||
default_availability: 0
|
|
@ -1,42 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"abdecider/src/main/scala",
|
||||
"configapi/configapi-core",
|
||||
"decider/src/main/scala",
|
||||
"featureswitches/featureswitches-core/src/main/scala",
|
||||
"servo/decider/src/main/scala",
|
||||
"servo/util/src/main/scala",
|
||||
"stitch/stitch-core",
|
||||
"util/util-logging/src/main/scala",
|
||||
"util/util-stats/src/main/scala",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common/actions",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common/stitch",
|
||||
"visibility/lib/src/main/resources/config",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/configapi",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/configs",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/params",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/engine",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/features",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/generators",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/models",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/rules",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/rules/generators",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/rules/providers",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/util",
|
||||
],
|
||||
exports = [
|
||||
"configapi/configapi-core",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common/actions",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/configapi",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/configs",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/models",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/rules",
|
||||
],
|
||||
)
|
|
@ -1,387 +0,0 @@
|
|||
package com.twitter.visibility
|
||||
|
||||
import com.twitter.abdecider.LoggingABDecider
|
||||
import com.twitter.abdecider.NullABDecider
|
||||
import com.twitter.decider.Decider
|
||||
import com.twitter.decider.NullDecider
|
||||
import com.twitter.featureswitches.v2.FeatureSwitches
|
||||
import com.twitter.featureswitches.v2.NullFeatureSwitches
|
||||
import com.twitter.finagle.stats.NullStatsReceiver
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.logging.Logger
|
||||
import com.twitter.logging.NullLogger
|
||||
import com.twitter.servo.util.Gate
|
||||
import com.twitter.servo.util.MemoizingStatsReceiver
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.timelines.configapi.Params
|
||||
import com.twitter.util.Try
|
||||
import com.twitter.visibility.builder._
|
||||
import com.twitter.visibility.common.stitch.StitchHelpers
|
||||
import com.twitter.visibility.configapi.VisibilityParams
|
||||
import com.twitter.visibility.configapi.configs.VisibilityDeciderGates
|
||||
import com.twitter.visibility.engine.DeciderableVisibilityRuleEngine
|
||||
import com.twitter.visibility.engine.VisibilityResultsMetricRecorder
|
||||
import com.twitter.visibility.engine.VisibilityRuleEngine
|
||||
import com.twitter.visibility.engine.VisibilityRulePreprocessor
|
||||
import com.twitter.visibility.features.FeatureMap
|
||||
import com.twitter.visibility.models.ContentId
|
||||
import com.twitter.visibility.models.SafetyLevel
|
||||
import com.twitter.visibility.models.ViewerContext
|
||||
import com.twitter.visibility.rules.EvaluationContext
|
||||
import com.twitter.visibility.rules.Rule
|
||||
import com.twitter.visibility.rules.generators.TweetRuleGenerator
|
||||
import com.twitter.visibility.rules.providers.InjectedPolicyProvider
|
||||
import com.twitter.visibility.util.DeciderUtil
|
||||
import com.twitter.visibility.util.FeatureSwitchUtil
|
||||
import com.twitter.visibility.util.LoggingUtil
|
||||
|
||||
object VisibilityLibrary {
|
||||
|
||||
object Builder {
|
||||
|
||||
def apply(log: Logger, statsReceiver: StatsReceiver): Builder = new Builder(
|
||||
log,
|
||||
new MemoizingStatsReceiver(statsReceiver)
|
||||
)
|
||||
}
|
||||
|
||||
case class Builder(
|
||||
log: Logger,
|
||||
statsReceiver: StatsReceiver,
|
||||
decider: Option[Decider] = None,
|
||||
abDecider: Option[LoggingABDecider] = None,
|
||||
featureSwitches: Option[FeatureSwitches] = None,
|
||||
enableStitchProfiling: Gate[Unit] = Gate.False,
|
||||
captureDebugStats: Gate[Unit] = Gate.False,
|
||||
enableComposableActions: Gate[Unit] = Gate.False,
|
||||
enableFailClosed: Gate[Unit] = Gate.False,
|
||||
enableShortCircuiting: Gate[Unit] = Gate.True,
|
||||
memoizeSafetyLevelParams: Gate[Unit] = Gate.False) {
|
||||
|
||||
def withDecider(decider: Decider): Builder = copy(decider = Some(decider))
|
||||
|
||||
@deprecated("use .withDecider and pass in a decider that is properly configured per DC")
|
||||
def withDefaultDecider(isLocal: Boolean, useLocalOverrides: Boolean = false): Builder = {
|
||||
if (isLocal) {
|
||||
withLocalDecider
|
||||
} else {
|
||||
withDecider(
|
||||
DeciderUtil.mkDecider(
|
||||
useLocalDeciderOverrides = useLocalOverrides,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
def withLocalDecider(): Builder = withDecider(DeciderUtil.mkLocalDecider)
|
||||
|
||||
def withNullDecider(): Builder =
|
||||
withDecider(new NullDecider(isAvail = true, availabilityDefined = true))
|
||||
|
||||
def withABDecider(abDecider: LoggingABDecider, featureSwitches: FeatureSwitches): Builder =
|
||||
abDecider match {
|
||||
case abd: NullABDecider =>
|
||||
copy(abDecider = Some(abd), featureSwitches = Some(NullFeatureSwitches))
|
||||
case _ =>
|
||||
copy(
|
||||
abDecider = Some(abDecider),
|
||||
featureSwitches = Some(featureSwitches)
|
||||
)
|
||||
}
|
||||
|
||||
def withABDecider(abDecider: LoggingABDecider): Builder = abDecider match {
|
||||
case abd: NullABDecider =>
|
||||
withABDecider(abDecider = abd, featureSwitches = NullFeatureSwitches)
|
||||
case _ =>
|
||||
withABDecider(
|
||||
abDecider = abDecider,
|
||||
featureSwitches =
|
||||
FeatureSwitchUtil.mkVisibilityLibraryFeatureSwitches(abDecider, statsReceiver)
|
||||
)
|
||||
}
|
||||
|
||||
def withClientEventsLogger(clientEventsLogger: Logger): Builder =
|
||||
withABDecider(DeciderUtil.mkABDecider(Some(clientEventsLogger)))
|
||||
|
||||
def withDefaultABDecider(isLocal: Boolean): Builder =
|
||||
if (isLocal) {
|
||||
withABDecider(NullABDecider)
|
||||
} else {
|
||||
withClientEventsLogger(LoggingUtil.mkDefaultLogger(statsReceiver))
|
||||
}
|
||||
|
||||
def withNullABDecider(): Builder = withABDecider(NullABDecider)
|
||||
|
||||
def withEnableStitchProfiling(gate: Gate[Unit]): Builder =
|
||||
copy(enableStitchProfiling = gate)
|
||||
|
||||
def withCaptureDebugStats(gate: Gate[Unit]): Builder =
|
||||
copy(captureDebugStats = gate)
|
||||
|
||||
def withEnableComposableActions(gate: Gate[Unit]): Builder =
|
||||
copy(enableComposableActions = gate)
|
||||
|
||||
def withEnableComposableActions(gateBoolean: Boolean): Builder = {
|
||||
val gate = Gate.const(gateBoolean)
|
||||
copy(enableComposableActions = gate)
|
||||
}
|
||||
|
||||
def withEnableFailClosed(gate: Gate[Unit]): Builder =
|
||||
copy(enableFailClosed = gate)
|
||||
|
||||
def withEnableFailClosed(gateBoolean: Boolean): Builder = {
|
||||
val gate = Gate.const(gateBoolean)
|
||||
copy(enableFailClosed = gate)
|
||||
}
|
||||
|
||||
def withEnableShortCircuiting(gate: Gate[Unit]): Builder =
|
||||
copy(enableShortCircuiting = gate)
|
||||
|
||||
def withEnableShortCircuiting(gateBoolean: Boolean): Builder = {
|
||||
val gate = Gate.const(gateBoolean)
|
||||
copy(enableShortCircuiting = gate)
|
||||
}
|
||||
|
||||
def memoizeSafetyLevelParams(gate: Gate[Unit]): Builder =
|
||||
copy(memoizeSafetyLevelParams = gate)
|
||||
|
||||
def memoizeSafetyLevelParams(gateBoolean: Boolean): Builder = {
|
||||
val gate = Gate.const(gateBoolean)
|
||||
copy(memoizeSafetyLevelParams = gate)
|
||||
}
|
||||
|
||||
def build(): VisibilityLibrary = {
|
||||
|
||||
(decider, abDecider, featureSwitches) match {
|
||||
case (None, _, _) =>
|
||||
throw new IllegalStateException(
|
||||
"Decider is unset! If intentional, please call .withNullDecider()."
|
||||
)
|
||||
|
||||
case (_, None, _) =>
|
||||
throw new IllegalStateException(
|
||||
"ABDecider is unset! If intentional, please call .withNullABDecider()."
|
||||
)
|
||||
|
||||
case (_, _, None) =>
|
||||
throw new IllegalStateException(
|
||||
"FeatureSwitches is unset! This is a bug."
|
||||
)
|
||||
|
||||
case (Some(d), Some(abd), Some(fs)) =>
|
||||
new VisibilityLibrary(
|
||||
statsReceiver,
|
||||
d,
|
||||
abd,
|
||||
VisibilityParams(log, statsReceiver, d, abd, fs),
|
||||
enableStitchProfiling = enableStitchProfiling,
|
||||
captureDebugStats = captureDebugStats,
|
||||
enableComposableActions = enableComposableActions,
|
||||
enableFailClosed = enableFailClosed,
|
||||
enableShortCircuiting = enableShortCircuiting,
|
||||
memoizeSafetyLevelParams = memoizeSafetyLevelParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val nullDecider = new NullDecider(true, true)
|
||||
|
||||
lazy val NullLibrary: VisibilityLibrary = new VisibilityLibrary(
|
||||
NullStatsReceiver,
|
||||
nullDecider,
|
||||
NullABDecider,
|
||||
VisibilityParams(
|
||||
NullLogger,
|
||||
NullStatsReceiver,
|
||||
nullDecider,
|
||||
NullABDecider,
|
||||
NullFeatureSwitches),
|
||||
enableStitchProfiling = Gate.False,
|
||||
captureDebugStats = Gate.False,
|
||||
enableComposableActions = Gate.False,
|
||||
enableFailClosed = Gate.False,
|
||||
enableShortCircuiting = Gate.True,
|
||||
memoizeSafetyLevelParams = Gate.False
|
||||
)
|
||||
}
|
||||
|
||||
class VisibilityLibrary private[VisibilityLibrary] (
|
||||
baseStatsReceiver: StatsReceiver,
|
||||
decider: Decider,
|
||||
abDecider: LoggingABDecider,
|
||||
visibilityParams: VisibilityParams,
|
||||
enableStitchProfiling: Gate[Unit],
|
||||
captureDebugStats: Gate[Unit],
|
||||
enableComposableActions: Gate[Unit],
|
||||
enableFailClosed: Gate[Unit],
|
||||
enableShortCircuiting: Gate[Unit],
|
||||
memoizeSafetyLevelParams: Gate[Unit]) {
|
||||
|
||||
val statsReceiver: StatsReceiver =
|
||||
new MemoizingStatsReceiver(baseStatsReceiver.scope("visibility_library"))
|
||||
|
||||
val metricsRecorder = VisibilityResultsMetricRecorder(statsReceiver, captureDebugStats)
|
||||
|
||||
val visParams: VisibilityParams = visibilityParams
|
||||
|
||||
val visibilityDeciderGates = VisibilityDeciderGates(decider)
|
||||
|
||||
val profileStats: MemoizingStatsReceiver = new MemoizingStatsReceiver(
|
||||
statsReceiver.scope("profiling"))
|
||||
|
||||
val perSafetyLevelProfileStats: StatsReceiver = profileStats.scope("for_safety_level")
|
||||
|
||||
val featureMapBuilder: FeatureMapBuilder.Build =
|
||||
FeatureMapBuilder(statsReceiver, enableStitchProfiling)
|
||||
|
||||
private lazy val tweetRuleGenerator = new TweetRuleGenerator()
|
||||
lazy val policyProvider = new InjectedPolicyProvider(
|
||||
visibilityDeciderGates = visibilityDeciderGates,
|
||||
tweetRuleGenerator = tweetRuleGenerator)
|
||||
|
||||
val candidateVisibilityRulePreprocessor: VisibilityRulePreprocessor = VisibilityRulePreprocessor(
|
||||
metricsRecorder,
|
||||
policyProviderOpt = Some(policyProvider)
|
||||
)
|
||||
|
||||
val fallbackVisibilityRulePreprocessor: VisibilityRulePreprocessor = VisibilityRulePreprocessor(
|
||||
metricsRecorder)
|
||||
|
||||
lazy val candidateVisibilityRuleEngine: VisibilityRuleEngine = VisibilityRuleEngine(
|
||||
Some(candidateVisibilityRulePreprocessor),
|
||||
metricsRecorder,
|
||||
enableComposableActions,
|
||||
enableFailClosed,
|
||||
policyProviderOpt = Some(policyProvider)
|
||||
)
|
||||
|
||||
lazy val fallbackVisibilityRuleEngine: VisibilityRuleEngine = VisibilityRuleEngine(
|
||||
Some(fallbackVisibilityRulePreprocessor),
|
||||
metricsRecorder,
|
||||
enableComposableActions,
|
||||
enableFailClosed)
|
||||
|
||||
val ruleEngineVersionStatsReceiver = statsReceiver.scope("rule_engine_version")
|
||||
def isReleaseCandidateEnabled: Boolean = visibilityDeciderGates.enableExperimentalRuleEngine()
|
||||
|
||||
private def visibilityRuleEngine: DeciderableVisibilityRuleEngine = {
|
||||
if (isReleaseCandidateEnabled) {
|
||||
ruleEngineVersionStatsReceiver.counter("release_candidate").incr()
|
||||
candidateVisibilityRuleEngine
|
||||
} else {
|
||||
ruleEngineVersionStatsReceiver.counter("fallback").incr()
|
||||
fallbackVisibilityRuleEngine
|
||||
}
|
||||
}
|
||||
|
||||
private def profileStitch[A](result: Stitch[A], safetyLevelName: String): Stitch[A] =
|
||||
if (enableStitchProfiling()) {
|
||||
StitchHelpers.profileStitch(
|
||||
result,
|
||||
Seq(profileStats, perSafetyLevelProfileStats.scope(safetyLevelName))
|
||||
)
|
||||
} else {
|
||||
result
|
||||
}
|
||||
|
||||
def getParams(viewerContext: ViewerContext, safetyLevel: SafetyLevel): Params = {
|
||||
if (memoizeSafetyLevelParams()) {
|
||||
visibilityParams.memoized(viewerContext, safetyLevel)
|
||||
} else {
|
||||
visibilityParams(viewerContext, safetyLevel)
|
||||
}
|
||||
}
|
||||
|
||||
def evaluationContextBuilder(viewerContext: ViewerContext): EvaluationContext.Builder =
|
||||
EvaluationContext
|
||||
.Builder(statsReceiver, visibilityParams, viewerContext)
|
||||
.withMemoizedParams(memoizeSafetyLevelParams)
|
||||
|
||||
def runRuleEngine(
|
||||
contentId: ContentId,
|
||||
featureMap: FeatureMap,
|
||||
evaluationContextBuilder: EvaluationContext.Builder,
|
||||
safetyLevel: SafetyLevel
|
||||
): Stitch[VisibilityResult] =
|
||||
profileStitch(
|
||||
visibilityRuleEngine(
|
||||
evaluationContextBuilder.build(safetyLevel),
|
||||
safetyLevel,
|
||||
new VisibilityResultBuilder(contentId, featureMap),
|
||||
enableShortCircuiting
|
||||
),
|
||||
safetyLevel.name
|
||||
)
|
||||
|
||||
def runRuleEngine(
|
||||
contentId: ContentId,
|
||||
featureMap: FeatureMap,
|
||||
viewerContext: ViewerContext,
|
||||
safetyLevel: SafetyLevel
|
||||
): Stitch[VisibilityResult] =
|
||||
profileStitch(
|
||||
visibilityRuleEngine(
|
||||
EvaluationContext(safetyLevel, getParams(viewerContext, safetyLevel), statsReceiver),
|
||||
safetyLevel,
|
||||
new VisibilityResultBuilder(contentId, featureMap),
|
||||
enableShortCircuiting
|
||||
),
|
||||
safetyLevel.name
|
||||
)
|
||||
|
||||
def runRuleEngine(
|
||||
viewerContext: ViewerContext,
|
||||
safetyLevel: SafetyLevel,
|
||||
preprocessedResultBuilder: VisibilityResultBuilder,
|
||||
preprocessedRules: Seq[Rule]
|
||||
): Stitch[VisibilityResult] =
|
||||
profileStitch(
|
||||
visibilityRuleEngine(
|
||||
EvaluationContext(safetyLevel, getParams(viewerContext, safetyLevel), statsReceiver),
|
||||
safetyLevel,
|
||||
preprocessedResultBuilder,
|
||||
enableShortCircuiting,
|
||||
Some(preprocessedRules)
|
||||
),
|
||||
safetyLevel.name
|
||||
)
|
||||
|
||||
def runRuleEngineBatch(
|
||||
contentIds: Seq[ContentId],
|
||||
featureMapProvider: (ContentId, SafetyLevel) => FeatureMap,
|
||||
viewerContext: ViewerContext,
|
||||
safetyLevel: SafetyLevel,
|
||||
): Stitch[Seq[Try[VisibilityResult]]] = {
|
||||
val params = getParams(viewerContext, safetyLevel)
|
||||
profileStitch(
|
||||
Stitch.traverse(contentIds) { contentId =>
|
||||
visibilityRuleEngine(
|
||||
EvaluationContext(safetyLevel, params, NullStatsReceiver),
|
||||
safetyLevel,
|
||||
new VisibilityResultBuilder(contentId, featureMapProvider(contentId, safetyLevel)),
|
||||
enableShortCircuiting
|
||||
).liftToTry
|
||||
},
|
||||
safetyLevel.name
|
||||
)
|
||||
}
|
||||
|
||||
def runRuleEngineBatch(
|
||||
contentIds: Seq[ContentId],
|
||||
featureMapProvider: (ContentId, SafetyLevel) => FeatureMap,
|
||||
evaluationContextBuilder: EvaluationContext.Builder,
|
||||
safetyLevel: SafetyLevel
|
||||
): Stitch[Seq[Try[VisibilityResult]]] = {
|
||||
val evaluationContext = evaluationContextBuilder.build(safetyLevel)
|
||||
profileStitch(
|
||||
Stitch.traverse(contentIds) { contentId =>
|
||||
visibilityRuleEngine(
|
||||
evaluationContext,
|
||||
safetyLevel,
|
||||
new VisibilityResultBuilder(contentId, featureMapProvider(contentId, safetyLevel)),
|
||||
enableShortCircuiting
|
||||
).liftToTry
|
||||
},
|
||||
safetyLevel.name
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/com/twitter/src/java/com/twitter/logpipeline/client:logpipeline-event-publisher-thin",
|
||||
"configapi/configapi-core",
|
||||
"decider/src/main/scala",
|
||||
"servo/util/src/main/scala",
|
||||
"src/thrift/com/twitter/search/common:constants-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-result-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:tweet-rtf-event-scala",
|
||||
"stitch/stitch-core",
|
||||
"util/util-stats/src/main/scala/com/twitter/finagle/stats",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common/actions",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common/actions/converter/scala",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common/stitch",
|
||||
"visibility/common/src/main/thrift/com/twitter/visibility:action-scala",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/configs",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/features",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/models",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/rules",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/util",
|
||||
"visibility/lib/src/main/thrift/com/twitter/visibility/logging:vf-logging-scala",
|
||||
],
|
||||
)
|
|
@ -1,64 +0,0 @@
|
|||
package com.twitter.visibility.builder
|
||||
|
||||
import com.twitter.finagle.stats.NullStatsReceiver
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.servo.util.Gate
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.features._
|
||||
import com.twitter.visibility.common.stitch.StitchHelpers
|
||||
import scala.collection.mutable
|
||||
|
||||
object FeatureMapBuilder {
|
||||
type Build = Seq[FeatureMapBuilder => FeatureMapBuilder] => FeatureMap
|
||||
|
||||
def apply(
|
||||
statsReceiver: StatsReceiver = NullStatsReceiver,
|
||||
enableStitchProfiling: Gate[Unit] = Gate.False
|
||||
): Build =
|
||||
fns =>
|
||||
Function
|
||||
.chain(fns).apply(
|
||||
new FeatureMapBuilder(statsReceiver, enableStitchProfiling)
|
||||
).build
|
||||
}
|
||||
|
||||
class FeatureMapBuilder private[builder] (
|
||||
statsReceiver: StatsReceiver,
|
||||
enableStitchProfiling: Gate[Unit] = Gate.False) {
|
||||
|
||||
private[this] val hydratedScope =
|
||||
statsReceiver.scope("visibility_result_builder").scope("hydrated")
|
||||
|
||||
val mapBuilder: mutable.Builder[(Feature[_], Stitch[_]), Map[Feature[_], Stitch[_]]] =
|
||||
Map.newBuilder[Feature[_], Stitch[_]]
|
||||
|
||||
val constantMapBuilder: mutable.Builder[(Feature[_], Any), Map[Feature[_], Any]] =
|
||||
Map.newBuilder[Feature[_], Any]
|
||||
|
||||
def build: FeatureMap = new FeatureMap(mapBuilder.result(), constantMapBuilder.result())
|
||||
|
||||
def withConstantFeature[T](feature: Feature[T], value: T): FeatureMapBuilder = {
|
||||
val anyValue: Any = value.asInstanceOf[Any]
|
||||
constantMapBuilder += (feature -> anyValue)
|
||||
this
|
||||
}
|
||||
|
||||
def withFeature[T](feature: Feature[T], stitch: Stitch[T]): FeatureMapBuilder = {
|
||||
val profiledStitch = if (enableStitchProfiling()) {
|
||||
val featureScope = hydratedScope.scope(feature.name)
|
||||
StitchHelpers.profileStitch(stitch, Seq(hydratedScope, featureScope))
|
||||
} else {
|
||||
stitch
|
||||
}
|
||||
|
||||
val featureStitchRef = Stitch.ref(profiledStitch)
|
||||
|
||||
mapBuilder += FeatureMap.rescueFeatureTuple(feature -> featureStitchRef)
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
def withConstantFeature[T](feature: Feature[T], option: Option[T]): FeatureMapBuilder = {
|
||||
option.map(withConstantFeature(feature, _)).getOrElse(this)
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
package com.twitter.visibility.builder
|
||||
|
||||
import com.twitter.datatools.entityservice.entities.thriftscala.FleetInterstitial
|
||||
import com.twitter.decider.Decider
|
||||
import com.twitter.decider.Decider.NullDecider
|
||||
import com.twitter.finagle.stats.NullStatsReceiver
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.logpipeline.client.common.EventPublisher
|
||||
import com.twitter.logpipeline.client.EventPublisherManager
|
||||
import com.twitter.logpipeline.client.serializers.EventLogMsgThriftStructSerializer
|
||||
import com.twitter.spam.rtf.thriftscala.SafetyLevel
|
||||
import com.twitter.visibility.builder.VerdictLogger.FailureCounterName
|
||||
import com.twitter.visibility.builder.VerdictLogger.SuccessCounterName
|
||||
import com.twitter.visibility.features.Feature
|
||||
import com.twitter.visibility.logging.thriftscala.ActionSource
|
||||
import com.twitter.visibility.logging.thriftscala.EntityId
|
||||
import com.twitter.visibility.logging.thriftscala.EntityIdType
|
||||
import com.twitter.visibility.logging.thriftscala.EntityIdValue
|
||||
import com.twitter.visibility.logging.thriftscala.HealthActionType
|
||||
import com.twitter.visibility.logging.thriftscala.MisinfoPolicyCategory
|
||||
import com.twitter.visibility.logging.thriftscala.VFLibType
|
||||
import com.twitter.visibility.logging.thriftscala.VFVerdictLogEntry
|
||||
import com.twitter.visibility.models.ContentId
|
||||
import com.twitter.visibility.rules._
|
||||
|
||||
object VerdictLogger {
|
||||
|
||||
private val BaseStatsNamespace = "vf_verdict_logger"
|
||||
private val FailureCounterName = "failures"
|
||||
private val SuccessCounterName = "successes"
|
||||
val LogCategoryName: String = "visibility_filtering_verdicts"
|
||||
|
||||
val Empty: VerdictLogger = new VerdictLogger(NullStatsReceiver, NullDecider, None)
|
||||
|
||||
def apply(
|
||||
statsReceiver: StatsReceiver,
|
||||
decider: Decider
|
||||
): VerdictLogger = {
|
||||
val eventPublisher: EventPublisher[VFVerdictLogEntry] =
|
||||
EventPublisherManager
|
||||
.newScribePublisherBuilder(
|
||||
LogCategoryName,
|
||||
EventLogMsgThriftStructSerializer.getNewSerializer[VFVerdictLogEntry]()).build()
|
||||
new VerdictLogger(statsReceiver.scope(BaseStatsNamespace), decider, Some(eventPublisher))
|
||||
}
|
||||
}
|
||||
|
||||
class VerdictLogger(
|
||||
statsReceiver: StatsReceiver,
|
||||
decider: Decider,
|
||||
publisherOpt: Option[EventPublisher[VFVerdictLogEntry]]) {
|
||||
|
||||
def log(
|
||||
verdictLogEntry: VFVerdictLogEntry,
|
||||
publisher: EventPublisher[VFVerdictLogEntry]
|
||||
): Unit = {
|
||||
publisher
|
||||
.publish(verdictLogEntry)
|
||||
.onSuccess(_ => statsReceiver.counter(SuccessCounterName).incr())
|
||||
.onFailure { e =>
|
||||
statsReceiver.counter(FailureCounterName).incr()
|
||||
statsReceiver.scope(FailureCounterName).counter(e.getClass.getName).incr()
|
||||
}
|
||||
}
|
||||
|
||||
private def toEntityId(contentId: ContentId): Option[EntityId] = {
|
||||
contentId match {
|
||||
case ContentId.TweetId(id) => Some(EntityId(EntityIdType.TweetId, EntityIdValue.EntityId(id)))
|
||||
case ContentId.UserId(id) => Some(EntityId(EntityIdType.UserId, EntityIdValue.EntityId(id)))
|
||||
case ContentId.QuotedTweetRelationship(outerTweetId, _) =>
|
||||
Some(EntityId(EntityIdType.TweetId, EntityIdValue.EntityId(outerTweetId)))
|
||||
case ContentId.NotificationId(Some(id)) =>
|
||||
Some(EntityId(EntityIdType.NotificationId, EntityIdValue.EntityId(id)))
|
||||
case ContentId.DmId(id) => Some(EntityId(EntityIdType.DmId, EntityIdValue.EntityId(id)))
|
||||
case ContentId.BlenderTweetId(id) =>
|
||||
Some(EntityId(EntityIdType.TweetId, EntityIdValue.EntityId(id)))
|
||||
case ContentId.SpacePlusUserId(_) =>
|
||||
}
|
||||
}
|
||||
|
||||
private def getLogEntryData(
|
||||
actingRule: Option[Rule],
|
||||
secondaryActingRules: Seq[Rule],
|
||||
verdict: Action,
|
||||
secondaryVerdicts: Seq[Action],
|
||||
resolvedFeatureMap: Map[Feature[_], Any]
|
||||
): (Seq[String], Seq[ActionSource], Seq[HealthActionType], Option[FleetInterstitial]) = {
|
||||
actingRule
|
||||
.filter {
|
||||
case decideredRule: DoesLogVerdictDecidered =>
|
||||
decider.isAvailable(decideredRule.verdictLogDeciderKey.toString)
|
||||
case rule: DoesLogVerdict => true
|
||||
case _ => false
|
||||
}
|
||||
.map { primaryRule =>
|
||||
val secondaryRulesAndVerdicts = secondaryActingRules zip secondaryVerdicts
|
||||
var actingRules: Seq[Rule] = Seq(primaryRule)
|
||||
var actingRuleNames: Seq[String] = Seq(primaryRule.name)
|
||||
var actionSources: Seq[ActionSource] = Seq()
|
||||
var healthActionTypes: Seq[HealthActionType] = Seq(verdict.toHealthActionTypeThrift.get)
|
||||
|
||||
val misinfoPolicyCategory: Option[FleetInterstitial] = {
|
||||
verdict match {
|
||||
case softIntervention: SoftIntervention =>
|
||||
softIntervention.fleetInterstitial
|
||||
case tweetInterstitial: TweetInterstitial =>
|
||||
tweetInterstitial.softIntervention.flatMap(_.fleetInterstitial)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
secondaryRulesAndVerdicts.foreach(ruleAndVerdict => {
|
||||
if (ruleAndVerdict._1.isInstanceOf[DoesLogVerdict]) {
|
||||
actingRules = actingRules :+ ruleAndVerdict._1
|
||||
actingRuleNames = actingRuleNames :+ ruleAndVerdict._1.name
|
||||
healthActionTypes = healthActionTypes :+ ruleAndVerdict._2.toHealthActionTypeThrift.get
|
||||
}
|
||||
})
|
||||
|
||||
actingRules.foreach(rule => {
|
||||
rule.actionSourceBuilder
|
||||
.flatMap(_.build(resolvedFeatureMap, verdict))
|
||||
.map(actionSource => {
|
||||
actionSources = actionSources :+ actionSource
|
||||
})
|
||||
})
|
||||
(actingRuleNames, actionSources, healthActionTypes, misinfoPolicyCategory)
|
||||
}
|
||||
.getOrElse((Seq.empty[String], Seq.empty[ActionSource], Seq.empty[HealthActionType], None))
|
||||
}
|
||||
|
||||
def scribeVerdict(
|
||||
visibilityResult: VisibilityResult,
|
||||
safetyLevel: SafetyLevel,
|
||||
vfLibType: VFLibType,
|
||||
viewerId: Option[Long] = None
|
||||
): Unit = {
|
||||
publisherOpt.foreach { publisher =>
|
||||
toEntityId(visibilityResult.contentId).foreach { entityId =>
|
||||
visibilityResult.verdict.toHealthActionTypeThrift.foreach { healthActionType =>
|
||||
val (actioningRules, actionSources, healthActionTypes, misinfoPolicyCategory) =
|
||||
getLogEntryData(
|
||||
actingRule = visibilityResult.actingRule,
|
||||
secondaryActingRules = visibilityResult.secondaryActingRules,
|
||||
verdict = visibilityResult.verdict,
|
||||
secondaryVerdicts = visibilityResult.secondaryVerdicts,
|
||||
resolvedFeatureMap = visibilityResult.resolvedFeatureMap
|
||||
)
|
||||
|
||||
if (actioningRules.nonEmpty) {
|
||||
log(
|
||||
VFVerdictLogEntry(
|
||||
entityId = entityId,
|
||||
viewerId = viewerId,
|
||||
timestampMsec = System.currentTimeMillis(),
|
||||
vfLibType = vfLibType,
|
||||
healthActionType = healthActionType,
|
||||
safetyLevel = safetyLevel,
|
||||
actioningRules = actioningRules,
|
||||
actionSources = actionSources,
|
||||
healthActionTypes = healthActionTypes,
|
||||
misinfoPolicyCategory =
|
||||
fleetInterstitialToMisinfoPolicyCategory(misinfoPolicyCategory)
|
||||
),
|
||||
publisher
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def fleetInterstitialToMisinfoPolicyCategory(
|
||||
fleetInterstitialOption: Option[FleetInterstitial]
|
||||
): Option[MisinfoPolicyCategory] = {
|
||||
fleetInterstitialOption.map {
|
||||
case FleetInterstitial.Generic =>
|
||||
MisinfoPolicyCategory.Generic
|
||||
case FleetInterstitial.Samm =>
|
||||
MisinfoPolicyCategory.Samm
|
||||
case FleetInterstitial.CivicIntegrity =>
|
||||
MisinfoPolicyCategory.CivicIntegrity
|
||||
case _ => MisinfoPolicyCategory.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
package com.twitter.visibility.builder
|
||||
|
||||
import com.twitter.spam.rtf.thriftscala.SafetyResult
|
||||
import com.twitter.visibility.common.actions.converter.scala.DropReasonConverter
|
||||
import com.twitter.visibility.rules.ComposableActions._
|
||||
import com.twitter.visibility.features.Feature
|
||||
import com.twitter.visibility.features.FeatureMap
|
||||
import com.twitter.visibility.models.ContentId
|
||||
import com.twitter.visibility.rules._
|
||||
import com.twitter.visibility.{thriftscala => t}
|
||||
|
||||
case class VisibilityResult(
|
||||
contentId: ContentId,
|
||||
featureMap: FeatureMap = FeatureMap.empty,
|
||||
ruleResultMap: Map[Rule, RuleResult] = Map.empty,
|
||||
verdict: Action = Allow,
|
||||
finished: Boolean = false,
|
||||
actingRule: Option[Rule] = None,
|
||||
secondaryActingRules: Seq[Rule] = Seq(),
|
||||
secondaryVerdicts: Seq[Action] = Seq(),
|
||||
resolvedFeatureMap: Map[Feature[_], Any] = Map.empty) {
|
||||
|
||||
def getSafetyResult: SafetyResult =
|
||||
verdict match {
|
||||
case InterstitialLimitedEngagements(reason: Reason, _, _, _)
|
||||
if PublicInterest.Reasons
|
||||
.contains(reason) =>
|
||||
SafetyResult(
|
||||
Some(PublicInterest.ReasonToSafetyResultReason(reason)),
|
||||
verdict.toActionThrift()
|
||||
)
|
||||
case ComposableActionsWithInterstitialLimitedEngagements(tweetInterstitial)
|
||||
if PublicInterest.Reasons.contains(tweetInterstitial.reason) =>
|
||||
SafetyResult(
|
||||
Some(PublicInterest.ReasonToSafetyResultReason(tweetInterstitial.reason)),
|
||||
verdict.toActionThrift()
|
||||
)
|
||||
case FreedomOfSpeechNotReachReason(appealableReason) =>
|
||||
SafetyResult(
|
||||
Some(FreedomOfSpeechNotReach.reasonToSafetyResultReason(appealableReason)),
|
||||
verdict.toActionThrift()
|
||||
)
|
||||
case _ => SafetyResult(None, verdict.toActionThrift())
|
||||
}
|
||||
|
||||
def getUserVisibilityResult: Option[t.UserVisibilityResult] =
|
||||
(verdict match {
|
||||
case Drop(reason, _) =>
|
||||
Some(
|
||||
t.UserAction.Drop(t.Drop(Reason.toDropReason(reason).map(DropReasonConverter.toThrift))))
|
||||
case _ => None
|
||||
}).map(userAction => t.UserVisibilityResult(Some(userAction)))
|
||||
}
|
||||
|
||||
object VisibilityResult {
|
||||
class Builder {
|
||||
var featureMap: FeatureMap = FeatureMap.empty
|
||||
var ruleResultMap: Map[Rule, RuleResult] = Map.empty
|
||||
var verdict: Action = Allow
|
||||
var finished: Boolean = false
|
||||
var actingRule: Option[Rule] = None
|
||||
var secondaryActingRules: Seq[Rule] = Seq()
|
||||
var secondaryVerdicts: Seq[Action] = Seq()
|
||||
var resolvedFeatureMap: Map[Feature[_], Any] = Map.empty
|
||||
|
||||
def withFeatureMap(featureMapBld: FeatureMap) = {
|
||||
featureMap = featureMapBld
|
||||
this
|
||||
}
|
||||
|
||||
def withRuleResultMap(ruleResultMapBld: Map[Rule, RuleResult]) = {
|
||||
ruleResultMap = ruleResultMapBld
|
||||
this
|
||||
}
|
||||
|
||||
def withVerdict(verdictBld: Action) = {
|
||||
verdict = verdictBld
|
||||
this
|
||||
}
|
||||
|
||||
def withFinished(finishedBld: Boolean) = {
|
||||
finished = finishedBld
|
||||
this
|
||||
}
|
||||
|
||||
def withActingRule(actingRuleBld: Option[Rule]) = {
|
||||
actingRule = actingRuleBld
|
||||
this
|
||||
}
|
||||
|
||||
def withSecondaryActingRules(secondaryActingRulesBld: Seq[Rule]) = {
|
||||
secondaryActingRules = secondaryActingRulesBld
|
||||
this
|
||||
}
|
||||
|
||||
def withSecondaryVerdicts(secondaryVerdictsBld: Seq[Action]) = {
|
||||
secondaryVerdicts = secondaryVerdictsBld
|
||||
this
|
||||
}
|
||||
|
||||
def build(contentId: ContentId) = VisibilityResult(
|
||||
contentId,
|
||||
featureMap,
|
||||
ruleResultMap,
|
||||
verdict,
|
||||
finished,
|
||||
actingRule,
|
||||
secondaryActingRules,
|
||||
secondaryVerdicts,
|
||||
resolvedFeatureMap)
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
package com.twitter.visibility.builder
|
||||
|
||||
import com.twitter.visibility.features.Feature
|
||||
import com.twitter.visibility.features.FeatureMap
|
||||
import com.twitter.visibility.models.ContentId
|
||||
import com.twitter.visibility.rules.Action
|
||||
import com.twitter.visibility.rules.Allow
|
||||
import com.twitter.visibility.rules.EvaluationContext
|
||||
import com.twitter.visibility.rules.FailClosedException
|
||||
import com.twitter.visibility.rules.FeaturesFailedException
|
||||
import com.twitter.visibility.rules.MissingFeaturesException
|
||||
import com.twitter.visibility.rules.Rule
|
||||
import com.twitter.visibility.rules.RuleFailedException
|
||||
import com.twitter.visibility.rules.RuleResult
|
||||
import com.twitter.visibility.rules.State.FeatureFailed
|
||||
import com.twitter.visibility.rules.State.MissingFeature
|
||||
import com.twitter.visibility.rules.State.RuleFailed
|
||||
|
||||
class VisibilityResultBuilder(
|
||||
val contentId: ContentId,
|
||||
val featureMap: FeatureMap = FeatureMap.empty,
|
||||
private var ruleResultMap: Map[Rule, RuleResult] = Map.empty) {
|
||||
private var mapBuilder = Map.newBuilder[Rule, RuleResult]
|
||||
mapBuilder ++= ruleResultMap
|
||||
var verdict: Action = Allow
|
||||
var finished: Boolean = false
|
||||
var features: FeatureMap = featureMap
|
||||
var actingRule: Option[Rule] = None
|
||||
var secondaryVerdicts: Seq[Action] = Seq()
|
||||
var secondaryActingRules: Seq[Rule] = Seq()
|
||||
var resolvedFeatureMap: Map[Feature[_], Any] = Map.empty
|
||||
|
||||
def ruleResults: Map[Rule, RuleResult] = mapBuilder.result()
|
||||
|
||||
def withFeatureMap(featureMap: FeatureMap): VisibilityResultBuilder = {
|
||||
this.features = featureMap
|
||||
this
|
||||
}
|
||||
|
||||
def withRuleResultMap(ruleResultMap: Map[Rule, RuleResult]): VisibilityResultBuilder = {
|
||||
this.ruleResultMap = ruleResultMap
|
||||
mapBuilder = Map.newBuilder[Rule, RuleResult]
|
||||
mapBuilder ++= ruleResultMap
|
||||
this
|
||||
}
|
||||
|
||||
def withRuleResult(rule: Rule, result: RuleResult): VisibilityResultBuilder = {
|
||||
mapBuilder += ((rule, result))
|
||||
this
|
||||
}
|
||||
|
||||
def withVerdict(verdict: Action, ruleOpt: Option[Rule] = None): VisibilityResultBuilder = {
|
||||
this.verdict = verdict
|
||||
this.actingRule = ruleOpt
|
||||
this
|
||||
}
|
||||
|
||||
def withSecondaryVerdict(verdict: Action, rule: Rule): VisibilityResultBuilder = {
|
||||
this.secondaryVerdicts = this.secondaryVerdicts :+ verdict
|
||||
this.secondaryActingRules = this.secondaryActingRules :+ rule
|
||||
this
|
||||
}
|
||||
|
||||
def withFinished(finished: Boolean): VisibilityResultBuilder = {
|
||||
this.finished = finished
|
||||
this
|
||||
}
|
||||
|
||||
def withResolvedFeatureMap(
|
||||
resolvedFeatureMap: Map[Feature[_], Any]
|
||||
): VisibilityResultBuilder = {
|
||||
this.resolvedFeatureMap = resolvedFeatureMap
|
||||
this
|
||||
}
|
||||
|
||||
def isVerdictComposable(): Boolean = this.verdict.isComposable
|
||||
|
||||
def failClosedException(evaluationContext: EvaluationContext): Option[FailClosedException] = {
|
||||
mapBuilder
|
||||
.result().collect {
|
||||
case (r: Rule, RuleResult(_, MissingFeature(mf)))
|
||||
if r.shouldFailClosed(evaluationContext.params) =>
|
||||
Some(MissingFeaturesException(r.name, mf))
|
||||
case (r: Rule, RuleResult(_, FeatureFailed(ff)))
|
||||
if r.shouldFailClosed(evaluationContext.params) =>
|
||||
Some(FeaturesFailedException(r.name, ff))
|
||||
case (r: Rule, RuleResult(_, RuleFailed(t)))
|
||||
if r.shouldFailClosed(evaluationContext.params) =>
|
||||
Some(RuleFailedException(r.name, t))
|
||||
}.toList.foldLeft(None: Option[FailClosedException]) { (acc, arg) =>
|
||||
(acc, arg) match {
|
||||
case (None, Some(_)) => arg
|
||||
case (Some(FeaturesFailedException(_, _)), Some(MissingFeaturesException(_, _))) => arg
|
||||
case (Some(RuleFailedException(_, _)), Some(MissingFeaturesException(_, _))) => arg
|
||||
case (Some(RuleFailedException(_, _)), Some(FeaturesFailedException(_, _))) => arg
|
||||
case _ => acc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def build: VisibilityResult = {
|
||||
VisibilityResult(
|
||||
contentId = contentId,
|
||||
featureMap = features,
|
||||
ruleResultMap = mapBuilder.result(),
|
||||
verdict = verdict,
|
||||
finished = finished,
|
||||
actingRule = actingRule,
|
||||
secondaryActingRules = secondaryActingRules,
|
||||
secondaryVerdicts = secondaryVerdicts,
|
||||
resolvedFeatureMap = resolvedFeatureMap
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/com/google/guava",
|
||||
"communities/thrift/src/main/thrift/com/twitter/communities:thrift-scala",
|
||||
"communities/thrift/src/main/thrift/com/twitter/communities/moderation:thrift-scala",
|
||||
"escherbird/src/thrift/com/twitter/escherbird/softintervention:softintervention_thrift-scala",
|
||||
"snowflake/src/main/scala/com/twitter/snowflake/id",
|
||||
"src/thrift/com/twitter/context:twitter-context-scala",
|
||||
"src/thrift/com/twitter/escherbird/common:common-scala",
|
||||
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
|
||||
"src/thrift/com/twitter/search/common:constants-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:tweet-rtf-event-scala",
|
||||
"src/thrift/com/twitter/tweetypie:tweet-scala",
|
||||
"stitch/stitch-core",
|
||||
"tweetypie/src/scala/com/twitter/tweetypie/additionalfields",
|
||||
"twitter-context/src/main/scala",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common/stitch",
|
||||
"visibility/lib/src/main/resources/config",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/features",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/blender",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/tweets",
|
||||
],
|
||||
)
|
|
@ -1,228 +0,0 @@
|
|||
package com.twitter.visibility.builder.common
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.gizmoduck.thriftscala.MuteOption
|
||||
import com.twitter.gizmoduck.thriftscala.MuteSurface
|
||||
import com.twitter.gizmoduck.thriftscala.{MutedKeyword => GdMutedKeyword}
|
||||
import com.twitter.servo.util.Gate
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common._
|
||||
import com.twitter.visibility.features._
|
||||
import com.twitter.visibility.models.{MutedKeyword => VfMutedKeyword}
|
||||
import java.util.Locale
|
||||
|
||||
class MutedKeywordFeatures(
|
||||
userSource: UserSource,
|
||||
userRelationshipSource: UserRelationshipSource,
|
||||
keywordMatcher: KeywordMatcher.Matcher = KeywordMatcher.TestMatcher,
|
||||
statsReceiver: StatsReceiver,
|
||||
enableFollowCheckInMutedKeyword: Gate[Unit] = Gate.False) {
|
||||
|
||||
private[this] val scopedStatsReceiver: StatsReceiver =
|
||||
statsReceiver.scope("muted_keyword_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val viewerMutesKeywordInTweetForHomeTimeline =
|
||||
scopedStatsReceiver.scope(ViewerMutesKeywordInTweetForHomeTimeline.name).counter("requests")
|
||||
private[this] val viewerMutesKeywordInTweetForTweetReplies =
|
||||
scopedStatsReceiver.scope(ViewerMutesKeywordInTweetForTweetReplies.name).counter("requests")
|
||||
private[this] val viewerMutesKeywordInTweetForNotifications =
|
||||
scopedStatsReceiver.scope(ViewerMutesKeywordInTweetForNotifications.name).counter("requests")
|
||||
private[this] val excludeFollowingForMutedKeywordsRequests =
|
||||
scopedStatsReceiver.scope("exclude_following").counter("requests")
|
||||
private[this] val viewerMutesKeywordInTweetForAllSurfaces =
|
||||
scopedStatsReceiver.scope(ViewerMutesKeywordInTweetForAllSurfaces.name).counter("requests")
|
||||
|
||||
def forTweet(
|
||||
tweet: Tweet,
|
||||
viewerId: Option[Long],
|
||||
authorId: Long
|
||||
): FeatureMapBuilder => FeatureMapBuilder = { featureMapBuilder =>
|
||||
requests.incr()
|
||||
viewerMutesKeywordInTweetForHomeTimeline.incr()
|
||||
viewerMutesKeywordInTweetForTweetReplies.incr()
|
||||
viewerMutesKeywordInTweetForNotifications.incr()
|
||||
viewerMutesKeywordInTweetForAllSurfaces.incr()
|
||||
|
||||
val keywordsBySurface = allMutedKeywords(viewerId)
|
||||
|
||||
val keywordsWithoutDefinedSurface = allMutedKeywordsWithoutDefinedSurface(viewerId)
|
||||
|
||||
featureMapBuilder
|
||||
.withFeature(
|
||||
ViewerMutesKeywordInTweetForHomeTimeline,
|
||||
tweetContainsMutedKeyword(
|
||||
tweet,
|
||||
keywordsBySurface,
|
||||
MuteSurface.HomeTimeline,
|
||||
viewerId,
|
||||
authorId
|
||||
)
|
||||
)
|
||||
.withFeature(
|
||||
ViewerMutesKeywordInTweetForTweetReplies,
|
||||
tweetContainsMutedKeyword(
|
||||
tweet,
|
||||
keywordsBySurface,
|
||||
MuteSurface.TweetReplies,
|
||||
viewerId,
|
||||
authorId
|
||||
)
|
||||
)
|
||||
.withFeature(
|
||||
ViewerMutesKeywordInTweetForNotifications,
|
||||
tweetContainsMutedKeyword(
|
||||
tweet,
|
||||
keywordsBySurface,
|
||||
MuteSurface.Notifications,
|
||||
viewerId,
|
||||
authorId
|
||||
)
|
||||
)
|
||||
.withFeature(
|
||||
ViewerMutesKeywordInTweetForAllSurfaces,
|
||||
tweetContainsMutedKeywordWithoutDefinedSurface(
|
||||
tweet,
|
||||
keywordsWithoutDefinedSurface,
|
||||
viewerId,
|
||||
authorId
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def allMutedKeywords(viewerId: Option[Long]): Stitch[Map[MuteSurface, Seq[GdMutedKeyword]]] =
|
||||
viewerId
|
||||
.map { id => userSource.getAllMutedKeywords(id) }.getOrElse(Stitch.value(Map.empty))
|
||||
|
||||
def allMutedKeywordsWithoutDefinedSurface(viewerId: Option[Long]): Stitch[Seq[GdMutedKeyword]] =
|
||||
viewerId
|
||||
.map { id => userSource.getAllMutedKeywordsWithoutDefinedSurface(id) }.getOrElse(
|
||||
Stitch.value(Seq.empty))
|
||||
|
||||
private def mutingKeywordsText(
|
||||
mutedKeywords: Seq[GdMutedKeyword],
|
||||
muteSurface: MuteSurface,
|
||||
viewerIdOpt: Option[Long],
|
||||
authorId: Long
|
||||
): Stitch[Option[String]] = {
|
||||
if (muteSurface == MuteSurface.HomeTimeline && mutedKeywords.nonEmpty) {
|
||||
Stitch.value(Some(mutedKeywords.map(_.keyword).mkString(",")))
|
||||
} else {
|
||||
mutedKeywords.partition(kw =>
|
||||
kw.muteOptions.contains(MuteOption.ExcludeFollowingAccounts)) match {
|
||||
case (_, mutedKeywordsFromAnyone) if mutedKeywordsFromAnyone.nonEmpty =>
|
||||
Stitch.value(Some(mutedKeywordsFromAnyone.map(_.keyword).mkString(",")))
|
||||
case (mutedKeywordsExcludeFollowing, _)
|
||||
if mutedKeywordsExcludeFollowing.nonEmpty && enableFollowCheckInMutedKeyword() =>
|
||||
excludeFollowingForMutedKeywordsRequests.incr()
|
||||
viewerIdOpt match {
|
||||
case Some(viewerId) =>
|
||||
userRelationshipSource.follows(viewerId, authorId).map {
|
||||
case true =>
|
||||
case false => Some(mutedKeywordsExcludeFollowing.map(_.keyword).mkString(","))
|
||||
}
|
||||
case _ => Stitch.None
|
||||
}
|
||||
case (_, _) => Stitch.None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def mutingKeywordsTextWithoutDefinedSurface(
|
||||
mutedKeywords: Seq[GdMutedKeyword],
|
||||
viewerIdOpt: Option[Long],
|
||||
authorId: Long
|
||||
): Stitch[Option[String]] = {
|
||||
mutedKeywords.partition(kw =>
|
||||
kw.muteOptions.contains(MuteOption.ExcludeFollowingAccounts)) match {
|
||||
case (_, mutedKeywordsFromAnyone) if mutedKeywordsFromAnyone.nonEmpty =>
|
||||
Stitch.value(Some(mutedKeywordsFromAnyone.map(_.keyword).mkString(",")))
|
||||
case (mutedKeywordsExcludeFollowing, _)
|
||||
if mutedKeywordsExcludeFollowing.nonEmpty && enableFollowCheckInMutedKeyword() =>
|
||||
excludeFollowingForMutedKeywordsRequests.incr()
|
||||
viewerIdOpt match {
|
||||
case Some(viewerId) =>
|
||||
userRelationshipSource.follows(viewerId, authorId).map {
|
||||
case true =>
|
||||
case false => Some(mutedKeywordsExcludeFollowing.map(_.keyword).mkString(","))
|
||||
}
|
||||
case _ => Stitch.None
|
||||
}
|
||||
case (_, _) => Stitch.None
|
||||
}
|
||||
}
|
||||
|
||||
def tweetContainsMutedKeyword(
|
||||
tweet: Tweet,
|
||||
mutedKeywordMap: Stitch[Map[MuteSurface, Seq[GdMutedKeyword]]],
|
||||
muteSurface: MuteSurface,
|
||||
viewerIdOpt: Option[Long],
|
||||
authorId: Long
|
||||
): Stitch[VfMutedKeyword] = {
|
||||
mutedKeywordMap.flatMap { keywordMap =>
|
||||
if (keywordMap.isEmpty) {
|
||||
Stitch.value(VfMutedKeyword(None))
|
||||
} else {
|
||||
val mutedKeywords = keywordMap.getOrElse(muteSurface, Nil)
|
||||
val matchTweetFn: KeywordMatcher.MatchTweet = keywordMatcher(mutedKeywords)
|
||||
val locale = tweet.language.map(l => Locale.forLanguageTag(l.language))
|
||||
val text = tweet.coreData.get.text
|
||||
|
||||
matchTweetFn(locale, text).flatMap { results =>
|
||||
mutingKeywordsText(results, muteSurface, viewerIdOpt, authorId).map(VfMutedKeyword)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def tweetContainsMutedKeywordWithoutDefinedSurface(
|
||||
tweet: Tweet,
|
||||
mutedKeywordSeq: Stitch[Seq[GdMutedKeyword]],
|
||||
viewerIdOpt: Option[Long],
|
||||
authorId: Long
|
||||
): Stitch[VfMutedKeyword] = {
|
||||
mutedKeywordSeq.flatMap { mutedKeyword =>
|
||||
if (mutedKeyword.isEmpty) {
|
||||
Stitch.value(VfMutedKeyword(None))
|
||||
} else {
|
||||
val matchTweetFn: KeywordMatcher.MatchTweet = keywordMatcher(mutedKeyword)
|
||||
val locale = tweet.language.map(l => Locale.forLanguageTag(l.language))
|
||||
val text = tweet.coreData.get.text
|
||||
|
||||
matchTweetFn(locale, text).flatMap { results =>
|
||||
mutingKeywordsTextWithoutDefinedSurface(results, viewerIdOpt, authorId).map(
|
||||
VfMutedKeyword
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
def spaceTitleContainsMutedKeyword(
|
||||
spaceTitle: String,
|
||||
spaceLanguageOpt: Option[String],
|
||||
mutedKeywordMap: Stitch[Map[MuteSurface, Seq[GdMutedKeyword]]],
|
||||
muteSurface: MuteSurface,
|
||||
): Stitch[VfMutedKeyword] = {
|
||||
mutedKeywordMap.flatMap { keywordMap =>
|
||||
if (keywordMap.isEmpty) {
|
||||
Stitch.value(VfMutedKeyword(None))
|
||||
} else {
|
||||
val mutedKeywords = keywordMap.getOrElse(muteSurface, Nil)
|
||||
val matchTweetFn: KeywordMatcher.MatchTweet = keywordMatcher(mutedKeywords)
|
||||
|
||||
val locale = spaceLanguageOpt.map(l => Locale.forLanguageTag(l))
|
||||
matchTweetFn(locale, spaceTitle).flatMap { results =>
|
||||
if (results.nonEmpty) {
|
||||
Stitch.value(Some(results.map(_.keyword).mkString(","))).map(VfMutedKeyword)
|
||||
} else {
|
||||
Stitch.None.map(VfMutedKeyword)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"src/thrift/com/twitter/convosvc:convosvc-scala",
|
||||
"src/thrift/com/twitter/convosvc/internal:internal-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
|
||||
"stitch/stitch-core",
|
||||
"stitch/stitch-core/src/main/scala/com/twitter/stitch",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common/dm_sources",
|
||||
"visibility/lib/src/main/resources/config",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/features",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/util",
|
||||
"visibility/lib/src/main/thrift/com/twitter/visibility/safety_label_store:safety-label-store-scala",
|
||||
],
|
||||
)
|
|
@ -1,196 +0,0 @@
|
|||
package com.twitter.visibility.builder.dms
|
||||
|
||||
import com.twitter.convosvc.thriftscala.ConversationQuery
|
||||
import com.twitter.convosvc.thriftscala.ConversationQueryOptions
|
||||
import com.twitter.convosvc.thriftscala.ConversationType
|
||||
import com.twitter.convosvc.thriftscala.TimelineLookupState
|
||||
import com.twitter.stitch.NotFound
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.builder.users.AuthorFeatures
|
||||
import com.twitter.visibility.common.DmConversationId
|
||||
import com.twitter.visibility.common.UserId
|
||||
import com.twitter.visibility.common.dm_sources.DmConversationSource
|
||||
import com.twitter.visibility.features._
|
||||
|
||||
case class InvalidDmConversationFeatureException(message: String) extends Exception(message)
|
||||
|
||||
class DmConversationFeatures(
|
||||
dmConversationSource: DmConversationSource,
|
||||
authorFeatures: AuthorFeatures) {
|
||||
|
||||
def forDmConversationId(
|
||||
dmConversationId: DmConversationId,
|
||||
viewerIdOpt: Option[UserId]
|
||||
): FeatureMapBuilder => FeatureMapBuilder =
|
||||
_.withFeature(
|
||||
DmConversationIsOneToOneConversation,
|
||||
dmConversationIsOneToOneConversation(dmConversationId, viewerIdOpt))
|
||||
.withFeature(
|
||||
DmConversationHasEmptyTimeline,
|
||||
dmConversationHasEmptyTimeline(dmConversationId, viewerIdOpt))
|
||||
.withFeature(
|
||||
DmConversationHasValidLastReadableEventId,
|
||||
dmConversationHasValidLastReadableEventId(dmConversationId, viewerIdOpt))
|
||||
.withFeature(
|
||||
DmConversationInfoExists,
|
||||
dmConversationInfoExists(dmConversationId, viewerIdOpt))
|
||||
.withFeature(
|
||||
DmConversationTimelineExists,
|
||||
dmConversationTimelineExists(dmConversationId, viewerIdOpt))
|
||||
.withFeature(
|
||||
AuthorIsSuspended,
|
||||
dmConversationHasSuspendedParticipant(dmConversationId, viewerIdOpt))
|
||||
.withFeature(
|
||||
AuthorIsDeactivated,
|
||||
dmConversationHasDeactivatedParticipant(dmConversationId, viewerIdOpt))
|
||||
.withFeature(
|
||||
AuthorIsErased,
|
||||
dmConversationHasErasedParticipant(dmConversationId, viewerIdOpt))
|
||||
.withFeature(
|
||||
ViewerIsDmConversationParticipant,
|
||||
viewerIsDmConversationParticipant(dmConversationId, viewerIdOpt))
|
||||
|
||||
def dmConversationIsOneToOneConversation(
|
||||
dmConversationId: DmConversationId,
|
||||
viewerIdOpt: Option[UserId]
|
||||
): Stitch[Boolean] =
|
||||
viewerIdOpt match {
|
||||
case Some(viewerId) =>
|
||||
dmConversationSource.getConversationType(dmConversationId, viewerId).flatMap {
|
||||
case Some(ConversationType.OneToOneDm | ConversationType.SecretOneToOneDm) =>
|
||||
Stitch.True
|
||||
case None =>
|
||||
Stitch.exception(InvalidDmConversationFeatureException("Conversation type not found"))
|
||||
case _ => Stitch.False
|
||||
}
|
||||
case _ => Stitch.exception(InvalidDmConversationFeatureException("Viewer id missing"))
|
||||
}
|
||||
|
||||
private[dms] def dmConversationHasEmptyTimeline(
|
||||
dmConversationId: DmConversationId,
|
||||
viewerIdOpt: Option[UserId]
|
||||
): Stitch[Boolean] =
|
||||
dmConversationSource
|
||||
.getConversationTimelineEntries(
|
||||
dmConversationId,
|
||||
ConversationQuery(
|
||||
conversationId = Some(dmConversationId),
|
||||
options = Some(
|
||||
ConversationQueryOptions(
|
||||
perspectivalUserId = viewerIdOpt,
|
||||
hydrateEvents = Some(false),
|
||||
supportsReactions = Some(true)
|
||||
)
|
||||
),
|
||||
maxCount = 10
|
||||
)
|
||||
).map(_.forall(entries => entries.isEmpty))
|
||||
|
||||
private[dms] def dmConversationHasValidLastReadableEventId(
|
||||
dmConversationId: DmConversationId,
|
||||
viewerIdOpt: Option[UserId]
|
||||
): Stitch[Boolean] =
|
||||
viewerIdOpt match {
|
||||
case Some(viewerId) =>
|
||||
dmConversationSource
|
||||
.getConversationLastReadableEventId(dmConversationId, viewerId).map(_.exists(id =>
|
||||
id > 0L))
|
||||
case _ => Stitch.exception(InvalidDmConversationFeatureException("Viewer id missing"))
|
||||
}
|
||||
|
||||
private[dms] def dmConversationInfoExists(
|
||||
dmConversationId: DmConversationId,
|
||||
viewerIdOpt: Option[UserId]
|
||||
): Stitch[Boolean] =
|
||||
viewerIdOpt match {
|
||||
case Some(viewerId) =>
|
||||
dmConversationSource
|
||||
.getDmConversationInfo(dmConversationId, viewerId).map(_.isDefined)
|
||||
case _ => Stitch.exception(InvalidDmConversationFeatureException("Viewer id missing"))
|
||||
}
|
||||
|
||||
private[dms] def dmConversationTimelineExists(
|
||||
dmConversationId: DmConversationId,
|
||||
viewerIdOpt: Option[UserId]
|
||||
): Stitch[Boolean] =
|
||||
dmConversationSource
|
||||
.getConversationTimelineState(
|
||||
dmConversationId,
|
||||
ConversationQuery(
|
||||
conversationId = Some(dmConversationId),
|
||||
options = Some(
|
||||
ConversationQueryOptions(
|
||||
perspectivalUserId = viewerIdOpt,
|
||||
hydrateEvents = Some(false),
|
||||
supportsReactions = Some(true)
|
||||
)
|
||||
),
|
||||
maxCount = 1
|
||||
)
|
||||
).map {
|
||||
case Some(TimelineLookupState.NotFound) | None => false
|
||||
case _ => true
|
||||
}
|
||||
|
||||
private[dms] def anyConversationParticipantMatchesCondition(
|
||||
condition: UserId => Stitch[Boolean],
|
||||
dmConversationId: DmConversationId,
|
||||
viewerIdOpt: Option[UserId]
|
||||
): Stitch[Boolean] =
|
||||
viewerIdOpt match {
|
||||
case Some(viewerId) =>
|
||||
dmConversationSource
|
||||
.getConversationParticipantIds(dmConversationId, viewerId).flatMap {
|
||||
case Some(participants) =>
|
||||
Stitch
|
||||
.collect(participants.map(condition)).map(_.contains(true)).rescue {
|
||||
case NotFound =>
|
||||
Stitch.exception(InvalidDmConversationFeatureException("User not found"))
|
||||
}
|
||||
case _ => Stitch.False
|
||||
}
|
||||
case _ => Stitch.exception(InvalidDmConversationFeatureException("Viewer id missing"))
|
||||
}
|
||||
|
||||
def dmConversationHasSuspendedParticipant(
|
||||
dmConversationId: DmConversationId,
|
||||
viewerIdOpt: Option[UserId]
|
||||
): Stitch[Boolean] =
|
||||
anyConversationParticipantMatchesCondition(
|
||||
participant => authorFeatures.authorIsSuspended(participant),
|
||||
dmConversationId,
|
||||
viewerIdOpt)
|
||||
|
||||
def dmConversationHasDeactivatedParticipant(
|
||||
dmConversationId: DmConversationId,
|
||||
viewerIdOpt: Option[UserId]
|
||||
): Stitch[Boolean] =
|
||||
anyConversationParticipantMatchesCondition(
|
||||
participant => authorFeatures.authorIsDeactivated(participant),
|
||||
dmConversationId,
|
||||
viewerIdOpt)
|
||||
|
||||
def dmConversationHasErasedParticipant(
|
||||
dmConversationId: DmConversationId,
|
||||
viewerIdOpt: Option[UserId]
|
||||
): Stitch[Boolean] =
|
||||
anyConversationParticipantMatchesCondition(
|
||||
participant => authorFeatures.authorIsErased(participant),
|
||||
dmConversationId,
|
||||
viewerIdOpt)
|
||||
|
||||
def viewerIsDmConversationParticipant(
|
||||
dmConversationId: DmConversationId,
|
||||
viewerIdOpt: Option[UserId]
|
||||
): Stitch[Boolean] =
|
||||
viewerIdOpt match {
|
||||
case Some(viewerId) =>
|
||||
dmConversationSource
|
||||
.getConversationParticipantIds(dmConversationId, viewerId).map {
|
||||
case Some(participants) => participants.contains(viewerId)
|
||||
case _ => false
|
||||
}
|
||||
case _ => Stitch.exception(InvalidDmConversationFeatureException("Viewer id missing"))
|
||||
}
|
||||
}
|
|
@ -1,341 +0,0 @@
|
|||
package com.twitter.visibility.builder.dms
|
||||
|
||||
import com.twitter.convosvc.thriftscala.Event
|
||||
import com.twitter.convosvc.thriftscala.StoredDelete
|
||||
import com.twitter.convosvc.thriftscala.StoredPerspectivalMessageInfo
|
||||
import com.twitter.convosvc.thriftscala.PerspectivalSpamState
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.builder.users.AuthorFeatures
|
||||
import com.twitter.visibility.common.DmEventId
|
||||
import com.twitter.visibility.common.dm_sources.DmEventSource
|
||||
import com.twitter.visibility.common.UserId
|
||||
import com.twitter.convosvc.thriftscala.EventType
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.stitch.NotFound
|
||||
import com.twitter.visibility.common.dm_sources.DmConversationSource
|
||||
import com.twitter.visibility.features._
|
||||
|
||||
case class InvalidDmEventFeatureException(message: String) extends Exception(message)
|
||||
|
||||
class DmEventFeatures(
|
||||
dmEventSource: DmEventSource,
|
||||
dmConversationSource: DmConversationSource,
|
||||
authorFeatures: AuthorFeatures,
|
||||
dmConversationFeatures: DmConversationFeatures,
|
||||
statsReceiver: StatsReceiver) {
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("dm_event_features")
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
def forDmEventId(
|
||||
dmEventId: DmEventId,
|
||||
viewerId: UserId
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
|
||||
val dmEventStitchRef: Stitch[Option[Event]] =
|
||||
Stitch.ref(dmEventSource.getDmEvent(dmEventId, viewerId))
|
||||
|
||||
_.withFeature(
|
||||
DmEventIsMessageCreateEvent,
|
||||
isDmEventType(dmEventStitchRef, EventType.MessageCreate))
|
||||
.withFeature(
|
||||
AuthorIsSuspended,
|
||||
messageCreateEventHasInactiveInitiatingUser(
|
||||
dmEventStitchRef,
|
||||
initiatingUser => authorFeatures.authorIsSuspended(initiatingUser))
|
||||
)
|
||||
.withFeature(
|
||||
AuthorIsDeactivated,
|
||||
messageCreateEventHasInactiveInitiatingUser(
|
||||
dmEventStitchRef,
|
||||
initiatingUser => authorFeatures.authorIsDeactivated(initiatingUser))
|
||||
)
|
||||
.withFeature(
|
||||
AuthorIsErased,
|
||||
messageCreateEventHasInactiveInitiatingUser(
|
||||
dmEventStitchRef,
|
||||
initiatingUser => authorFeatures.authorIsErased(initiatingUser))
|
||||
)
|
||||
.withFeature(
|
||||
DmEventOccurredBeforeLastClearedEvent,
|
||||
dmEventOccurredBeforeLastClearedEvent(dmEventStitchRef, dmEventId, viewerId)
|
||||
)
|
||||
.withFeature(
|
||||
DmEventOccurredBeforeJoinConversationEvent,
|
||||
dmEventOccurredBeforeJoinConversationEvent(dmEventStitchRef, dmEventId, viewerId)
|
||||
)
|
||||
.withFeature(
|
||||
ViewerIsDmConversationParticipant,
|
||||
dmEventViewerIsDmConversationParticipant(dmEventStitchRef, viewerId)
|
||||
)
|
||||
.withFeature(
|
||||
DmEventIsDeleted,
|
||||
dmEventIsDeleted(dmEventStitchRef, dmEventId)
|
||||
)
|
||||
.withFeature(
|
||||
DmEventIsHidden,
|
||||
dmEventIsHidden(dmEventStitchRef, dmEventId)
|
||||
)
|
||||
.withFeature(
|
||||
ViewerIsDmEventInitiatingUser,
|
||||
viewerIsDmEventInitiatingUser(dmEventStitchRef, viewerId)
|
||||
)
|
||||
.withFeature(
|
||||
DmEventInOneToOneConversationWithUnavailableUser,
|
||||
dmEventInOneToOneConversationWithUnavailableUser(dmEventStitchRef, viewerId)
|
||||
)
|
||||
.withFeature(
|
||||
DmEventIsLastMessageReadUpdateEvent,
|
||||
isDmEventType(dmEventStitchRef, EventType.LastMessageReadUpdate)
|
||||
)
|
||||
.withFeature(
|
||||
DmEventIsJoinConversationEvent,
|
||||
isDmEventType(dmEventStitchRef, EventType.JoinConversation)
|
||||
)
|
||||
.withFeature(
|
||||
DmEventIsWelcomeMessageCreateEvent,
|
||||
isDmEventType(dmEventStitchRef, EventType.WelcomeMessageCreate)
|
||||
)
|
||||
.withFeature(
|
||||
DmEventIsTrustConversationEvent,
|
||||
isDmEventType(dmEventStitchRef, EventType.TrustConversation)
|
||||
)
|
||||
.withFeature(
|
||||
DmEventIsCsFeedbackSubmitted,
|
||||
isDmEventType(dmEventStitchRef, EventType.CsFeedbackSubmitted)
|
||||
)
|
||||
.withFeature(
|
||||
DmEventIsCsFeedbackDismissed,
|
||||
isDmEventType(dmEventStitchRef, EventType.CsFeedbackDismissed)
|
||||
)
|
||||
.withFeature(
|
||||
DmEventIsConversationCreateEvent,
|
||||
isDmEventType(dmEventStitchRef, EventType.ConversationCreate)
|
||||
)
|
||||
.withFeature(
|
||||
DmEventInOneToOneConversation,
|
||||
dmEventInOneToOneConversation(dmEventStitchRef, viewerId)
|
||||
)
|
||||
.withFeature(
|
||||
DmEventIsPerspectivalJoinConversationEvent,
|
||||
dmEventIsPerspectivalJoinConversationEvent(dmEventStitchRef, dmEventId, viewerId))
|
||||
|
||||
}
|
||||
|
||||
private def isDmEventType(
|
||||
dmEventOptStitch: Stitch[Option[Event]],
|
||||
eventType: EventType
|
||||
): Stitch[Boolean] =
|
||||
dmEventSource.getEventType(dmEventOptStitch).flatMap {
|
||||
case Some(_: eventType.type) =>
|
||||
Stitch.True
|
||||
case None =>
|
||||
Stitch.exception(InvalidDmEventFeatureException(s"$eventType event type not found"))
|
||||
case _ =>
|
||||
Stitch.False
|
||||
}
|
||||
|
||||
private def dmEventIsPerspectivalJoinConversationEvent(
|
||||
dmEventOptStitch: Stitch[Option[Event]],
|
||||
dmEventId: DmEventId,
|
||||
viewerId: UserId
|
||||
): Stitch[Boolean] =
|
||||
Stitch
|
||||
.join(
|
||||
dmEventSource.getEventType(dmEventOptStitch),
|
||||
dmEventSource.getConversationId(dmEventOptStitch)).flatMap {
|
||||
case (Some(EventType.JoinConversation), conversationIdOpt) =>
|
||||
conversationIdOpt match {
|
||||
case Some(conversationId) =>
|
||||
dmConversationSource
|
||||
.getParticipantJoinConversationEventId(conversationId, viewerId, viewerId)
|
||||
.flatMap {
|
||||
case Some(joinConversationEventId) =>
|
||||
Stitch.value(joinConversationEventId == dmEventId)
|
||||
case _ => Stitch.False
|
||||
}
|
||||
case _ =>
|
||||
Stitch.exception(InvalidDmEventFeatureException("Conversation id not found"))
|
||||
}
|
||||
case (None, _) =>
|
||||
Stitch.exception(InvalidDmEventFeatureException("Event type not found"))
|
||||
case _ => Stitch.False
|
||||
}
|
||||
|
||||
private def messageCreateEventHasInactiveInitiatingUser(
|
||||
dmEventOptStitch: Stitch[Option[Event]],
|
||||
condition: UserId => Stitch[Boolean],
|
||||
): Stitch[Boolean] =
|
||||
Stitch
|
||||
.join(
|
||||
dmEventSource.getEventType(dmEventOptStitch),
|
||||
dmEventSource.getInitiatingUserId(dmEventOptStitch)).flatMap {
|
||||
case (Some(EventType.MessageCreate), Some(userId)) =>
|
||||
condition(userId).rescue {
|
||||
case NotFound =>
|
||||
Stitch.exception(InvalidDmEventFeatureException("initiating user not found"))
|
||||
}
|
||||
case (None, _) =>
|
||||
Stitch.exception(InvalidDmEventFeatureException("DmEvent type is missing"))
|
||||
case (Some(EventType.MessageCreate), _) =>
|
||||
Stitch.exception(InvalidDmEventFeatureException("initiating user id is missing"))
|
||||
case _ => Stitch.False
|
||||
}
|
||||
|
||||
private def dmEventOccurredBeforeLastClearedEvent(
|
||||
dmEventOptStitch: Stitch[Option[Event]],
|
||||
dmEventId: DmEventId,
|
||||
viewerId: UserId
|
||||
): Stitch[Boolean] = {
|
||||
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
|
||||
case Some(convoId) =>
|
||||
val lastClearedEventIdStitch =
|
||||
dmConversationSource.getParticipantLastClearedEventId(convoId, viewerId, viewerId)
|
||||
lastClearedEventIdStitch.flatMap {
|
||||
case Some(lastClearedEventId) => Stitch(dmEventId <= lastClearedEventId)
|
||||
case _ =>
|
||||
Stitch.False
|
||||
}
|
||||
case _ => Stitch.False
|
||||
}
|
||||
}
|
||||
|
||||
private def dmEventOccurredBeforeJoinConversationEvent(
|
||||
dmEventOptStitch: Stitch[Option[Event]],
|
||||
dmEventId: DmEventId,
|
||||
viewerId: UserId
|
||||
): Stitch[Boolean] = {
|
||||
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
|
||||
case Some(convoId) =>
|
||||
val joinConversationEventIdStitch =
|
||||
dmConversationSource
|
||||
.getParticipantJoinConversationEventId(convoId, viewerId, viewerId)
|
||||
joinConversationEventIdStitch.flatMap {
|
||||
case Some(joinConversationEventId) => Stitch(dmEventId < joinConversationEventId)
|
||||
case _ => Stitch.False
|
||||
}
|
||||
case _ => Stitch.False
|
||||
}
|
||||
}
|
||||
|
||||
private def dmEventViewerIsDmConversationParticipant(
|
||||
dmEventOptStitch: Stitch[Option[Event]],
|
||||
viewerId: UserId
|
||||
): Stitch[Boolean] = {
|
||||
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
|
||||
case Some(convoId) =>
|
||||
dmConversationFeatures.viewerIsDmConversationParticipant(convoId, Some(viewerId))
|
||||
case _ => Stitch.True
|
||||
}
|
||||
}
|
||||
|
||||
private def dmEventIsDeleted(
|
||||
dmEventOptStitch: Stitch[Option[Event]],
|
||||
dmEventId: DmEventId
|
||||
): Stitch[Boolean] =
|
||||
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
|
||||
case Some(convoId) =>
|
||||
dmConversationSource
|
||||
.getDeleteInfo(convoId, dmEventId).rescue {
|
||||
case e: java.lang.IllegalArgumentException =>
|
||||
Stitch.exception(InvalidDmEventFeatureException("Invalid conversation id"))
|
||||
}.flatMap {
|
||||
case Some(StoredDelete(None)) => Stitch.True
|
||||
case _ => Stitch.False
|
||||
}
|
||||
case _ => Stitch.False
|
||||
}
|
||||
|
||||
private def dmEventIsHidden(
|
||||
dmEventOptStitch: Stitch[Option[Event]],
|
||||
dmEventId: DmEventId
|
||||
): Stitch[Boolean] =
|
||||
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
|
||||
case Some(convoId) =>
|
||||
dmConversationSource
|
||||
.getPerspectivalMessageInfo(convoId, dmEventId).rescue {
|
||||
case e: java.lang.IllegalArgumentException =>
|
||||
Stitch.exception(InvalidDmEventFeatureException("Invalid conversation id"))
|
||||
}.flatMap {
|
||||
case Some(StoredPerspectivalMessageInfo(Some(hidden), _)) if hidden =>
|
||||
Stitch.True
|
||||
case Some(StoredPerspectivalMessageInfo(_, Some(spamState)))
|
||||
if spamState == PerspectivalSpamState.Spam =>
|
||||
Stitch.True
|
||||
case _ => Stitch.False
|
||||
}
|
||||
case _ => Stitch.False
|
||||
}
|
||||
|
||||
private def viewerIsDmEventInitiatingUser(
|
||||
dmEventOptStitch: Stitch[Option[Event]],
|
||||
viewerId: UserId
|
||||
): Stitch[Boolean] =
|
||||
Stitch
|
||||
.join(
|
||||
dmEventSource.getEventType(dmEventOptStitch),
|
||||
dmEventSource.getInitiatingUserId(dmEventOptStitch)).flatMap {
|
||||
case (
|
||||
Some(
|
||||
EventType.TrustConversation | EventType.CsFeedbackSubmitted |
|
||||
EventType.CsFeedbackDismissed | EventType.WelcomeMessageCreate |
|
||||
EventType.JoinConversation),
|
||||
Some(userId)) =>
|
||||
Stitch(viewerId == userId)
|
||||
case (
|
||||
Some(
|
||||
EventType.TrustConversation | EventType.CsFeedbackSubmitted |
|
||||
EventType.CsFeedbackDismissed | EventType.WelcomeMessageCreate |
|
||||
EventType.JoinConversation),
|
||||
None) =>
|
||||
Stitch.exception(InvalidDmEventFeatureException("Initiating user id is missing"))
|
||||
case (None, _) =>
|
||||
Stitch.exception(InvalidDmEventFeatureException("DmEvent type is missing"))
|
||||
case _ => Stitch.True
|
||||
}
|
||||
|
||||
private def dmEventInOneToOneConversationWithUnavailableUser(
|
||||
dmEventOptStitch: Stitch[Option[Event]],
|
||||
viewerId: UserId
|
||||
): Stitch[Boolean] =
|
||||
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
|
||||
case Some(conversationId) =>
|
||||
dmConversationFeatures
|
||||
.dmConversationIsOneToOneConversation(conversationId, Some(viewerId)).flatMap {
|
||||
isOneToOne =>
|
||||
if (isOneToOne) {
|
||||
Stitch
|
||||
.join(
|
||||
dmConversationFeatures
|
||||
.dmConversationHasSuspendedParticipant(conversationId, Some(viewerId)),
|
||||
dmConversationFeatures
|
||||
.dmConversationHasDeactivatedParticipant(conversationId, Some(viewerId)),
|
||||
dmConversationFeatures
|
||||
.dmConversationHasErasedParticipant(conversationId, Some(viewerId))
|
||||
).flatMap {
|
||||
case (
|
||||
convoParticipantIsSuspended,
|
||||
convoParticipantIsDeactivated,
|
||||
convoParticipantIsErased) =>
|
||||
Stitch.value(
|
||||
convoParticipantIsSuspended || convoParticipantIsDeactivated || convoParticipantIsErased)
|
||||
}
|
||||
} else {
|
||||
Stitch.False
|
||||
}
|
||||
}
|
||||
case _ => Stitch.False
|
||||
}
|
||||
|
||||
private def dmEventInOneToOneConversation(
|
||||
dmEventOptStitch: Stitch[Option[Event]],
|
||||
viewerId: UserId
|
||||
): Stitch[Boolean] =
|
||||
dmEventSource.getConversationId(dmEventOptStitch).flatMap {
|
||||
case Some(conversationId) =>
|
||||
dmConversationFeatures
|
||||
.dmConversationIsOneToOneConversation(conversationId, Some(viewerId))
|
||||
case _ => Stitch.False
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/com/google/guava",
|
||||
"mediaservices/commons/src/main/thrift:thrift-scala",
|
||||
"mediaservices/media-util/src/main/scala",
|
||||
"snowflake/src/main/scala/com/twitter/snowflake/id",
|
||||
"src/thrift/com/twitter/context:twitter-context-scala",
|
||||
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:tweet-rtf-event-scala",
|
||||
"src/thrift/com/twitter/tweetypie:service-scala",
|
||||
"src/thrift/com/twitter/tweetypie:tweet-scala",
|
||||
"stitch/stitch-core",
|
||||
"tweetypie/src/scala/com/twitter/tweetypie/additionalfields",
|
||||
"twitter-context/src/main/scala",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common",
|
||||
"visibility/lib/src/main/resources/config",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder/tweets",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/features",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/tweets",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/util",
|
||||
"visibility/lib/src/main/thrift/com/twitter/visibility/safety_label_store:safety-label-store-scala",
|
||||
],
|
||||
)
|
|
@ -1,90 +0,0 @@
|
|||
package com.twitter.visibility.builder.media
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.mediaservices.media_util.GenericMediaKey
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.MediaSafetyLabelMapSource
|
||||
import com.twitter.visibility.features.MediaSafetyLabels
|
||||
import com.twitter.visibility.models.MediaSafetyLabel
|
||||
import com.twitter.visibility.models.MediaSafetyLabelType
|
||||
import com.twitter.visibility.models.SafetyLabel
|
||||
|
||||
class MediaFeatures(
|
||||
mediaSafetyLabelMap: StratoMediaLabelMaps,
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("media_features")
|
||||
|
||||
private[this] val requests =
|
||||
scopedStatsReceiver
|
||||
.counter("requests")
|
||||
|
||||
private[this] val mediaSafetyLabelsStats =
|
||||
scopedStatsReceiver
|
||||
.scope(MediaSafetyLabels.name)
|
||||
.counter("requests")
|
||||
|
||||
private[this] val nonEmptyMediaStats = scopedStatsReceiver.scope("non_empty_media")
|
||||
private[this] val nonEmptyMediaRequests = nonEmptyMediaStats.counter("requests")
|
||||
private[this] val nonEmptyMediaKeysCount = nonEmptyMediaStats.counter("keys")
|
||||
private[this] val nonEmptyMediaKeysLength = nonEmptyMediaStats.stat("keys_length")
|
||||
|
||||
def forMediaKeys(
|
||||
mediaKeys: Seq[GenericMediaKey],
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
nonEmptyMediaKeysCount.incr(mediaKeys.size)
|
||||
mediaSafetyLabelsStats.incr()
|
||||
|
||||
if (mediaKeys.nonEmpty) {
|
||||
nonEmptyMediaRequests.incr()
|
||||
nonEmptyMediaKeysLength.add(mediaKeys.size)
|
||||
}
|
||||
|
||||
_.withFeature(MediaSafetyLabels, mediaSafetyLabelMap.forGenericMediaKeys(mediaKeys))
|
||||
}
|
||||
|
||||
def forGenericMediaKey(
|
||||
genericMediaKey: GenericMediaKey
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
nonEmptyMediaKeysCount.incr()
|
||||
mediaSafetyLabelsStats.incr()
|
||||
nonEmptyMediaRequests.incr()
|
||||
nonEmptyMediaKeysLength.add(1L)
|
||||
|
||||
_.withFeature(MediaSafetyLabels, mediaSafetyLabelMap.forGenericMediaKey(genericMediaKey))
|
||||
}
|
||||
}
|
||||
|
||||
class StratoMediaLabelMaps(source: MediaSafetyLabelMapSource) {
|
||||
|
||||
def forGenericMediaKeys(
|
||||
mediaKeys: Seq[GenericMediaKey],
|
||||
): Stitch[Seq[MediaSafetyLabel]] = {
|
||||
Stitch
|
||||
.collect(
|
||||
mediaKeys
|
||||
.map(getFilteredSafetyLabels)
|
||||
).map(_.flatten)
|
||||
}
|
||||
|
||||
def forGenericMediaKey(
|
||||
genericMediaKey: GenericMediaKey
|
||||
): Stitch[Seq[MediaSafetyLabel]] = {
|
||||
getFilteredSafetyLabels(genericMediaKey)
|
||||
}
|
||||
|
||||
private def getFilteredSafetyLabels(
|
||||
genericMediaKey: GenericMediaKey,
|
||||
): Stitch[Seq[MediaSafetyLabel]] =
|
||||
source
|
||||
.fetch(genericMediaKey).map(_.flatMap(_.labels.map { stratoSafetyLabelMap =>
|
||||
stratoSafetyLabelMap
|
||||
.map(label =>
|
||||
MediaSafetyLabel(
|
||||
MediaSafetyLabelType.fromThrift(label._1),
|
||||
SafetyLabel.fromThrift(label._2)))
|
||||
}).toSeq.flatten)
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package com.twitter.visibility.builder.media
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.mediaservices.media_util.GenericMediaKey
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.MediaMetadataSource
|
||||
import com.twitter.visibility.features.HasDmcaMediaFeature
|
||||
import com.twitter.visibility.features.MediaGeoRestrictionsAllowList
|
||||
import com.twitter.visibility.features.MediaGeoRestrictionsDenyList
|
||||
import com.twitter.visibility.features.AuthorId
|
||||
|
||||
class MediaMetadataFeatures(
|
||||
mediaMetadataSource: MediaMetadataSource,
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("media_metadata_features")
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val hasDmcaMedia =
|
||||
scopedStatsReceiver.scope(HasDmcaMediaFeature.name).counter("requests")
|
||||
private[this] val mediaGeoAllowList =
|
||||
scopedStatsReceiver.scope(MediaGeoRestrictionsAllowList.name).counter("requests")
|
||||
private[this] val mediaGeoDenyList =
|
||||
scopedStatsReceiver.scope(MediaGeoRestrictionsDenyList.name).counter("requests")
|
||||
private[this] val uploaderId =
|
||||
scopedStatsReceiver.scope(AuthorId.name).counter("requests")
|
||||
|
||||
def forGenericMediaKey(
|
||||
genericMediaKey: GenericMediaKey
|
||||
): FeatureMapBuilder => FeatureMapBuilder = { featureMapBuilder =>
|
||||
requests.incr()
|
||||
|
||||
featureMapBuilder.withFeature(
|
||||
HasDmcaMediaFeature,
|
||||
mediaIsDmca(genericMediaKey)
|
||||
)
|
||||
|
||||
featureMapBuilder.withFeature(
|
||||
MediaGeoRestrictionsAllowList,
|
||||
geoRestrictionsAllowList(genericMediaKey)
|
||||
)
|
||||
|
||||
featureMapBuilder.withFeature(
|
||||
MediaGeoRestrictionsDenyList,
|
||||
geoRestrictionsDenyList(genericMediaKey)
|
||||
)
|
||||
|
||||
featureMapBuilder.withFeature(
|
||||
AuthorId,
|
||||
mediaUploaderId(genericMediaKey)
|
||||
)
|
||||
}
|
||||
|
||||
private def mediaIsDmca(genericMediaKey: GenericMediaKey) = {
|
||||
hasDmcaMedia.incr()
|
||||
mediaMetadataSource.getMediaIsDmca(genericMediaKey)
|
||||
}
|
||||
|
||||
private def geoRestrictionsAllowList(genericMediaKey: GenericMediaKey) = {
|
||||
mediaGeoAllowList.incr()
|
||||
mediaMetadataSource.getGeoRestrictionsAllowList(genericMediaKey).map { allowListOpt =>
|
||||
allowListOpt.getOrElse(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
private def geoRestrictionsDenyList(genericMediaKey: GenericMediaKey) = {
|
||||
mediaGeoDenyList.incr()
|
||||
mediaMetadataSource.getGeoRestrictionsDenyList(genericMediaKey).map { denyListOpt =>
|
||||
denyListOpt.getOrElse(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
private def mediaUploaderId(genericMediaKey: GenericMediaKey) = {
|
||||
uploaderId.incr()
|
||||
mediaMetadataSource.getMediaUploaderId(genericMediaKey).map { uploaderIdOpt =>
|
||||
uploaderIdOpt.map(Set(_)).getOrElse(Set.empty)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/com/google/guava",
|
||||
"snowflake/src/main/scala/com/twitter/snowflake/id",
|
||||
"src/thrift/com/twitter/context:twitter-context-scala",
|
||||
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:tweet-rtf-event-scala",
|
||||
"stitch/stitch-core",
|
||||
"twitter-context/src/main/scala",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common",
|
||||
"visibility/lib/src/main/resources/config",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder/common",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/features",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/util",
|
||||
"visibility/lib/src/main/thrift/com/twitter/visibility/safety_label_store:safety-label-store-scala",
|
||||
],
|
||||
)
|
|
@ -1,131 +0,0 @@
|
|||
package com.twitter.visibility.builder.spaces
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.gizmoduck.thriftscala.Label
|
||||
import com.twitter.gizmoduck.thriftscala.MuteSurface
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.builder.common.MutedKeywordFeatures
|
||||
import com.twitter.visibility.builder.users.AuthorFeatures
|
||||
import com.twitter.visibility.builder.users.RelationshipFeatures
|
||||
import com.twitter.visibility.common.AudioSpaceSource
|
||||
import com.twitter.visibility.common.SpaceId
|
||||
import com.twitter.visibility.common.SpaceSafetyLabelMapSource
|
||||
import com.twitter.visibility.common.UserId
|
||||
import com.twitter.visibility.features._
|
||||
import com.twitter.visibility.models.{MutedKeyword => VfMutedKeyword}
|
||||
import com.twitter.visibility.models.SafetyLabel
|
||||
import com.twitter.visibility.models.SpaceSafetyLabel
|
||||
import com.twitter.visibility.models.SpaceSafetyLabelType
|
||||
|
||||
class SpaceFeatures(
|
||||
spaceSafetyLabelMap: StratoSpaceLabelMaps,
|
||||
authorFeatures: AuthorFeatures,
|
||||
relationshipFeatures: RelationshipFeatures,
|
||||
mutedKeywordFeatures: MutedKeywordFeatures,
|
||||
audioSpaceSource: AudioSpaceSource) {
|
||||
|
||||
def forSpaceAndAuthorIds(
|
||||
spaceId: SpaceId,
|
||||
viewerId: Option[UserId],
|
||||
authorIds: Option[Seq[UserId]]
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
|
||||
_.withFeature(SpaceSafetyLabels, spaceSafetyLabelMap.forSpaceId(spaceId))
|
||||
.withFeature(AuthorId, getSpaceAuthors(spaceId, authorIds).map(_.toSet))
|
||||
.withFeature(AuthorUserLabels, allSpaceAuthorLabels(spaceId, authorIds))
|
||||
.withFeature(ViewerFollowsAuthor, viewerFollowsAnySpaceAuthor(spaceId, authorIds, viewerId))
|
||||
.withFeature(ViewerMutesAuthor, viewerMutesAnySpaceAuthor(spaceId, authorIds, viewerId))
|
||||
.withFeature(ViewerBlocksAuthor, viewerBlocksAnySpaceAuthor(spaceId, authorIds, viewerId))
|
||||
.withFeature(AuthorBlocksViewer, anySpaceAuthorBlocksViewer(spaceId, authorIds, viewerId))
|
||||
.withFeature(
|
||||
ViewerMutesKeywordInSpaceTitleForNotifications,
|
||||
titleContainsMutedKeyword(
|
||||
audioSpaceSource.getSpaceTitle(spaceId),
|
||||
audioSpaceSource.getSpaceLanguage(spaceId),
|
||||
viewerId)
|
||||
)
|
||||
}
|
||||
|
||||
def titleContainsMutedKeyword(
|
||||
titleOptStitch: Stitch[Option[String]],
|
||||
languageOptStitch: Stitch[Option[String]],
|
||||
viewerId: Option[UserId],
|
||||
): Stitch[VfMutedKeyword] = {
|
||||
titleOptStitch.flatMap {
|
||||
case None => Stitch.value(VfMutedKeyword(None))
|
||||
case Some(spaceTitle) =>
|
||||
languageOptStitch.flatMap { languageOpt =>
|
||||
mutedKeywordFeatures.spaceTitleContainsMutedKeyword(
|
||||
spaceTitle,
|
||||
languageOpt,
|
||||
mutedKeywordFeatures.allMutedKeywords(viewerId),
|
||||
MuteSurface.Notifications)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getSpaceAuthors(
|
||||
spaceId: SpaceId,
|
||||
authorIdsFromRequest: Option[Seq[UserId]]
|
||||
): Stitch[Seq[UserId]] = {
|
||||
authorIdsFromRequest match {
|
||||
case Some(authorIds) => Stitch.apply(authorIds)
|
||||
case _ => audioSpaceSource.getAdminIds(spaceId)
|
||||
}
|
||||
}
|
||||
|
||||
def allSpaceAuthorLabels(
|
||||
spaceId: SpaceId,
|
||||
authorIdsFromRequest: Option[Seq[UserId]]
|
||||
): Stitch[Seq[Label]] = {
|
||||
getSpaceAuthors(spaceId, authorIdsFromRequest)
|
||||
.flatMap(authorIds =>
|
||||
Stitch.collect(authorIds.map(authorId => authorFeatures.authorUserLabels(authorId)))).map(
|
||||
_.flatten)
|
||||
}
|
||||
|
||||
def viewerMutesAnySpaceAuthor(
|
||||
spaceId: SpaceId,
|
||||
authorIdsFromRequest: Option[Seq[UserId]],
|
||||
viewerId: Option[UserId]
|
||||
): Stitch[Boolean] = {
|
||||
getSpaceAuthors(spaceId, authorIdsFromRequest)
|
||||
.flatMap(authorIds =>
|
||||
Stitch.collect(authorIds.map(authorId =>
|
||||
relationshipFeatures.viewerMutesAuthor(authorId, viewerId)))).map(_.contains(true))
|
||||
}
|
||||
|
||||
def anySpaceAuthorBlocksViewer(
|
||||
spaceId: SpaceId,
|
||||
authorIdsFromRequest: Option[Seq[UserId]],
|
||||
viewerId: Option[UserId]
|
||||
): Stitch[Boolean] = {
|
||||
getSpaceAuthors(spaceId, authorIdsFromRequest)
|
||||
.flatMap(authorIds =>
|
||||
Stitch.collect(authorIds.map(authorId =>
|
||||
relationshipFeatures.authorBlocksViewer(authorId, viewerId)))).map(_.contains(true))
|
||||
}
|
||||
}
|
||||
|
||||
class StratoSpaceLabelMaps(
|
||||
spaceSafetyLabelSource: SpaceSafetyLabelMapSource,
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("space_features")
|
||||
private[this] val spaceSafetyLabelsStats =
|
||||
scopedStatsReceiver.scope(SpaceSafetyLabels.name).counter("requests")
|
||||
|
||||
def forSpaceId(
|
||||
spaceId: SpaceId,
|
||||
): Stitch[Seq[SpaceSafetyLabel]] = {
|
||||
spaceSafetyLabelSource
|
||||
.fetch(spaceId).map(_.flatMap(_.labels.map { stratoSafetyLabelMap =>
|
||||
stratoSafetyLabelMap
|
||||
.map(label =>
|
||||
SpaceSafetyLabel(
|
||||
SpaceSafetyLabelType.fromThrift(label._1),
|
||||
SafetyLabel.fromThrift(label._2)))
|
||||
}).toSeq.flatten).ensure(spaceSafetyLabelsStats.incr)
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/com/google/guava",
|
||||
"communities/thrift/src/main/thrift/com/twitter/communities:thrift-scala",
|
||||
"communities/thrift/src/main/thrift/com/twitter/communities/moderation:thrift-scala",
|
||||
"communities/thrift/src/main/thrift/com/twitter/communities/visibility:thrift-scala",
|
||||
"escherbird/src/thrift/com/twitter/escherbird/softintervention:softintervention_thrift-scala",
|
||||
"mediaservices/media-util/src/main/scala",
|
||||
"notificationservice/common/src/main/scala/com/twitter/notificationservice/model:alias",
|
||||
"notificationservice/common/src/main/scala/com/twitter/notificationservice/model/notification",
|
||||
"snowflake/src/main/scala/com/twitter/snowflake/id",
|
||||
"src/thrift/com/twitter/context:twitter-context-scala",
|
||||
"src/thrift/com/twitter/escherbird/common:common-scala",
|
||||
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
|
||||
"src/thrift/com/twitter/search/common:constants-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:tweet-rtf-event-scala",
|
||||
"src/thrift/com/twitter/tweetypie:tweet-scala",
|
||||
"stitch/stitch-core",
|
||||
# "tweetypie/src/scala/com/twitter/tweetypie/additionalfields",
|
||||
"twitter-context/src/main/scala",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common/stitch",
|
||||
"visibility/lib/src/main/resources/config",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/features",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/blender",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/search",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/tweets",
|
||||
"visibility/lib/src/main/thrift/com/twitter/visibility/strato:vf-strato-scala",
|
||||
],
|
||||
)
|
|
@ -1,45 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.search.common.constants.thriftscala.ThriftQuerySource
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.features.SearchCandidateCount
|
||||
import com.twitter.visibility.features.SearchQueryHasUser
|
||||
import com.twitter.visibility.features.SearchQuerySource
|
||||
import com.twitter.visibility.features.SearchResultsPageNumber
|
||||
import com.twitter.visibility.interfaces.common.blender.BlenderVFRequestContext
|
||||
|
||||
@Deprecated
|
||||
class BlenderContextFeatures(
|
||||
statsReceiver: StatsReceiver) {
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("blender_context_features")
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
private[this] val searchResultsPageNumber =
|
||||
scopedStatsReceiver.scope(SearchResultsPageNumber.name).counter("requests")
|
||||
private[this] val searchCandidateCount =
|
||||
scopedStatsReceiver.scope(SearchCandidateCount.name).counter("requests")
|
||||
private[this] val searchQuerySource =
|
||||
scopedStatsReceiver.scope(SearchQuerySource.name).counter("requests")
|
||||
private[this] val searchQueryHasUser =
|
||||
scopedStatsReceiver.scope(SearchQueryHasUser.name).counter("requests")
|
||||
|
||||
def forBlenderContext(
|
||||
blenderContext: BlenderVFRequestContext
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
searchResultsPageNumber.incr()
|
||||
searchCandidateCount.incr()
|
||||
searchQuerySource.incr()
|
||||
searchQueryHasUser.incr()
|
||||
|
||||
_.withConstantFeature(SearchResultsPageNumber, blenderContext.resultsPageNumber)
|
||||
.withConstantFeature(SearchCandidateCount, blenderContext.candidateCount)
|
||||
.withConstantFeature(
|
||||
SearchQuerySource,
|
||||
blenderContext.querySourceOption match {
|
||||
case Some(querySource) => querySource
|
||||
case _ => ThriftQuerySource.Unknown
|
||||
})
|
||||
.withConstantFeature(SearchQueryHasUser, blenderContext.queryHasUser)
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.notificationservice.model.notification.ActivityNotification
|
||||
import com.twitter.notificationservice.model.notification.MentionNotification
|
||||
import com.twitter.notificationservice.model.notification.MentionQuoteNotification
|
||||
import com.twitter.notificationservice.model.notification.Notification
|
||||
import com.twitter.notificationservice.model.notification.QuoteTweetNotification
|
||||
import com.twitter.servo.util.Gate
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.TweetSource
|
||||
import com.twitter.visibility.features.NotificationIsOnCommunityTweet
|
||||
import com.twitter.visibility.models.CommunityTweet
|
||||
|
||||
object CommunityNotificationFeatures {
|
||||
def ForNonCommunityTweetNotification: FeatureMapBuilder => FeatureMapBuilder = {
|
||||
_.withConstantFeature(NotificationIsOnCommunityTweet, false)
|
||||
}
|
||||
}
|
||||
|
||||
class CommunityNotificationFeatures(
|
||||
tweetSource: TweetSource,
|
||||
enableCommunityTweetHydration: Gate[Long],
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("community_notification_features")
|
||||
private[this] val requestsCounter = scopedStatsReceiver.counter("requests")
|
||||
private[this] val hydrationsCounter = scopedStatsReceiver.counter("hydrations")
|
||||
private[this] val notificationIsOnCommunityTweetCounter =
|
||||
scopedStatsReceiver.scope(NotificationIsOnCommunityTweet.name).counter("true")
|
||||
private[this] val notificationIsNotOnCommunityTweetCounter =
|
||||
scopedStatsReceiver.scope(NotificationIsOnCommunityTweet.name).counter("false")
|
||||
|
||||
def forNotification(notification: Notification): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requestsCounter.incr()
|
||||
val isCommunityTweetResult = getTweetIdOption(notification) match {
|
||||
case Some(tweetId) if enableCommunityTweetHydration(notification.target) =>
|
||||
hydrationsCounter.incr()
|
||||
tweetSource
|
||||
.getTweet(tweetId)
|
||||
.map {
|
||||
case Some(tweet) if CommunityTweet(tweet).nonEmpty =>
|
||||
notificationIsOnCommunityTweetCounter.incr()
|
||||
true
|
||||
case _ =>
|
||||
notificationIsNotOnCommunityTweetCounter.incr()
|
||||
false
|
||||
}
|
||||
case _ => Stitch.False
|
||||
}
|
||||
_.withFeature(NotificationIsOnCommunityTweet, isCommunityTweetResult)
|
||||
}
|
||||
|
||||
private[this] def getTweetIdOption(notification: Notification): Option[Long] = {
|
||||
notification match {
|
||||
case n: MentionNotification => Some(n.mentioningTweetId)
|
||||
case n: MentionQuoteNotification => Some(n.mentioningTweetId)
|
||||
case n: QuoteTweetNotification => Some(n.quotedTweetId)
|
||||
case n: ActivityNotification[_] if n.visibilityTweets.contains(n.objectId) => Some(n.objectId)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.features.CommunityTweetAuthorIsRemoved
|
||||
import com.twitter.visibility.features.CommunityTweetCommunityNotFound
|
||||
import com.twitter.visibility.features.CommunityTweetCommunityDeleted
|
||||
import com.twitter.visibility.features.CommunityTweetCommunitySuspended
|
||||
import com.twitter.visibility.features.CommunityTweetCommunityVisible
|
||||
import com.twitter.visibility.features.CommunityTweetIsHidden
|
||||
import com.twitter.visibility.features.TweetIsCommunityTweet
|
||||
import com.twitter.visibility.features.ViewerIsCommunityAdmin
|
||||
import com.twitter.visibility.features.ViewerIsCommunityMember
|
||||
import com.twitter.visibility.features.ViewerIsCommunityModerator
|
||||
import com.twitter.visibility.features.ViewerIsInternalCommunitiesAdmin
|
||||
import com.twitter.visibility.models.CommunityTweet
|
||||
import com.twitter.visibility.models.ViewerContext
|
||||
|
||||
trait CommunityTweetFeatures {
|
||||
|
||||
def forTweet(
|
||||
tweet: Tweet,
|
||||
viewerContext: ViewerContext
|
||||
): FeatureMapBuilder => FeatureMapBuilder
|
||||
|
||||
def forTweetOnly(tweet: Tweet): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
_.withConstantFeature(
|
||||
TweetIsCommunityTweet,
|
||||
CommunityTweet(tweet).isDefined
|
||||
)
|
||||
}
|
||||
|
||||
protected def forNonCommunityTweet(): FeatureMapBuilder => FeatureMapBuilder = { builder =>
|
||||
builder
|
||||
.withConstantFeature(
|
||||
TweetIsCommunityTweet,
|
||||
false
|
||||
).withConstantFeature(
|
||||
CommunityTweetCommunityNotFound,
|
||||
false
|
||||
).withConstantFeature(
|
||||
CommunityTweetCommunitySuspended,
|
||||
false
|
||||
).withConstantFeature(
|
||||
CommunityTweetCommunityDeleted,
|
||||
false
|
||||
).withConstantFeature(
|
||||
CommunityTweetCommunityVisible,
|
||||
false
|
||||
).withConstantFeature(
|
||||
ViewerIsInternalCommunitiesAdmin,
|
||||
false
|
||||
).withConstantFeature(
|
||||
ViewerIsCommunityAdmin,
|
||||
false
|
||||
).withConstantFeature(
|
||||
ViewerIsCommunityModerator,
|
||||
false
|
||||
).withConstantFeature(
|
||||
ViewerIsCommunityMember,
|
||||
false
|
||||
).withConstantFeature(
|
||||
CommunityTweetIsHidden,
|
||||
false
|
||||
).withConstantFeature(
|
||||
CommunityTweetAuthorIsRemoved,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.servo.util.Gate
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.models.ViewerContext
|
||||
|
||||
class CommunityTweetFeaturesPartitioned(
|
||||
a: CommunityTweetFeatures,
|
||||
b: CommunityTweetFeatures,
|
||||
bEnabled: Gate[Unit],
|
||||
) extends CommunityTweetFeatures {
|
||||
override def forTweet(
|
||||
tweet: Tweet,
|
||||
viewerContext: ViewerContext
|
||||
): FeatureMapBuilder => FeatureMapBuilder =
|
||||
bEnabled.pick(
|
||||
b.forTweet(tweet, viewerContext),
|
||||
a.forTweet(tweet, viewerContext),
|
||||
)
|
||||
|
||||
override def forTweetOnly(tweet: Tweet): FeatureMapBuilder => FeatureMapBuilder = bEnabled.pick(
|
||||
b.forTweetOnly(tweet),
|
||||
a.forTweetOnly(tweet),
|
||||
)
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.communities.moderation.thriftscala.CommunityTweetModerationState
|
||||
import com.twitter.communities.moderation.thriftscala.CommunityUserModerationState
|
||||
import com.twitter.communities.visibility.thriftscala.CommunityVisibilityFeatures
|
||||
import com.twitter.communities.visibility.thriftscala.CommunityVisibilityFeaturesV1
|
||||
import com.twitter.communities.visibility.thriftscala.CommunityVisibilityResult
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.CommunitiesSource
|
||||
import com.twitter.visibility.features.CommunityTweetAuthorIsRemoved
|
||||
import com.twitter.visibility.features.CommunityTweetCommunityNotFound
|
||||
import com.twitter.visibility.features.CommunityTweetCommunityDeleted
|
||||
import com.twitter.visibility.features.CommunityTweetCommunitySuspended
|
||||
import com.twitter.visibility.features.CommunityTweetCommunityVisible
|
||||
import com.twitter.visibility.features.CommunityTweetIsHidden
|
||||
import com.twitter.visibility.features.TweetIsCommunityTweet
|
||||
import com.twitter.visibility.features.ViewerIsCommunityAdmin
|
||||
import com.twitter.visibility.features.ViewerIsCommunityMember
|
||||
import com.twitter.visibility.features.ViewerIsCommunityModerator
|
||||
import com.twitter.visibility.features.ViewerIsInternalCommunitiesAdmin
|
||||
import com.twitter.visibility.models.CommunityTweet
|
||||
import com.twitter.visibility.models.ViewerContext
|
||||
|
||||
class CommunityTweetFeaturesV2(communitiesSource: CommunitiesSource)
|
||||
extends CommunityTweetFeatures {
|
||||
private[this] def forCommunityTweet(
|
||||
communityTweet: CommunityTweet
|
||||
): FeatureMapBuilder => FeatureMapBuilder = { builder: FeatureMapBuilder =>
|
||||
{
|
||||
val communityVisibilityFeaturesStitch =
|
||||
communitiesSource.getCommunityVisibilityFeatures(communityTweet.communityId)
|
||||
val communityTweetModerationStateStitch =
|
||||
communitiesSource.getTweetModerationState(communityTweet.tweet.id)
|
||||
val communityTweetAuthorModerationStateStitch =
|
||||
communitiesSource.getUserModerationState(
|
||||
communityTweet.authorId,
|
||||
communityTweet.communityId
|
||||
)
|
||||
|
||||
def getFlagFromFeatures(f: CommunityVisibilityFeaturesV1 => Boolean): Stitch[Boolean] =
|
||||
communityVisibilityFeaturesStitch.map {
|
||||
case Some(CommunityVisibilityFeatures.V1(v1)) => f(v1)
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def getFlagFromCommunityVisibilityResult(
|
||||
f: CommunityVisibilityResult => Boolean
|
||||
): Stitch[Boolean] = getFlagFromFeatures { v =>
|
||||
f(v.communityVisibilityResult)
|
||||
}
|
||||
|
||||
builder
|
||||
.withConstantFeature(
|
||||
TweetIsCommunityTweet,
|
||||
true
|
||||
)
|
||||
.withFeature(
|
||||
CommunityTweetCommunityNotFound,
|
||||
getFlagFromCommunityVisibilityResult {
|
||||
case CommunityVisibilityResult.NotFound => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
.withFeature(
|
||||
CommunityTweetCommunitySuspended,
|
||||
getFlagFromCommunityVisibilityResult {
|
||||
case CommunityVisibilityResult.Suspended => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
.withFeature(
|
||||
CommunityTweetCommunityDeleted,
|
||||
getFlagFromCommunityVisibilityResult {
|
||||
case CommunityVisibilityResult.Deleted => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
.withFeature(
|
||||
CommunityTweetCommunityVisible,
|
||||
getFlagFromCommunityVisibilityResult {
|
||||
case CommunityVisibilityResult.Visible => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
.withFeature(
|
||||
ViewerIsInternalCommunitiesAdmin,
|
||||
getFlagFromFeatures { _.viewerIsInternalAdmin }
|
||||
)
|
||||
.withFeature(
|
||||
ViewerIsCommunityAdmin,
|
||||
getFlagFromFeatures { _.viewerIsCommunityAdmin }
|
||||
)
|
||||
.withFeature(
|
||||
ViewerIsCommunityModerator,
|
||||
getFlagFromFeatures { _.viewerIsCommunityModerator }
|
||||
)
|
||||
.withFeature(
|
||||
ViewerIsCommunityMember,
|
||||
getFlagFromFeatures { _.viewerIsCommunityMember }
|
||||
)
|
||||
.withFeature(
|
||||
CommunityTweetIsHidden,
|
||||
communityTweetModerationStateStitch.map {
|
||||
case Some(CommunityTweetModerationState.Hidden(_)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
.withFeature(
|
||||
CommunityTweetAuthorIsRemoved,
|
||||
communityTweetAuthorModerationStateStitch.map {
|
||||
case Some(CommunityUserModerationState.Removed(_)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def forTweet(
|
||||
tweet: Tweet,
|
||||
viewerContext: ViewerContext
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
CommunityTweet(tweet) match {
|
||||
case None => forNonCommunityTweet()
|
||||
case Some(communityTweet) => forCommunityTweet(communityTweet)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.tweetypie.thriftscala.ConversationControl
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.builder.users.RelationshipFeatures
|
||||
import com.twitter.visibility.common.InvitedToConversationRepo
|
||||
import com.twitter.visibility.features.ConversationRootAuthorFollowsViewer
|
||||
import com.twitter.visibility.features.TweetConversationViewerIsInvited
|
||||
import com.twitter.visibility.features.TweetConversationViewerIsInvitedViaReplyMention
|
||||
import com.twitter.visibility.features.TweetConversationViewerIsRootAuthor
|
||||
import com.twitter.visibility.features.TweetHasByInvitationConversationControl
|
||||
import com.twitter.visibility.features.TweetHasCommunityConversationControl
|
||||
import com.twitter.visibility.features.TweetHasFollowersConversationControl
|
||||
import com.twitter.visibility.features.ViewerFollowsConversationRootAuthor
|
||||
|
||||
class ConversationControlFeatures(
|
||||
relationshipFeatures: RelationshipFeatures,
|
||||
isInvitedToConversationRepository: InvitedToConversationRepo,
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("conversation_control_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val tweetCommunityConversationRequest =
|
||||
scopedStatsReceiver.scope(TweetHasCommunityConversationControl.name).counter("requests")
|
||||
private[this] val tweetByInvitationConversationRequest =
|
||||
scopedStatsReceiver.scope(TweetHasByInvitationConversationControl.name).counter("requests")
|
||||
private[this] val tweetFollowersConversationRequest =
|
||||
scopedStatsReceiver.scope(TweetHasFollowersConversationControl.name).counter("requests")
|
||||
private[this] val rootAuthorFollowsViewer =
|
||||
scopedStatsReceiver.scope(ConversationRootAuthorFollowsViewer.name).counter("requests")
|
||||
private[this] val viewerFollowsRootAuthor =
|
||||
scopedStatsReceiver.scope(ViewerFollowsConversationRootAuthor.name).counter("requests")
|
||||
|
||||
def isCommunityConversation(conversationControl: Option[ConversationControl]): Boolean =
|
||||
conversationControl
|
||||
.collect {
|
||||
case _: ConversationControl.Community =>
|
||||
tweetCommunityConversationRequest.incr()
|
||||
true
|
||||
}.getOrElse(false)
|
||||
|
||||
def isByInvitationConversation(conversationControl: Option[ConversationControl]): Boolean =
|
||||
conversationControl
|
||||
.collect {
|
||||
case _: ConversationControl.ByInvitation =>
|
||||
tweetByInvitationConversationRequest.incr()
|
||||
true
|
||||
}.getOrElse(false)
|
||||
|
||||
def isFollowersConversation(conversationControl: Option[ConversationControl]): Boolean =
|
||||
conversationControl
|
||||
.collect {
|
||||
case _: ConversationControl.Followers =>
|
||||
tweetFollowersConversationRequest.incr()
|
||||
true
|
||||
}.getOrElse(false)
|
||||
|
||||
def conversationRootAuthorId(
|
||||
conversationControl: Option[ConversationControl]
|
||||
): Option[Long] =
|
||||
conversationControl match {
|
||||
case Some(ConversationControl.Community(community)) =>
|
||||
Some(community.conversationTweetAuthorId)
|
||||
case Some(ConversationControl.ByInvitation(byInvitation)) =>
|
||||
Some(byInvitation.conversationTweetAuthorId)
|
||||
case Some(ConversationControl.Followers(followers)) =>
|
||||
Some(followers.conversationTweetAuthorId)
|
||||
case _ => None
|
||||
}
|
||||
|
||||
def viewerIsRootAuthor(
|
||||
conversationControl: Option[ConversationControl],
|
||||
viewerIdOpt: Option[Long]
|
||||
): Boolean =
|
||||
(conversationRootAuthorId(conversationControl), viewerIdOpt) match {
|
||||
case (Some(rootAuthorId), Some(viewerId)) if rootAuthorId == viewerId => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def viewerIsInvited(
|
||||
conversationControl: Option[ConversationControl],
|
||||
viewerId: Option[Long]
|
||||
): Boolean = {
|
||||
val invitedUserIds = conversationControl match {
|
||||
case Some(ConversationControl.Community(community)) =>
|
||||
community.invitedUserIds
|
||||
case Some(ConversationControl.ByInvitation(byInvitation)) =>
|
||||
byInvitation.invitedUserIds
|
||||
case Some(ConversationControl.Followers(followers)) =>
|
||||
followers.invitedUserIds
|
||||
case _ => Seq()
|
||||
}
|
||||
|
||||
viewerId.exists(invitedUserIds.contains(_))
|
||||
}
|
||||
|
||||
def conversationAuthorFollows(
|
||||
conversationControl: Option[ConversationControl],
|
||||
viewerId: Option[Long]
|
||||
): Stitch[Boolean] = {
|
||||
val conversationAuthorId = conversationControl.collect {
|
||||
case ConversationControl.Community(community) =>
|
||||
community.conversationTweetAuthorId
|
||||
}
|
||||
|
||||
conversationAuthorId match {
|
||||
case Some(authorId) =>
|
||||
rootAuthorFollowsViewer.incr()
|
||||
relationshipFeatures.authorFollowsViewer(authorId, viewerId)
|
||||
case None =>
|
||||
Stitch.False
|
||||
}
|
||||
}
|
||||
|
||||
def followsConversationAuthor(
|
||||
conversationControl: Option[ConversationControl],
|
||||
viewerId: Option[Long]
|
||||
): Stitch[Boolean] = {
|
||||
val conversationAuthorId = conversationControl.collect {
|
||||
case ConversationControl.Followers(followers) =>
|
||||
followers.conversationTweetAuthorId
|
||||
}
|
||||
|
||||
conversationAuthorId match {
|
||||
case Some(authorId) =>
|
||||
viewerFollowsRootAuthor.incr()
|
||||
relationshipFeatures.viewerFollowsAuthor(authorId, viewerId)
|
||||
case None =>
|
||||
Stitch.False
|
||||
}
|
||||
}
|
||||
|
||||
def viewerIsInvitedViaReplyMention(
|
||||
tweet: Tweet,
|
||||
viewerIdOpt: Option[Long]
|
||||
): Stitch[Boolean] = {
|
||||
val conversationIdOpt: Option[Long] = tweet.conversationControl match {
|
||||
case Some(ConversationControl.Community(community))
|
||||
if community.inviteViaMention.contains(true) =>
|
||||
tweet.coreData.flatMap(_.conversationId)
|
||||
case Some(ConversationControl.ByInvitation(invitation))
|
||||
if invitation.inviteViaMention.contains(true) =>
|
||||
tweet.coreData.flatMap(_.conversationId)
|
||||
case Some(ConversationControl.Followers(followers))
|
||||
if followers.inviteViaMention.contains(true) =>
|
||||
tweet.coreData.flatMap(_.conversationId)
|
||||
case _ => None
|
||||
}
|
||||
|
||||
(conversationIdOpt, viewerIdOpt) match {
|
||||
case (Some(conversationId), Some(viewerId)) =>
|
||||
isInvitedToConversationRepository(conversationId, viewerId)
|
||||
case _ => Stitch.False
|
||||
}
|
||||
}
|
||||
|
||||
def forTweet(tweet: Tweet, viewerId: Option[Long]): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
val cc = tweet.conversationControl
|
||||
|
||||
_.withConstantFeature(TweetHasCommunityConversationControl, isCommunityConversation(cc))
|
||||
.withConstantFeature(TweetHasByInvitationConversationControl, isByInvitationConversation(cc))
|
||||
.withConstantFeature(TweetHasFollowersConversationControl, isFollowersConversation(cc))
|
||||
.withConstantFeature(TweetConversationViewerIsRootAuthor, viewerIsRootAuthor(cc, viewerId))
|
||||
.withConstantFeature(TweetConversationViewerIsInvited, viewerIsInvited(cc, viewerId))
|
||||
.withFeature(ConversationRootAuthorFollowsViewer, conversationAuthorFollows(cc, viewerId))
|
||||
.withFeature(ViewerFollowsConversationRootAuthor, followsConversationAuthor(cc, viewerId))
|
||||
.withFeature(
|
||||
TweetConversationViewerIsInvitedViaReplyMention,
|
||||
viewerIsInvitedViaReplyMention(tweet, viewerId))
|
||||
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.tweetypie.thriftscala.EditControl
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.features.TweetIsEditTweet
|
||||
import com.twitter.visibility.features.TweetIsInitialTweet
|
||||
import com.twitter.visibility.features.TweetIsLatestTweet
|
||||
import com.twitter.visibility.features.TweetIsStaleTweet
|
||||
|
||||
class EditTweetFeatures(
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("edit_tweet_features")
|
||||
private[this] val tweetIsEditTweet =
|
||||
scopedStatsReceiver.scope(TweetIsEditTweet.name).counter("requests")
|
||||
private[this] val tweetIsStaleTweet =
|
||||
scopedStatsReceiver.scope(TweetIsStaleTweet.name).counter("requests")
|
||||
private[this] val tweetIsLatestTweet =
|
||||
scopedStatsReceiver.scope(TweetIsLatestTweet.name).counter("requests")
|
||||
private[this] val tweetIsInitialTweet =
|
||||
scopedStatsReceiver.scope(TweetIsInitialTweet.name).counter("requests")
|
||||
|
||||
def forTweet(
|
||||
tweet: Tweet
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
_.withConstantFeature(TweetIsEditTweet, tweetIsEditTweet(tweet))
|
||||
.withConstantFeature(TweetIsStaleTweet, tweetIsStaleTweet(tweet))
|
||||
.withConstantFeature(TweetIsLatestTweet, tweetIsLatestTweet(tweet))
|
||||
.withConstantFeature(TweetIsInitialTweet, tweetIsInitialTweet(tweet))
|
||||
}
|
||||
|
||||
def tweetIsStaleTweet(tweet: Tweet, incrementMetric: Boolean = true): Boolean = {
|
||||
if (incrementMetric) tweetIsStaleTweet.incr()
|
||||
|
||||
tweet.editControl match {
|
||||
case None => false
|
||||
case Some(ec) =>
|
||||
ec match {
|
||||
case eci: EditControl.Initial => eci.initial.editTweetIds.last != tweet.id
|
||||
case ece: EditControl.Edit =>
|
||||
ece.edit.editControlInitial.exists(_.editTweetIds.last != tweet.id)
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def tweetIsEditTweet(tweet: Tweet, incrementMetric: Boolean = true): Boolean = {
|
||||
if (incrementMetric) tweetIsEditTweet.incr()
|
||||
|
||||
tweet.editControl match {
|
||||
case None => false
|
||||
case Some(ec) =>
|
||||
ec match {
|
||||
case _: EditControl.Initial => false
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def tweetIsLatestTweet(tweet: Tweet): Boolean = {
|
||||
tweetIsLatestTweet.incr()
|
||||
!tweetIsStaleTweet(tweet = tweet, incrementMetric = false)
|
||||
}
|
||||
|
||||
def tweetIsInitialTweet(tweet: Tweet): Boolean = {
|
||||
tweetIsInitialTweet.incr()
|
||||
!tweetIsEditTweet(tweet = tweet, incrementMetric = false)
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.builder.users.ViewerVerbsAuthor
|
||||
import com.twitter.visibility.common.UserRelationshipSource
|
||||
import com.twitter.visibility.features.TweetIsExclusiveTweet
|
||||
import com.twitter.visibility.features.ViewerIsExclusiveTweetRootAuthor
|
||||
import com.twitter.visibility.features.ViewerSuperFollowsExclusiveTweetRootAuthor
|
||||
import com.twitter.visibility.models.ViewerContext
|
||||
|
||||
class ExclusiveTweetFeatures(
|
||||
userRelationshipSource: UserRelationshipSource,
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("exclusive_tweet_features")
|
||||
private[this] val viewerSuperFollowsAuthor =
|
||||
scopedStatsReceiver.scope(ViewerSuperFollowsExclusiveTweetRootAuthor.name).counter("requests")
|
||||
|
||||
def rootAuthorId(tweet: Tweet): Option[Long] =
|
||||
tweet.exclusiveTweetControl.map(_.conversationAuthorId)
|
||||
|
||||
def viewerIsRootAuthor(
|
||||
tweet: Tweet,
|
||||
viewerIdOpt: Option[Long]
|
||||
): Boolean =
|
||||
(rootAuthorId(tweet), viewerIdOpt) match {
|
||||
case (Some(rootAuthorId), Some(viewerId)) if rootAuthorId == viewerId => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def viewerSuperFollowsRootAuthor(
|
||||
tweet: Tweet,
|
||||
viewerId: Option[Long]
|
||||
): Stitch[Boolean] =
|
||||
rootAuthorId(tweet) match {
|
||||
case Some(authorId) =>
|
||||
ViewerVerbsAuthor(
|
||||
authorId,
|
||||
viewerId,
|
||||
userRelationshipSource.superFollows,
|
||||
viewerSuperFollowsAuthor)
|
||||
case None =>
|
||||
Stitch.False
|
||||
}
|
||||
|
||||
def forTweet(
|
||||
tweet: Tweet,
|
||||
viewerContext: ViewerContext
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
val viewerId = viewerContext.userId
|
||||
|
||||
_.withConstantFeature(TweetIsExclusiveTweet, tweet.exclusiveTweetControl.isDefined)
|
||||
.withConstantFeature(ViewerIsExclusiveTweetRootAuthor, viewerIsRootAuthor(tweet, viewerId))
|
||||
.withFeature(
|
||||
ViewerSuperFollowsExclusiveTweetRootAuthor,
|
||||
viewerSuperFollowsRootAuthor(tweet, viewerId))
|
||||
}
|
||||
|
||||
def forTweetOnly(tweet: Tweet): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
_.withConstantFeature(TweetIsExclusiveTweet, tweet.exclusiveTweetControl.isDefined)
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.builder.users.ViewerVerbsAuthor
|
||||
import com.twitter.visibility.common.UserId
|
||||
import com.twitter.visibility.common.UserRelationshipSource
|
||||
import com.twitter.visibility.features._
|
||||
import com.twitter.visibility.models.TweetSafetyLabel
|
||||
import com.twitter.visibility.models.ViolationLevel
|
||||
|
||||
class FosnrPefetchedLabelsRelationshipFeatures(
|
||||
userRelationshipSource: UserRelationshipSource,
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver =
|
||||
statsReceiver.scope("fonsr_prefetched_relationship_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val viewerFollowsAuthorOfViolatingTweet =
|
||||
scopedStatsReceiver.scope(ViewerFollowsAuthorOfViolatingTweet.name).counter("requests")
|
||||
|
||||
private[this] val viewerDoesNotFollowAuthorOfViolatingTweet =
|
||||
scopedStatsReceiver.scope(ViewerDoesNotFollowAuthorOfViolatingTweet.name).counter("requests")
|
||||
|
||||
def forNonFosnr(): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
_.withConstantFeature(ViewerFollowsAuthorOfViolatingTweet, false)
|
||||
.withConstantFeature(ViewerDoesNotFollowAuthorOfViolatingTweet, false)
|
||||
}
|
||||
def forTweetWithSafetyLabelsAndAuthorId(
|
||||
safetyLabels: Seq[TweetSafetyLabel],
|
||||
authorId: Long,
|
||||
viewerId: Option[Long]
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
_.withFeature(
|
||||
ViewerFollowsAuthorOfViolatingTweet,
|
||||
viewerFollowsAuthorOfViolatingTweet(safetyLabels, authorId, viewerId))
|
||||
.withFeature(
|
||||
ViewerDoesNotFollowAuthorOfViolatingTweet,
|
||||
viewerDoesNotFollowAuthorOfViolatingTweet(safetyLabels, authorId, viewerId))
|
||||
}
|
||||
def viewerFollowsAuthorOfViolatingTweet(
|
||||
safetyLabels: Seq[TweetSafetyLabel],
|
||||
authorId: UserId,
|
||||
viewerId: Option[UserId]
|
||||
): Stitch[Boolean] = {
|
||||
if (safetyLabels
|
||||
.map(ViolationLevel.fromTweetSafetyLabelOpt).collect {
|
||||
case Some(level) => level
|
||||
}.isEmpty) {
|
||||
return Stitch.False
|
||||
}
|
||||
ViewerVerbsAuthor(
|
||||
authorId,
|
||||
viewerId,
|
||||
userRelationshipSource.follows,
|
||||
viewerFollowsAuthorOfViolatingTweet)
|
||||
}
|
||||
def viewerDoesNotFollowAuthorOfViolatingTweet(
|
||||
safetyLabels: Seq[TweetSafetyLabel],
|
||||
authorId: UserId,
|
||||
viewerId: Option[UserId]
|
||||
): Stitch[Boolean] = {
|
||||
if (safetyLabels
|
||||
.map(ViolationLevel.fromTweetSafetyLabelOpt).collect {
|
||||
case Some(level) => level
|
||||
}.isEmpty) {
|
||||
return Stitch.False
|
||||
}
|
||||
ViewerVerbsAuthor(
|
||||
authorId,
|
||||
viewerId,
|
||||
userRelationshipSource.follows,
|
||||
viewerDoesNotFollowAuthorOfViolatingTweet).map(following => !following)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.builder.users.ViewerVerbsAuthor
|
||||
import com.twitter.visibility.common.UserId
|
||||
import com.twitter.visibility.common.UserRelationshipSource
|
||||
import com.twitter.visibility.features._
|
||||
import com.twitter.visibility.models.ViolationLevel
|
||||
|
||||
class FosnrRelationshipFeatures(
|
||||
tweetLabels: TweetLabels,
|
||||
userRelationshipSource: UserRelationshipSource,
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("fonsr_relationship_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val viewerFollowsAuthorOfViolatingTweet =
|
||||
scopedStatsReceiver.scope(ViewerFollowsAuthorOfViolatingTweet.name).counter("requests")
|
||||
|
||||
private[this] val viewerDoesNotFollowAuthorOfViolatingTweet =
|
||||
scopedStatsReceiver.scope(ViewerDoesNotFollowAuthorOfViolatingTweet.name).counter("requests")
|
||||
|
||||
def forTweetAndAuthorId(
|
||||
tweet: Tweet,
|
||||
authorId: Long,
|
||||
viewerId: Option[Long]
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
_.withFeature(
|
||||
ViewerFollowsAuthorOfViolatingTweet,
|
||||
viewerFollowsAuthorOfViolatingTweet(tweet, authorId, viewerId))
|
||||
.withFeature(
|
||||
ViewerDoesNotFollowAuthorOfViolatingTweet,
|
||||
viewerDoesNotFollowAuthorOfViolatingTweet(tweet, authorId, viewerId))
|
||||
}
|
||||
|
||||
def viewerFollowsAuthorOfViolatingTweet(
|
||||
tweet: Tweet,
|
||||
authorId: UserId,
|
||||
viewerId: Option[UserId]
|
||||
): Stitch[Boolean] =
|
||||
tweetLabels.forTweet(tweet).flatMap { safetyLabels =>
|
||||
if (safetyLabels
|
||||
.map(ViolationLevel.fromTweetSafetyLabelOpt).collect {
|
||||
case Some(level) => level
|
||||
}.isEmpty) {
|
||||
Stitch.False
|
||||
} else {
|
||||
ViewerVerbsAuthor(
|
||||
authorId,
|
||||
viewerId,
|
||||
userRelationshipSource.follows,
|
||||
viewerFollowsAuthorOfViolatingTweet)
|
||||
}
|
||||
}
|
||||
|
||||
def viewerDoesNotFollowAuthorOfViolatingTweet(
|
||||
tweet: Tweet,
|
||||
authorId: UserId,
|
||||
viewerId: Option[UserId]
|
||||
): Stitch[Boolean] =
|
||||
tweetLabels.forTweet(tweet).flatMap { safetyLabels =>
|
||||
if (safetyLabels
|
||||
.map(ViolationLevel.fromTweetSafetyLabelOpt).collect {
|
||||
case Some(level) => level
|
||||
}.isEmpty) {
|
||||
Stitch.False
|
||||
} else {
|
||||
ViewerVerbsAuthor(
|
||||
authorId,
|
||||
viewerId,
|
||||
userRelationshipSource.follows,
|
||||
viewerDoesNotFollowAuthorOfViolatingTweet).map(following => !following)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tweetypie.thriftscala.EscherbirdEntityAnnotations
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.MisinformationPolicySource
|
||||
import com.twitter.visibility.features._
|
||||
import com.twitter.visibility.models.MisinformationPolicy
|
||||
import com.twitter.visibility.models.SemanticCoreMisinformation
|
||||
import com.twitter.visibility.models.ViewerContext
|
||||
|
||||
class MisinformationPolicyFeatures(
|
||||
misinformationPolicySource: MisinformationPolicySource,
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver =
|
||||
statsReceiver.scope("misinformation_policy_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
private[this] val tweetMisinformationPolicies =
|
||||
scopedStatsReceiver.scope(TweetMisinformationPolicies.name).counter("requests")
|
||||
|
||||
def forTweet(
|
||||
tweet: Tweet,
|
||||
viewerContext: ViewerContext
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
tweetMisinformationPolicies.incr()
|
||||
|
||||
_.withFeature(
|
||||
TweetMisinformationPolicies,
|
||||
misinformationPolicy(tweet.escherbirdEntityAnnotations, viewerContext))
|
||||
.withFeature(
|
||||
TweetEnglishMisinformationPolicies,
|
||||
misinformationPolicyEnglishOnly(tweet.escherbirdEntityAnnotations))
|
||||
}
|
||||
|
||||
def misinformationPolicyEnglishOnly(
|
||||
escherbirdEntityAnnotations: Option[EscherbirdEntityAnnotations],
|
||||
): Stitch[Seq[MisinformationPolicy]] = {
|
||||
val locale = Some(
|
||||
MisinformationPolicySource.LanguageAndCountry(
|
||||
language = Some("en"),
|
||||
country = Some("us")
|
||||
))
|
||||
fetchMisinformationPolicy(escherbirdEntityAnnotations, locale)
|
||||
}
|
||||
|
||||
def misinformationPolicy(
|
||||
escherbirdEntityAnnotations: Option[EscherbirdEntityAnnotations],
|
||||
viewerContext: ViewerContext
|
||||
): Stitch[Seq[MisinformationPolicy]] = {
|
||||
val locale = viewerContext.requestLanguageCode.map { language =>
|
||||
MisinformationPolicySource.LanguageAndCountry(
|
||||
language = Some(language),
|
||||
country = viewerContext.requestCountryCode
|
||||
)
|
||||
}
|
||||
fetchMisinformationPolicy(escherbirdEntityAnnotations, locale)
|
||||
}
|
||||
|
||||
def fetchMisinformationPolicy(
|
||||
escherbirdEntityAnnotations: Option[EscherbirdEntityAnnotations],
|
||||
locale: Option[MisinformationPolicySource.LanguageAndCountry]
|
||||
): Stitch[Seq[MisinformationPolicy]] = {
|
||||
Stitch.collect(
|
||||
escherbirdEntityAnnotations
|
||||
.map(_.entityAnnotations)
|
||||
.getOrElse(Seq.empty)
|
||||
.filter(_.domainId == SemanticCoreMisinformation.domainId)
|
||||
.map(annotation =>
|
||||
misinformationPolicySource
|
||||
.fetch(
|
||||
annotation,
|
||||
locale
|
||||
)
|
||||
.map(misinformation =>
|
||||
MisinformationPolicy(
|
||||
annotation = annotation,
|
||||
misinformation = misinformation
|
||||
)))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.features.TweetIsModerated
|
||||
|
||||
class ModerationFeatures(moderationSource: Long => Boolean, statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver: StatsReceiver =
|
||||
statsReceiver.scope("moderation_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val tweetIsModerated =
|
||||
scopedStatsReceiver.scope(TweetIsModerated.name).counter("requests")
|
||||
|
||||
def forTweetId(tweetId: Long): FeatureMapBuilder => FeatureMapBuilder = { featureMapBuilder =>
|
||||
requests.incr()
|
||||
tweetIsModerated.incr()
|
||||
|
||||
featureMapBuilder.withConstantFeature(TweetIsModerated, moderationSource(tweetId))
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.search.common.constants.thriftscala.ThriftQuerySource
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.features.SearchCandidateCount
|
||||
import com.twitter.visibility.features.SearchQueryHasUser
|
||||
import com.twitter.visibility.features.SearchQuerySource
|
||||
import com.twitter.visibility.features.SearchResultsPageNumber
|
||||
import com.twitter.visibility.interfaces.common.search.SearchVFRequestContext
|
||||
|
||||
class SearchContextFeatures(
|
||||
statsReceiver: StatsReceiver) {
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("search_context_features")
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
private[this] val searchResultsPageNumber =
|
||||
scopedStatsReceiver.scope(SearchResultsPageNumber.name).counter("requests")
|
||||
private[this] val searchCandidateCount =
|
||||
scopedStatsReceiver.scope(SearchCandidateCount.name).counter("requests")
|
||||
private[this] val searchQuerySource =
|
||||
scopedStatsReceiver.scope(SearchQuerySource.name).counter("requests")
|
||||
private[this] val searchQueryHasUser =
|
||||
scopedStatsReceiver.scope(SearchQueryHasUser.name).counter("requests")
|
||||
|
||||
def forSearchContext(
|
||||
searchContext: SearchVFRequestContext
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
searchResultsPageNumber.incr()
|
||||
searchCandidateCount.incr()
|
||||
searchQuerySource.incr()
|
||||
searchQueryHasUser.incr()
|
||||
|
||||
_.withConstantFeature(SearchResultsPageNumber, searchContext.resultsPageNumber)
|
||||
.withConstantFeature(SearchCandidateCount, searchContext.candidateCount)
|
||||
.withConstantFeature(
|
||||
SearchQuerySource,
|
||||
searchContext.querySourceOption match {
|
||||
case Some(querySource) => querySource
|
||||
case _ => ThriftQuerySource.Unknown
|
||||
})
|
||||
.withConstantFeature(SearchQueryHasUser, searchContext.queryHasUser)
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.contenthealth.toxicreplyfilter.thriftscala.FilterState
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.features.ToxicReplyFilterConversationAuthorIsViewer
|
||||
import com.twitter.visibility.features.ToxicReplyFilterState
|
||||
|
||||
class ToxicReplyFilterFeature(
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
def forTweet(tweet: Tweet, viewerId: Option[Long]): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
builder =>
|
||||
requests.incr()
|
||||
|
||||
builder
|
||||
.withConstantFeature(ToxicReplyFilterState, isTweetFilteredFromAuthor(tweet))
|
||||
.withConstantFeature(
|
||||
ToxicReplyFilterConversationAuthorIsViewer,
|
||||
isRootAuthorViewer(tweet, viewerId))
|
||||
}
|
||||
|
||||
private[this] def isRootAuthorViewer(tweet: Tweet, maybeViewerId: Option[Long]): Boolean = {
|
||||
val maybeAuthorId = tweet.filteredReplyDetails.map(_.conversationAuthorId)
|
||||
|
||||
(maybeViewerId, maybeAuthorId) match {
|
||||
case (Some(viewerId), Some(authorId)) if viewerId == authorId => {
|
||||
rootAuthorViewerStats.incr()
|
||||
true
|
||||
}
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
private[this] def isTweetFilteredFromAuthor(
|
||||
tweet: Tweet,
|
||||
): FilterState = {
|
||||
val result = tweet.filteredReplyDetails.map(_.filterState).getOrElse(FilterState.Unfiltered)
|
||||
|
||||
if (result == FilterState.FilteredFromAuthor) {
|
||||
filteredFromAuthorStats.incr()
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
private[this] val scopedStatsReceiver =
|
||||
statsReceiver.scope("toxicreplyfilter")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val rootAuthorViewerStats =
|
||||
scopedStatsReceiver.scope(ToxicReplyFilterConversationAuthorIsViewer.name).counter("requests")
|
||||
|
||||
private[this] val filteredFromAuthorStats =
|
||||
scopedStatsReceiver.scope(ToxicReplyFilterState.name).counter("requests")
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.TrustedFriendsListId
|
||||
import com.twitter.visibility.common.TrustedFriendsSource
|
||||
import com.twitter.visibility.features.TweetIsTrustedFriendTweet
|
||||
import com.twitter.visibility.features.ViewerIsTrustedFriendOfTweetAuthor
|
||||
import com.twitter.visibility.features.ViewerIsTrustedFriendTweetAuthor
|
||||
|
||||
class TrustedFriendsFeatures(trustedFriendsSource: TrustedFriendsSource) {
|
||||
|
||||
private[builder] def viewerIsTrustedFriend(
|
||||
tweet: Tweet,
|
||||
viewerId: Option[Long]
|
||||
): Stitch[Boolean] =
|
||||
(trustedFriendsListId(tweet), viewerId) match {
|
||||
case (Some(tfListId), Some(userId)) =>
|
||||
trustedFriendsSource.isTrustedFriend(tfListId, userId)
|
||||
case _ => Stitch.False
|
||||
}
|
||||
|
||||
private[builder] def viewerIsTrustedFriendListOwner(
|
||||
tweet: Tweet,
|
||||
viewerId: Option[Long]
|
||||
): Stitch[Boolean] =
|
||||
(trustedFriendsListId(tweet), viewerId) match {
|
||||
case (Some(tfListId), Some(userId)) =>
|
||||
trustedFriendsSource.isTrustedFriendListOwner(tfListId, userId)
|
||||
case _ => Stitch.False
|
||||
}
|
||||
|
||||
private[builder] def trustedFriendsListId(tweet: Tweet): Option[TrustedFriendsListId] =
|
||||
tweet.trustedFriendsControl.map(_.trustedFriendsListId)
|
||||
|
||||
def forTweet(
|
||||
tweet: Tweet,
|
||||
viewerId: Option[Long]
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
_.withConstantFeature(
|
||||
TweetIsTrustedFriendTweet,
|
||||
tweet.trustedFriendsControl.isDefined
|
||||
).withFeature(
|
||||
ViewerIsTrustedFriendTweetAuthor,
|
||||
viewerIsTrustedFriendListOwner(tweet, viewerId)
|
||||
).withFeature(
|
||||
ViewerIsTrustedFriendOfTweetAuthor,
|
||||
viewerIsTrustedFriend(tweet, viewerId)
|
||||
)
|
||||
}
|
||||
|
||||
def forTweetOnly(tweet: Tweet): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
_.withConstantFeature(TweetIsTrustedFriendTweet, tweet.trustedFriendsControl.isDefined)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,210 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.snowflake.id.SnowflakeId
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tweetypie.thriftscala.CollabControl
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.util.Duration
|
||||
import com.twitter.util.Time
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.SafetyLabelMapSource
|
||||
import com.twitter.visibility.common.TweetId
|
||||
import com.twitter.visibility.common.UserId
|
||||
import com.twitter.visibility.features._
|
||||
import com.twitter.visibility.models.SemanticCoreAnnotation
|
||||
import com.twitter.visibility.models.TweetSafetyLabel
|
||||
|
||||
object TweetFeatures {
|
||||
|
||||
def FALLBACK_TIMESTAMP: Time = Time.epoch
|
||||
|
||||
def tweetIsSelfReply(tweet: Tweet): Boolean = {
|
||||
tweet.coreData match {
|
||||
case Some(coreData) =>
|
||||
coreData.reply match {
|
||||
case Some(reply) =>
|
||||
reply.inReplyToUserId == coreData.userId
|
||||
|
||||
case None =>
|
||||
false
|
||||
}
|
||||
|
||||
case None =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
def tweetReplyToParentTweetDuration(tweet: Tweet): Option[Duration] = for {
|
||||
coreData <- tweet.coreData
|
||||
reply <- coreData.reply
|
||||
inReplyToStatusId <- reply.inReplyToStatusId
|
||||
replyTime <- SnowflakeId.timeFromIdOpt(tweet.id)
|
||||
repliedToTime <- SnowflakeId.timeFromIdOpt(inReplyToStatusId)
|
||||
} yield {
|
||||
replyTime.diff(repliedToTime)
|
||||
}
|
||||
|
||||
def tweetReplyToRootTweetDuration(tweet: Tweet): Option[Duration] = for {
|
||||
coreData <- tweet.coreData
|
||||
if coreData.reply.isDefined
|
||||
conversationId <- coreData.conversationId
|
||||
replyTime <- SnowflakeId.timeFromIdOpt(tweet.id)
|
||||
rootTime <- SnowflakeId.timeFromIdOpt(conversationId)
|
||||
} yield {
|
||||
replyTime.diff(rootTime)
|
||||
}
|
||||
|
||||
def tweetTimestamp(tweetId: Long): Time =
|
||||
SnowflakeId.timeFromIdOpt(tweetId).getOrElse(FALLBACK_TIMESTAMP)
|
||||
|
||||
def tweetSemanticCoreAnnotations(tweet: Tweet): Seq[SemanticCoreAnnotation] = {
|
||||
tweet.escherbirdEntityAnnotations
|
||||
.map(a =>
|
||||
a.entityAnnotations.map { annotation =>
|
||||
SemanticCoreAnnotation(
|
||||
annotation.groupId,
|
||||
annotation.domainId,
|
||||
annotation.entityId
|
||||
)
|
||||
}).toSeq.flatten
|
||||
}
|
||||
|
||||
def tweetIsNullcast(tweet: Tweet): Boolean = {
|
||||
tweet.coreData match {
|
||||
case Some(coreData) =>
|
||||
coreData.nullcast
|
||||
case None =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
def tweetAuthorUserId(tweet: Tweet): Option[UserId] = {
|
||||
tweet.coreData.map(_.userId)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait TweetLabels {
|
||||
def forTweet(tweet: Tweet): Stitch[Seq[TweetSafetyLabel]]
|
||||
def forTweetId(tweetId: TweetId): Stitch[Seq[TweetSafetyLabel]]
|
||||
}
|
||||
|
||||
class StratoTweetLabelMaps(safetyLabelSource: SafetyLabelMapSource) extends TweetLabels {
|
||||
|
||||
override def forTweet(tweet: Tweet): Stitch[Seq[TweetSafetyLabel]] = {
|
||||
forTweetId(tweet.id)
|
||||
}
|
||||
|
||||
def forTweetId(tweetId: TweetId): Stitch[Seq[TweetSafetyLabel]] = {
|
||||
safetyLabelSource
|
||||
.fetch(tweetId).map(
|
||||
_.map(
|
||||
_.labels
|
||||
.map(
|
||||
_.map(sl => TweetSafetyLabel.fromTuple(sl._1, sl._2)).toSeq
|
||||
).getOrElse(Seq())
|
||||
).getOrElse(Seq()))
|
||||
}
|
||||
}
|
||||
|
||||
object NilTweetLabelMaps extends TweetLabels {
|
||||
override def forTweet(tweet: Tweet): Stitch[Seq[TweetSafetyLabel]] = Stitch.Nil
|
||||
override def forTweetId(tweetId: TweetId): Stitch[Seq[TweetSafetyLabel]] = Stitch.Nil
|
||||
}
|
||||
|
||||
class TweetFeatures(tweetLabels: TweetLabels, statsReceiver: StatsReceiver) {
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("tweet_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
private[this] val tweetSafetyLabels =
|
||||
scopedStatsReceiver.scope(TweetSafetyLabels.name).counter("requests")
|
||||
private[this] val tweetTakedownReasons =
|
||||
scopedStatsReceiver.scope(TweetTakedownReasons.name).counter("requests")
|
||||
private[this] val tweetIsSelfReply =
|
||||
scopedStatsReceiver.scope(TweetIsSelfReply.name).counter("requests")
|
||||
private[this] val tweetTimestamp =
|
||||
scopedStatsReceiver.scope(TweetTimestamp.name).counter("requests")
|
||||
private[this] val tweetReplyToParentTweetDuration =
|
||||
scopedStatsReceiver.scope(TweetReplyToParentTweetDuration.name).counter("requests")
|
||||
private[this] val tweetReplyToRootTweetDuration =
|
||||
scopedStatsReceiver.scope(TweetReplyToRootTweetDuration.name).counter("requests")
|
||||
private[this] val tweetSemanticCoreAnnotations =
|
||||
scopedStatsReceiver.scope(TweetSemanticCoreAnnotations.name).counter("requests")
|
||||
private[this] val tweetId =
|
||||
scopedStatsReceiver.scope(TweetId.name).counter("requests")
|
||||
private[this] val tweetHasNsfwUser =
|
||||
scopedStatsReceiver.scope(TweetHasNsfwUser.name).counter("requests")
|
||||
private[this] val tweetHasNsfwAdmin =
|
||||
scopedStatsReceiver.scope(TweetHasNsfwAdmin.name).counter("requests")
|
||||
private[this] val tweetIsNullcast =
|
||||
scopedStatsReceiver.scope(TweetIsNullcast.name).counter("requests")
|
||||
private[this] val tweetHasMedia =
|
||||
scopedStatsReceiver.scope(TweetHasMedia.name).counter("requests")
|
||||
private[this] val tweetIsCommunity =
|
||||
scopedStatsReceiver.scope(TweetIsCommunityTweet.name).counter("requests")
|
||||
private[this] val tweetIsCollabInvitation =
|
||||
scopedStatsReceiver.scope(TweetIsCollabInvitationTweet.name).counter("requests")
|
||||
|
||||
def forTweet(tweet: Tweet): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
forTweetWithoutSafetyLabels(tweet)
|
||||
.andThen(_.withFeature(TweetSafetyLabels, tweetLabels.forTweet(tweet)))
|
||||
}
|
||||
|
||||
def forTweetWithoutSafetyLabels(tweet: Tweet): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
|
||||
tweetTakedownReasons.incr()
|
||||
tweetIsSelfReply.incr()
|
||||
tweetTimestamp.incr()
|
||||
tweetReplyToParentTweetDuration.incr()
|
||||
tweetReplyToRootTweetDuration.incr()
|
||||
tweetSemanticCoreAnnotations.incr()
|
||||
tweetId.incr()
|
||||
tweetHasNsfwUser.incr()
|
||||
tweetHasNsfwAdmin.incr()
|
||||
tweetIsNullcast.incr()
|
||||
tweetHasMedia.incr()
|
||||
tweetIsCommunity.incr()
|
||||
tweetIsCollabInvitation.incr()
|
||||
|
||||
_.withConstantFeature(TweetTakedownReasons, tweet.takedownReasons.getOrElse(Seq.empty))
|
||||
.withConstantFeature(TweetIsSelfReply, TweetFeatures.tweetIsSelfReply(tweet))
|
||||
.withConstantFeature(TweetTimestamp, TweetFeatures.tweetTimestamp(tweet.id))
|
||||
.withConstantFeature(
|
||||
TweetReplyToParentTweetDuration,
|
||||
TweetFeatures.tweetReplyToParentTweetDuration(tweet))
|
||||
.withConstantFeature(
|
||||
TweetReplyToRootTweetDuration,
|
||||
TweetFeatures.tweetReplyToRootTweetDuration(tweet))
|
||||
.withConstantFeature(
|
||||
TweetSemanticCoreAnnotations,
|
||||
TweetFeatures.tweetSemanticCoreAnnotations(tweet))
|
||||
.withConstantFeature(TweetId, tweet.id)
|
||||
.withConstantFeature(TweetHasNsfwUser, tweetHasNsfwUser(tweet))
|
||||
.withConstantFeature(TweetHasNsfwAdmin, tweetHasNsfwAdmin(tweet))
|
||||
.withConstantFeature(TweetIsNullcast, TweetFeatures.tweetIsNullcast(tweet))
|
||||
.withConstantFeature(TweetHasMedia, tweetHasMedia(tweet))
|
||||
.withConstantFeature(TweetIsCommunityTweet, tweetHasCommunity(tweet))
|
||||
.withConstantFeature(TweetIsCollabInvitationTweet, tweetIsCollabInvitation(tweet))
|
||||
}
|
||||
|
||||
def tweetHasNsfwUser(tweet: Tweet): Boolean =
|
||||
tweet.coreData.exists(_.nsfwUser)
|
||||
|
||||
def tweetHasNsfwAdmin(tweet: Tweet): Boolean =
|
||||
tweet.coreData.exists(_.nsfwAdmin)
|
||||
|
||||
def tweetHasMedia(tweet: Tweet): Boolean =
|
||||
tweet.coreData.exists(_.hasMedia.getOrElse(false))
|
||||
|
||||
def tweetHasCommunity(tweet: Tweet): Boolean = {
|
||||
tweet.communities.exists(_.communityIds.nonEmpty)
|
||||
}
|
||||
|
||||
def tweetIsCollabInvitation(tweet: Tweet): Boolean = {
|
||||
tweet.collabControl.exists(_ match {
|
||||
case CollabControl.CollabInvitation(_) => true
|
||||
case _ => false
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.servo.util.Gate
|
||||
import com.twitter.spam.rtf.thriftscala.SafetyLabel
|
||||
import com.twitter.spam.rtf.thriftscala.SafetyLabelType
|
||||
import com.twitter.spam.rtf.thriftscala.SafetyLabelValue
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.stitch.StitchHelpers
|
||||
import com.twitter.visibility.features.TweetId
|
||||
import com.twitter.visibility.features.TweetSafetyLabels
|
||||
import com.twitter.visibility.features.TweetTimestamp
|
||||
import com.twitter.visibility.models.TweetSafetyLabel
|
||||
|
||||
class TweetIdFeatures(
|
||||
statsReceiver: StatsReceiver,
|
||||
enableStitchProfiling: Gate[Unit]) {
|
||||
private[this] val scopedStatsReceiver: StatsReceiver = statsReceiver.scope("tweet_id_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
private[this] val tweetSafetyLabels =
|
||||
scopedStatsReceiver.scope(TweetSafetyLabels.name).counter("requests")
|
||||
private[this] val tweetTimestamp =
|
||||
scopedStatsReceiver.scope(TweetTimestamp.name).counter("requests")
|
||||
|
||||
private[this] val labelFetchScope: StatsReceiver =
|
||||
scopedStatsReceiver.scope("labelFetch")
|
||||
|
||||
private[this] def getTweetLabels(
|
||||
tweetId: Long,
|
||||
labelFetcher: Long => Stitch[Map[SafetyLabelType, SafetyLabel]]
|
||||
): Stitch[Seq[TweetSafetyLabel]] = {
|
||||
val stitch =
|
||||
labelFetcher(tweetId).map { labelMap =>
|
||||
labelMap
|
||||
.map { case (labelType, label) => SafetyLabelValue(labelType, label) }.toSeq
|
||||
.map(TweetSafetyLabel.fromThrift)
|
||||
}
|
||||
|
||||
if (enableStitchProfiling()) {
|
||||
StitchHelpers.profileStitch(
|
||||
stitch,
|
||||
Seq(labelFetchScope)
|
||||
)
|
||||
} else {
|
||||
stitch
|
||||
}
|
||||
}
|
||||
|
||||
def forTweetId(
|
||||
tweetId: Long,
|
||||
labelFetcher: Long => Stitch[Map[SafetyLabelType, SafetyLabel]]
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
tweetSafetyLabels.incr()
|
||||
tweetTimestamp.incr()
|
||||
|
||||
_.withFeature(TweetSafetyLabels, getTweetLabels(tweetId, labelFetcher))
|
||||
.withConstantFeature(TweetTimestamp, TweetFeatures.tweetTimestamp(tweetId))
|
||||
.withConstantFeature(TweetId, tweetId)
|
||||
}
|
||||
|
||||
def forTweetId(
|
||||
tweetId: Long,
|
||||
constantTweetSafetyLabels: Seq[TweetSafetyLabel]
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
tweetSafetyLabels.incr()
|
||||
tweetTimestamp.incr()
|
||||
|
||||
_.withConstantFeature(TweetSafetyLabels, constantTweetSafetyLabels)
|
||||
.withConstantFeature(TweetTimestamp, TweetFeatures.tweetTimestamp(tweetId))
|
||||
.withConstantFeature(TweetId, tweetId)
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.mediaservices.commons.mediainformation.thriftscala.AdditionalMetadata
|
||||
import com.twitter.mediaservices.media_util.GenericMediaKey
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.TweetMediaMetadataSource
|
||||
import com.twitter.visibility.features.HasDmcaMediaFeature
|
||||
import com.twitter.visibility.features.MediaGeoRestrictionsAllowList
|
||||
import com.twitter.visibility.features.MediaGeoRestrictionsDenyList
|
||||
|
||||
class TweetMediaMetadataFeatures(
|
||||
mediaMetadataSource: TweetMediaMetadataSource,
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("tweet_media_metadata_features")
|
||||
private[this] val reportedStats = scopedStatsReceiver.scope("dmcaStats")
|
||||
|
||||
def forTweet(
|
||||
tweet: Tweet,
|
||||
mediaKeys: Seq[GenericMediaKey],
|
||||
enableFetchMediaMetadata: Boolean
|
||||
): FeatureMapBuilder => FeatureMapBuilder = { featureMapBuilder =>
|
||||
featureMapBuilder.withFeature(
|
||||
HasDmcaMediaFeature,
|
||||
mediaIsDmca(tweet, mediaKeys, enableFetchMediaMetadata))
|
||||
featureMapBuilder.withFeature(
|
||||
MediaGeoRestrictionsAllowList,
|
||||
allowlist(tweet, mediaKeys, enableFetchMediaMetadata))
|
||||
featureMapBuilder.withFeature(
|
||||
MediaGeoRestrictionsDenyList,
|
||||
denylist(tweet, mediaKeys, enableFetchMediaMetadata))
|
||||
}
|
||||
|
||||
private def mediaIsDmca(
|
||||
tweet: Tweet,
|
||||
mediaKeys: Seq[GenericMediaKey],
|
||||
enableFetchMediaMetadata: Boolean
|
||||
) = getMediaAdditionalMetadata(tweet, mediaKeys, enableFetchMediaMetadata)
|
||||
.map(_.exists(_.restrictions.exists(_.isDmca)))
|
||||
|
||||
private def allowlist(
|
||||
tweet: Tweet,
|
||||
mediaKeys: Seq[GenericMediaKey],
|
||||
enableFetchMediaMetadata: Boolean
|
||||
) = getMediaGeoRestrictions(tweet, mediaKeys, enableFetchMediaMetadata)
|
||||
.map(_.flatMap(_.whitelistedCountryCodes))
|
||||
|
||||
private def denylist(
|
||||
tweet: Tweet,
|
||||
mediaKeys: Seq[GenericMediaKey],
|
||||
enableFetchMediaMetadata: Boolean
|
||||
) = getMediaGeoRestrictions(tweet, mediaKeys, enableFetchMediaMetadata)
|
||||
.map(_.flatMap(_.blacklistedCountryCodes))
|
||||
|
||||
private def getMediaGeoRestrictions(
|
||||
tweet: Tweet,
|
||||
mediaKeys: Seq[GenericMediaKey],
|
||||
enableFetchMediaMetadata: Boolean
|
||||
) = {
|
||||
getMediaAdditionalMetadata(tweet, mediaKeys, enableFetchMediaMetadata)
|
||||
.map(additionalMetadatasSeq => {
|
||||
for {
|
||||
additionalMetadata <- additionalMetadatasSeq
|
||||
restrictions <- additionalMetadata.restrictions
|
||||
geoRestrictions <- restrictions.geoRestrictions
|
||||
} yield {
|
||||
geoRestrictions
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private def getMediaAdditionalMetadata(
|
||||
tweet: Tweet,
|
||||
mediaKeys: Seq[GenericMediaKey],
|
||||
enableFetchMediaMetadata: Boolean
|
||||
): Stitch[Seq[AdditionalMetadata]] = {
|
||||
if (mediaKeys.isEmpty) {
|
||||
reportedStats.counter("empty").incr()
|
||||
Stitch.value(Seq.empty)
|
||||
} else {
|
||||
tweet.media.flatMap { mediaEntities =>
|
||||
val alreadyHydratedMetadata = mediaEntities
|
||||
.filter(_.mediaKey.isDefined)
|
||||
.flatMap(_.additionalMetadata)
|
||||
|
||||
if (alreadyHydratedMetadata.nonEmpty) {
|
||||
Some(alreadyHydratedMetadata)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} match {
|
||||
case Some(additionalMetadata) =>
|
||||
reportedStats.counter("already_hydrated").incr()
|
||||
Stitch.value(additionalMetadata)
|
||||
case None =>
|
||||
Stitch
|
||||
.collect(
|
||||
mediaKeys.map(fetchAdditionalMetadata(tweet.id, _, enableFetchMediaMetadata))
|
||||
).map(maybeMetadatas => {
|
||||
maybeMetadatas
|
||||
.filter(_.isDefined)
|
||||
.map(_.get)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def fetchAdditionalMetadata(
|
||||
tweetId: Long,
|
||||
genericMediaKey: GenericMediaKey,
|
||||
enableFetchMediaMetadata: Boolean
|
||||
): Stitch[Option[AdditionalMetadata]] =
|
||||
if (enableFetchMediaMetadata) {
|
||||
genericMediaKey.toThriftMediaKey() match {
|
||||
case Some(mediaKey) =>
|
||||
reportedStats.counter("request").incr()
|
||||
mediaMetadataSource.fetch(tweetId, mediaKey)
|
||||
case None =>
|
||||
reportedStats.counter("empty_key").incr()
|
||||
Stitch.None
|
||||
}
|
||||
} else {
|
||||
reportedStats.counter("light_request").incr()
|
||||
Stitch.None
|
||||
}
|
||||
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.TweetPerspectiveSource
|
||||
import com.twitter.visibility.features.ViewerReportedTweet
|
||||
|
||||
class TweetPerspectiveFeatures(
|
||||
tweetPerspectiveSource: TweetPerspectiveSource,
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("tweet_perspective_features")
|
||||
private[this] val reportedStats = scopedStatsReceiver.scope("reported")
|
||||
|
||||
def forTweet(
|
||||
tweet: Tweet,
|
||||
viewerId: Option[Long],
|
||||
enableFetchReportedPerspective: Boolean
|
||||
): FeatureMapBuilder => FeatureMapBuilder =
|
||||
_.withFeature(
|
||||
ViewerReportedTweet,
|
||||
tweetIsReported(tweet, viewerId, enableFetchReportedPerspective))
|
||||
|
||||
private[builder] def tweetIsReported(
|
||||
tweet: Tweet,
|
||||
viewerId: Option[Long],
|
||||
enableFetchReportedPerspective: Boolean = true
|
||||
): Stitch[Boolean] = {
|
||||
((tweet.perspective, viewerId) match {
|
||||
case (Some(perspective), _) =>
|
||||
Stitch.value(perspective.reported).onSuccess { _ =>
|
||||
reportedStats.counter("already_hydrated").incr()
|
||||
}
|
||||
case (None, Some(viewerId)) =>
|
||||
if (enableFetchReportedPerspective) {
|
||||
tweetPerspectiveSource.reported(tweet.id, viewerId).onSuccess { _ =>
|
||||
reportedStats.counter("request").incr()
|
||||
}
|
||||
} else {
|
||||
Stitch.False.onSuccess { _ =>
|
||||
reportedStats.counter("light_request").incr()
|
||||
}
|
||||
}
|
||||
case _ =>
|
||||
Stitch.False.onSuccess { _ =>
|
||||
reportedStats.counter("empty").incr()
|
||||
}
|
||||
}).onSuccess { _ =>
|
||||
reportedStats.counter("").incr()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.spam.rtf.thriftscala.SafetyLabelType
|
||||
import com.twitter.spam.rtf.thriftscala.SafetyLabelType.ExperimentalNudge
|
||||
import com.twitter.spam.rtf.thriftscala.SafetyLabelType.SemanticCoreMisinformation
|
||||
import com.twitter.spam.rtf.thriftscala.SafetyLabelType.UnsafeUrl
|
||||
import com.twitter.visibility.common.LocalizedNudgeSource
|
||||
import com.twitter.visibility.common.actions.TweetVisibilityNudgeReason
|
||||
import com.twitter.visibility.common.actions.TweetVisibilityNudgeReason.ExperimentalNudgeSafetyLabelReason
|
||||
import com.twitter.visibility.common.actions.TweetVisibilityNudgeReason.SemanticCoreMisinformationLabelReason
|
||||
import com.twitter.visibility.common.actions.TweetVisibilityNudgeReason.UnsafeURLLabelReason
|
||||
import com.twitter.visibility.rules.LocalizedNudge
|
||||
|
||||
class TweetVisibilityNudgeSourceWrapper(localizedNudgeSource: LocalizedNudgeSource) {
|
||||
|
||||
def getLocalizedNudge(
|
||||
reason: TweetVisibilityNudgeReason,
|
||||
languageCode: String,
|
||||
countryCode: Option[String]
|
||||
): Option[LocalizedNudge] =
|
||||
reason match {
|
||||
case ExperimentalNudgeSafetyLabelReason =>
|
||||
fetchNudge(ExperimentalNudge, languageCode, countryCode)
|
||||
case SemanticCoreMisinformationLabelReason =>
|
||||
fetchNudge(SemanticCoreMisinformation, languageCode, countryCode)
|
||||
case UnsafeURLLabelReason =>
|
||||
fetchNudge(UnsafeUrl, languageCode, countryCode)
|
||||
}
|
||||
|
||||
private def fetchNudge(
|
||||
safetyLabel: SafetyLabelType,
|
||||
languageCode: String,
|
||||
countryCode: Option[String]
|
||||
): Option[LocalizedNudge] = {
|
||||
localizedNudgeSource
|
||||
.fetch(safetyLabel, languageCode, countryCode)
|
||||
.map(LocalizedNudge.fromStratoThrift)
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package com.twitter.visibility.builder.tweets
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.notificationservice.model.notification._
|
||||
import com.twitter.servo.util.Gate
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tweetypie.thriftscala.SettingsUnmentions
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.TweetSource
|
||||
import com.twitter.visibility.features.NotificationIsOnUnmentionedViewer
|
||||
|
||||
object UnmentionNotificationFeatures {
|
||||
def ForNonUnmentionNotificationFeatures: FeatureMapBuilder => FeatureMapBuilder = {
|
||||
_.withConstantFeature(NotificationIsOnUnmentionedViewer, false)
|
||||
}
|
||||
}
|
||||
|
||||
class UnmentionNotificationFeatures(
|
||||
tweetSource: TweetSource,
|
||||
enableUnmentionHydration: Gate[Long],
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver =
|
||||
statsReceiver.scope("unmention_notification_features")
|
||||
private[this] val requestsCounter = scopedStatsReceiver.counter("requests")
|
||||
private[this] val hydrationsCounter = scopedStatsReceiver.counter("hydrations")
|
||||
private[this] val notificationsUnmentionUserCounter =
|
||||
scopedStatsReceiver
|
||||
.scope(NotificationIsOnUnmentionedViewer.name).counter("unmentioned_users")
|
||||
|
||||
def forNotification(notification: Notification): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requestsCounter.incr()
|
||||
|
||||
val isUnmentionNotification = tweetId(notification) match {
|
||||
case Some(tweetId) if enableUnmentionHydration(notification.target) =>
|
||||
hydrationsCounter.incr()
|
||||
tweetSource
|
||||
.getTweet(tweetId)
|
||||
.map {
|
||||
case Some(tweet) =>
|
||||
tweet.settingsUnmentions match {
|
||||
case Some(SettingsUnmentions(Some(unmentionedUserIds))) =>
|
||||
if (unmentionedUserIds.contains(notification.target)) {
|
||||
notificationsUnmentionUserCounter.incr()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
case _ => false
|
||||
}
|
||||
case _ => false
|
||||
}
|
||||
case _ => Stitch.False
|
||||
}
|
||||
_.withFeature(NotificationIsOnUnmentionedViewer, isUnmentionNotification)
|
||||
}
|
||||
|
||||
private[this] def tweetId(notification: Notification): Option[Long] = {
|
||||
notification match {
|
||||
case n: MentionNotification => Some(n.mentioningTweetId)
|
||||
case n: FavoritedMentioningTweetNotification => Some(n.mentioningTweetId)
|
||||
case n: FavoritedReplyToYourTweetNotification => Some(n.replyTweetId)
|
||||
case n: MentionQuoteNotification => Some(n.mentioningTweetId)
|
||||
case n: ReactionMentioningTweetNotification => Some(n.mentioningTweetId)
|
||||
case n: ReplyNotification => Some(n.replyingTweetId)
|
||||
case n: RetweetedMentionNotification => Some(n.mentioningTweetId)
|
||||
case n: RetweetedReplyToYourTweetNotification => Some(n.replyTweetId)
|
||||
case n: ReplyToConversationNotification => Some(n.replyingTweetId)
|
||||
case n: ReactionReplyToYourTweetNotification => Some(n.replyTweetId)
|
||||
case _ => None
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package com.twitter.visibility.builder.users
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.gizmoduck.thriftscala.User
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.UserDeviceSource
|
||||
import com.twitter.visibility.features.AuthorHasConfirmedEmail
|
||||
import com.twitter.visibility.features.AuthorHasVerifiedPhone
|
||||
|
||||
class AuthorDeviceFeatures(userDeviceSource: UserDeviceSource, statsReceiver: StatsReceiver) {
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("author_device_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val authorHasConfirmedEmailRequests =
|
||||
scopedStatsReceiver.scope(AuthorHasConfirmedEmail.name).counter("requests")
|
||||
private[this] val authorHasVerifiedPhoneRequests =
|
||||
scopedStatsReceiver.scope(AuthorHasVerifiedPhone.name).counter("requests")
|
||||
|
||||
def forAuthor(author: User): FeatureMapBuilder => FeatureMapBuilder = forAuthorId(author.id)
|
||||
|
||||
def forAuthorId(authorId: Long): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
|
||||
_.withFeature(AuthorHasConfirmedEmail, authorHasConfirmedEmail(authorId))
|
||||
.withFeature(AuthorHasVerifiedPhone, authorHasVerifiedPhone(authorId))
|
||||
}
|
||||
|
||||
def authorHasConfirmedEmail(authorId: Long): Stitch[Boolean] = {
|
||||
authorHasConfirmedEmailRequests.incr()
|
||||
userDeviceSource.hasConfirmedEmail(authorId)
|
||||
}
|
||||
|
||||
def authorHasVerifiedPhone(authorId: Long): Stitch[Boolean] = {
|
||||
authorHasVerifiedPhoneRequests.incr()
|
||||
userDeviceSource.hasConfirmedPhone(authorId)
|
||||
}
|
||||
}
|
|
@ -1,221 +0,0 @@
|
|||
package com.twitter.visibility.builder.users
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.gizmoduck.thriftscala.Label
|
||||
import com.twitter.gizmoduck.thriftscala.Labels
|
||||
import com.twitter.gizmoduck.thriftscala.Profile
|
||||
import com.twitter.gizmoduck.thriftscala.Safety
|
||||
import com.twitter.gizmoduck.thriftscala.User
|
||||
import com.twitter.stitch.NotFound
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tseng.withholding.thriftscala.TakedownReason
|
||||
import com.twitter.util.Duration
|
||||
import com.twitter.util.Time
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.UserSource
|
||||
import com.twitter.visibility.features._
|
||||
|
||||
class AuthorFeatures(userSource: UserSource, statsReceiver: StatsReceiver) {
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("author_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val authorUserLabels =
|
||||
scopedStatsReceiver.scope(AuthorUserLabels.name).counter("requests")
|
||||
private[this] val authorIsSuspended =
|
||||
scopedStatsReceiver.scope(AuthorIsSuspended.name).counter("requests")
|
||||
private[this] val authorIsProtected =
|
||||
scopedStatsReceiver.scope(AuthorIsProtected.name).counter("requests")
|
||||
private[this] val authorIsDeactivated =
|
||||
scopedStatsReceiver.scope(AuthorIsDeactivated.name).counter("requests")
|
||||
private[this] val authorIsErased =
|
||||
scopedStatsReceiver.scope(AuthorIsErased.name).counter("requests")
|
||||
private[this] val authorIsOffboarded =
|
||||
scopedStatsReceiver.scope(AuthorIsOffboarded.name).counter("requests")
|
||||
private[this] val authorIsNsfwUser =
|
||||
scopedStatsReceiver.scope(AuthorIsNsfwUser.name).counter("requests")
|
||||
private[this] val authorIsNsfwAdmin =
|
||||
scopedStatsReceiver.scope(AuthorIsNsfwAdmin.name).counter("requests")
|
||||
private[this] val authorTakedownReasons =
|
||||
scopedStatsReceiver.scope(AuthorTakedownReasons.name).counter("requests")
|
||||
private[this] val authorHasDefaultProfileImage =
|
||||
scopedStatsReceiver.scope(AuthorHasDefaultProfileImage.name).counter("requests")
|
||||
private[this] val authorAccountAge =
|
||||
scopedStatsReceiver.scope(AuthorAccountAge.name).counter("requests")
|
||||
private[this] val authorIsVerified =
|
||||
scopedStatsReceiver.scope(AuthorIsVerified.name).counter("requests")
|
||||
private[this] val authorScreenName =
|
||||
scopedStatsReceiver.scope(AuthorScreenName.name).counter("requests")
|
||||
private[this] val authorIsBlueVerified =
|
||||
scopedStatsReceiver.scope(AuthorIsBlueVerified.name).counter("requests")
|
||||
|
||||
def forAuthor(author: User): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
|
||||
_.withConstantFeature(AuthorId, Set(author.id))
|
||||
.withConstantFeature(AuthorUserLabels, authorUserLabels(author))
|
||||
.withConstantFeature(AuthorIsProtected, authorIsProtected(author))
|
||||
.withConstantFeature(AuthorIsSuspended, authorIsSuspended(author))
|
||||
.withConstantFeature(AuthorIsDeactivated, authorIsDeactivated(author))
|
||||
.withConstantFeature(AuthorIsErased, authorIsErased(author))
|
||||
.withConstantFeature(AuthorIsOffboarded, authorIsOffboarded(author))
|
||||
.withConstantFeature(AuthorTakedownReasons, authorTakedownReasons(author))
|
||||
.withConstantFeature(AuthorHasDefaultProfileImage, authorHasDefaultProfileImage(author))
|
||||
.withConstantFeature(AuthorAccountAge, authorAccountAge(author))
|
||||
.withConstantFeature(AuthorIsNsfwUser, authorIsNsfwUser(author))
|
||||
.withConstantFeature(AuthorIsNsfwAdmin, authorIsNsfwAdmin(author))
|
||||
.withConstantFeature(AuthorIsVerified, authorIsVerified(author))
|
||||
.withConstantFeature(AuthorScreenName, authorScreenName(author))
|
||||
.withConstantFeature(AuthorIsBlueVerified, authorIsBlueVerified(author))
|
||||
}
|
||||
|
||||
def forAuthorNoDefaults(author: User): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
|
||||
_.withConstantFeature(AuthorId, Set(author.id))
|
||||
.withConstantFeature(AuthorUserLabels, authorUserLabelsOpt(author))
|
||||
.withConstantFeature(AuthorIsProtected, authorIsProtectedOpt(author))
|
||||
.withConstantFeature(AuthorIsSuspended, authorIsSuspendedOpt(author))
|
||||
.withConstantFeature(AuthorIsDeactivated, authorIsDeactivatedOpt(author))
|
||||
.withConstantFeature(AuthorIsErased, authorIsErasedOpt(author))
|
||||
.withConstantFeature(AuthorIsOffboarded, authorIsOffboarded(author))
|
||||
.withConstantFeature(AuthorTakedownReasons, authorTakedownReasons(author))
|
||||
.withConstantFeature(AuthorHasDefaultProfileImage, authorHasDefaultProfileImage(author))
|
||||
.withConstantFeature(AuthorAccountAge, authorAccountAge(author))
|
||||
.withConstantFeature(AuthorIsNsfwUser, authorIsNsfwUserOpt(author))
|
||||
.withConstantFeature(AuthorIsNsfwAdmin, authorIsNsfwAdminOpt(author))
|
||||
.withConstantFeature(AuthorIsVerified, authorIsVerifiedOpt(author))
|
||||
.withConstantFeature(AuthorScreenName, authorScreenName(author))
|
||||
.withConstantFeature(AuthorIsBlueVerified, authorIsBlueVerified(author))
|
||||
}
|
||||
|
||||
def forAuthorId(authorId: Long): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
|
||||
_.withConstantFeature(AuthorId, Set(authorId))
|
||||
.withFeature(AuthorUserLabels, authorUserLabels(authorId))
|
||||
.withFeature(AuthorIsProtected, authorIsProtected(authorId))
|
||||
.withFeature(AuthorIsSuspended, authorIsSuspended(authorId))
|
||||
.withFeature(AuthorIsDeactivated, authorIsDeactivated(authorId))
|
||||
.withFeature(AuthorIsErased, authorIsErased(authorId))
|
||||
.withFeature(AuthorIsOffboarded, authorIsOffboarded(authorId))
|
||||
.withFeature(AuthorTakedownReasons, authorTakedownReasons(authorId))
|
||||
.withFeature(AuthorHasDefaultProfileImage, authorHasDefaultProfileImage(authorId))
|
||||
.withFeature(AuthorAccountAge, authorAccountAge(authorId))
|
||||
.withFeature(AuthorIsNsfwUser, authorIsNsfwUser(authorId))
|
||||
.withFeature(AuthorIsNsfwAdmin, authorIsNsfwAdmin(authorId))
|
||||
.withFeature(AuthorIsVerified, authorIsVerified(authorId))
|
||||
.withFeature(AuthorScreenName, authorScreenName(authorId))
|
||||
.withFeature(AuthorIsBlueVerified, authorIsBlueVerified(authorId))
|
||||
}
|
||||
|
||||
def forNoAuthor(): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
_.withConstantFeature(AuthorId, Set.empty[Long])
|
||||
.withConstantFeature(AuthorUserLabels, Seq.empty)
|
||||
.withConstantFeature(AuthorIsProtected, false)
|
||||
.withConstantFeature(AuthorIsSuspended, false)
|
||||
.withConstantFeature(AuthorIsDeactivated, false)
|
||||
.withConstantFeature(AuthorIsErased, false)
|
||||
.withConstantFeature(AuthorIsOffboarded, false)
|
||||
.withConstantFeature(AuthorTakedownReasons, Seq.empty)
|
||||
.withConstantFeature(AuthorHasDefaultProfileImage, false)
|
||||
.withConstantFeature(AuthorAccountAge, Duration.Zero)
|
||||
.withConstantFeature(AuthorIsNsfwUser, false)
|
||||
.withConstantFeature(AuthorIsNsfwAdmin, false)
|
||||
.withConstantFeature(AuthorIsVerified, false)
|
||||
.withConstantFeature(AuthorIsBlueVerified, false)
|
||||
}
|
||||
|
||||
def authorUserLabels(author: User): Seq[Label] =
|
||||
authorUserLabels(author.labels)
|
||||
|
||||
def authorIsSuspended(authorId: Long): Stitch[Boolean] =
|
||||
userSource.getSafety(authorId).map(safety => authorIsSuspended(Some(safety)))
|
||||
|
||||
def authorIsSuspendedOpt(author: User): Option[Boolean] = {
|
||||
authorIsSuspended.incr()
|
||||
author.safety.map(_.suspended)
|
||||
}
|
||||
|
||||
private def authorIsSuspended(safety: Option[Safety]): Boolean = {
|
||||
authorIsSuspended.incr()
|
||||
safety.exists(_.suspended)
|
||||
}
|
||||
|
||||
def authorIsProtected(author: User): Boolean =
|
||||
authorIsProtected(author.safety)
|
||||
|
||||
def authorIsDeactivated(authorId: Long): Stitch[Boolean] =
|
||||
userSource.getSafety(authorId).map(safety => authorIsDeactivated(Some(safety)))
|
||||
|
||||
def authorIsDeactivatedOpt(author: User): Option[Boolean] = {
|
||||
authorIsDeactivated.incr()
|
||||
author.safety.map(_.deactivated)
|
||||
}
|
||||
|
||||
private def authorIsDeactivated(safety: Option[Safety]): Boolean = {
|
||||
authorIsDeactivated.incr()
|
||||
safety.exists(_.deactivated)
|
||||
}
|
||||
|
||||
def authorIsErased(author: User): Boolean = {
|
||||
authorIsErased.incr()
|
||||
author.safety.exists(_.erased)
|
||||
}
|
||||
|
||||
def authorIsOffboarded(authorId: Long): Stitch[Boolean] = {
|
||||
userSource.getSafety(authorId).map(safety => authorIsOffboarded(Some(safety)))
|
||||
}
|
||||
|
||||
def authorIsNsfwUser(author: User): Boolean = {
|
||||
authorIsNsfwUser(author.safety)
|
||||
}
|
||||
|
||||
def authorIsNsfwUser(authorId: Long): Stitch[Boolean] = {
|
||||
userSource.getSafety(authorId).map(safety => authorIsNsfwUser(Some(safety)))
|
||||
}
|
||||
|
||||
def authorIsNsfwUser(safety: Option[Safety]): Boolean = {
|
||||
authorIsNsfwUser.incr()
|
||||
safety.exists(_.nsfwUser)
|
||||
}
|
||||
|
||||
def authorIsNsfwAdminOpt(author: User): Option[Boolean] = {
|
||||
authorIsNsfwAdmin.incr()
|
||||
author.safety.map(_.nsfwAdmin)
|
||||
}
|
||||
|
||||
def authorTakedownReasons(authorId: Long): Stitch[Seq[TakedownReason]] = {
|
||||
authorTakedownReasons.incr()
|
||||
userSource.getTakedownReasons(authorId)
|
||||
}
|
||||
|
||||
def authorHasDefaultProfileImage(authorId: Long): Stitch[Boolean] =
|
||||
userSource.getProfile(authorId).map(profile => authorHasDefaultProfileImage(Some(profile)))
|
||||
|
||||
def authorAccountAge(authorId: Long): Stitch[Duration] =
|
||||
userSource.getCreatedAtMsec(authorId).map(authorAccountAgeFromTimestamp)
|
||||
|
||||
def authorIsVerified(authorId: Long): Stitch[Boolean] =
|
||||
userSource.getSafety(authorId).map(safety => authorIsVerified(Some(safety)))
|
||||
|
||||
def authorIsVerifiedOpt(author: User): Option[Boolean] = {
|
||||
authorIsVerified.incr()
|
||||
author.safety.map(_.verified)
|
||||
}
|
||||
|
||||
private def authorIsVerified(safety: Option[Safety]): Boolean = {
|
||||
authorIsVerified.incr()
|
||||
safety.exists(_.verified)
|
||||
}
|
||||
|
||||
def authorScreenName(author: User): Option[String] = {
|
||||
authorScreenName.incr()
|
||||
author.profile.map(_.screenName)
|
||||
}
|
||||
|
||||
def authorScreenName(authorId: Long): Stitch[String] = {
|
||||
authorScreenName.incr()
|
||||
userSource.getProfile(authorId).map(profile => profile.screenName)
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"src/scala/com/twitter/search/blender/services/strato",
|
||||
"src/thrift/com/twitter/content-health/sensitivemediasettings:sensitivemediasettings-scala",
|
||||
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
|
||||
"stitch/stitch-core",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common/user_result",
|
||||
"visibility/common/src/main/thrift/com/twitter/visibility:action-scala",
|
||||
"visibility/lib/src/main/resources/config",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/features",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/blender",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/search",
|
||||
"visibility/lib/src/main/thrift/com/twitter/visibility/context:vf-context-scala",
|
||||
],
|
||||
)
|
|
@ -1,52 +0,0 @@
|
|||
package com.twitter.visibility.builder.users
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.features.AuthorBlocksOuterAuthor
|
||||
import com.twitter.visibility.features.OuterAuthorFollowsAuthor
|
||||
import com.twitter.visibility.features.OuterAuthorId
|
||||
import com.twitter.visibility.features.OuterAuthorIsInnerAuthor
|
||||
|
||||
class QuotedTweetFeatures(
|
||||
relationshipFeatures: RelationshipFeatures,
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("quoted_tweet_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val outerAuthorIdStat =
|
||||
scopedStatsReceiver.scope(OuterAuthorId.name).counter("requests")
|
||||
private[this] val authorBlocksOuterAuthor =
|
||||
scopedStatsReceiver.scope(AuthorBlocksOuterAuthor.name).counter("requests")
|
||||
private[this] val outerAuthorFollowsAuthor =
|
||||
scopedStatsReceiver.scope(OuterAuthorFollowsAuthor.name).counter("requests")
|
||||
private[this] val outerAuthorIsInnerAuthor =
|
||||
scopedStatsReceiver.scope(OuterAuthorIsInnerAuthor.name).counter("requests")
|
||||
|
||||
def forOuterAuthor(
|
||||
outerAuthorId: Long,
|
||||
innerAuthorId: Long
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
|
||||
outerAuthorIdStat.incr()
|
||||
authorBlocksOuterAuthor.incr()
|
||||
outerAuthorFollowsAuthor.incr()
|
||||
outerAuthorIsInnerAuthor.incr()
|
||||
|
||||
val viewer = Some(outerAuthorId)
|
||||
|
||||
_.withConstantFeature(OuterAuthorId, outerAuthorId)
|
||||
.withFeature(
|
||||
AuthorBlocksOuterAuthor,
|
||||
relationshipFeatures.authorBlocksViewer(innerAuthorId, viewer))
|
||||
.withFeature(
|
||||
OuterAuthorFollowsAuthor,
|
||||
relationshipFeatures.viewerFollowsAuthor(innerAuthorId, viewer))
|
||||
.withConstantFeature(
|
||||
OuterAuthorIsInnerAuthor,
|
||||
innerAuthorId == outerAuthorId
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
package com.twitter.visibility.builder.users
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.gizmoduck.thriftscala.User
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.UserId
|
||||
import com.twitter.visibility.common.UserRelationshipSource
|
||||
import com.twitter.visibility.features._
|
||||
|
||||
class RelationshipFeatures(
|
||||
userRelationshipSource: UserRelationshipSource,
|
||||
statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("relationship_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val authorFollowsViewer =
|
||||
scopedStatsReceiver.scope(AuthorFollowsViewer.name).counter("requests")
|
||||
private[this] val viewerFollowsAuthor =
|
||||
scopedStatsReceiver.scope(ViewerFollowsAuthor.name).counter("requests")
|
||||
private[this] val authorBlocksViewer =
|
||||
scopedStatsReceiver.scope(AuthorBlocksViewer.name).counter("requests")
|
||||
private[this] val viewerBlocksAuthor =
|
||||
scopedStatsReceiver.scope(ViewerBlocksAuthor.name).counter("requests")
|
||||
private[this] val authorMutesViewer =
|
||||
scopedStatsReceiver.scope(AuthorMutesViewer.name).counter("requests")
|
||||
private[this] val viewerMutesAuthor =
|
||||
scopedStatsReceiver.scope(ViewerMutesAuthor.name).counter("requests")
|
||||
private[this] val authorHasReportedViewer =
|
||||
scopedStatsReceiver.scope(AuthorReportsViewerAsSpam.name).counter("requests")
|
||||
private[this] val viewerHasReportedAuthor =
|
||||
scopedStatsReceiver.scope(ViewerReportsAuthorAsSpam.name).counter("requests")
|
||||
private[this] val viewerMutesRetweetsFromAuthor =
|
||||
scopedStatsReceiver.scope(ViewerMutesRetweetsFromAuthor.name).counter("requests")
|
||||
|
||||
def forAuthorId(
|
||||
authorId: Long,
|
||||
viewerId: Option[Long]
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
|
||||
_.withFeature(AuthorFollowsViewer, authorFollowsViewer(authorId, viewerId))
|
||||
.withFeature(ViewerFollowsAuthor, viewerFollowsAuthor(authorId, viewerId))
|
||||
.withFeature(AuthorBlocksViewer, authorBlocksViewer(authorId, viewerId))
|
||||
.withFeature(ViewerBlocksAuthor, viewerBlocksAuthor(authorId, viewerId))
|
||||
.withFeature(AuthorMutesViewer, authorMutesViewer(authorId, viewerId))
|
||||
.withFeature(ViewerMutesAuthor, viewerMutesAuthor(authorId, viewerId))
|
||||
.withFeature(AuthorReportsViewerAsSpam, authorHasReportedViewer(authorId, viewerId))
|
||||
.withFeature(ViewerReportsAuthorAsSpam, viewerHasReportedAuthor(authorId, viewerId))
|
||||
.withFeature(ViewerMutesRetweetsFromAuthor, viewerMutesRetweetsFromAuthor(authorId, viewerId))
|
||||
}
|
||||
|
||||
def forNoAuthor(): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
_.withConstantFeature(AuthorFollowsViewer, false)
|
||||
.withConstantFeature(ViewerFollowsAuthor, false)
|
||||
.withConstantFeature(AuthorBlocksViewer, false)
|
||||
.withConstantFeature(ViewerBlocksAuthor, false)
|
||||
.withConstantFeature(AuthorMutesViewer, false)
|
||||
.withConstantFeature(ViewerMutesAuthor, false)
|
||||
.withConstantFeature(AuthorReportsViewerAsSpam, false)
|
||||
.withConstantFeature(ViewerReportsAuthorAsSpam, false)
|
||||
.withConstantFeature(ViewerMutesRetweetsFromAuthor, false)
|
||||
}
|
||||
|
||||
def forAuthor(author: User, viewerId: Option[Long]): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
|
||||
|
||||
_.withFeature(AuthorFollowsViewer, authorFollowsViewer(author, viewerId))
|
||||
.withFeature(ViewerFollowsAuthor, viewerFollowsAuthor(author, viewerId))
|
||||
.withFeature(AuthorBlocksViewer, authorBlocksViewer(author, viewerId))
|
||||
.withFeature(ViewerBlocksAuthor, viewerBlocksAuthor(author, viewerId))
|
||||
.withFeature(AuthorMutesViewer, authorMutesViewer(author, viewerId))
|
||||
.withFeature(ViewerMutesAuthor, viewerMutesAuthor(author, viewerId))
|
||||
.withFeature(AuthorReportsViewerAsSpam, authorHasReportedViewer(author.id, viewerId))
|
||||
.withFeature(ViewerReportsAuthorAsSpam, viewerHasReportedAuthor(author.id, viewerId))
|
||||
.withFeature(ViewerMutesRetweetsFromAuthor, viewerMutesRetweetsFromAuthor(author, viewerId))
|
||||
}
|
||||
|
||||
def viewerFollowsAuthor(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(authorId, viewerId, userRelationshipSource.follows, viewerFollowsAuthor)
|
||||
|
||||
def viewerFollowsAuthor(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(
|
||||
author,
|
||||
viewerId,
|
||||
p => p.following,
|
||||
userRelationshipSource.follows,
|
||||
viewerFollowsAuthor)
|
||||
|
||||
def authorFollowsViewer(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
AuthorVerbsViewer(authorId, viewerId, userRelationshipSource.follows, authorFollowsViewer)
|
||||
|
||||
def authorFollowsViewer(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
AuthorVerbsViewer(
|
||||
author,
|
||||
viewerId,
|
||||
p => p.followedBy,
|
||||
userRelationshipSource.follows,
|
||||
authorFollowsViewer)
|
||||
|
||||
def viewerBlocksAuthor(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(authorId, viewerId, userRelationshipSource.blocks, viewerBlocksAuthor)
|
||||
|
||||
def viewerBlocksAuthor(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(
|
||||
author,
|
||||
viewerId,
|
||||
p => p.blocking,
|
||||
userRelationshipSource.blocks,
|
||||
viewerBlocksAuthor)
|
||||
|
||||
def authorBlocksViewer(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(authorId, viewerId, userRelationshipSource.blockedBy, authorBlocksViewer)
|
||||
|
||||
def authorBlocksViewer(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(
|
||||
author,
|
||||
viewerId,
|
||||
p => p.blockedBy,
|
||||
userRelationshipSource.blockedBy,
|
||||
authorBlocksViewer)
|
||||
|
||||
def viewerMutesAuthor(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(authorId, viewerId, userRelationshipSource.mutes, viewerMutesAuthor)
|
||||
|
||||
def viewerMutesAuthor(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(
|
||||
author,
|
||||
viewerId,
|
||||
p => p.muting,
|
||||
userRelationshipSource.mutes,
|
||||
viewerMutesAuthor)
|
||||
|
||||
def authorMutesViewer(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(authorId, viewerId, userRelationshipSource.mutedBy, authorMutesViewer)
|
||||
|
||||
def authorMutesViewer(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(
|
||||
author,
|
||||
viewerId,
|
||||
p => p.mutedBy,
|
||||
userRelationshipSource.mutedBy,
|
||||
authorMutesViewer)
|
||||
|
||||
def viewerHasReportedAuthor(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(
|
||||
authorId,
|
||||
viewerId,
|
||||
userRelationshipSource.reportsAsSpam,
|
||||
viewerHasReportedAuthor)
|
||||
|
||||
def authorHasReportedViewer(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(
|
||||
authorId,
|
||||
viewerId,
|
||||
userRelationshipSource.reportedAsSpamBy,
|
||||
authorHasReportedViewer)
|
||||
|
||||
def viewerMutesRetweetsFromAuthor(authorId: UserId, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(
|
||||
authorId,
|
||||
viewerId,
|
||||
userRelationshipSource.noRetweetsFrom,
|
||||
viewerMutesRetweetsFromAuthor)
|
||||
|
||||
def viewerMutesRetweetsFromAuthor(author: User, viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
ViewerVerbsAuthor(
|
||||
author,
|
||||
viewerId,
|
||||
p => p.noRetweetsFrom,
|
||||
userRelationshipSource.noRetweetsFrom,
|
||||
viewerMutesRetweetsFromAuthor)
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package com.twitter.visibility.builder.users
|
||||
|
||||
import com.twitter.finagle.stats.Counter
|
||||
import com.twitter.gizmoduck.thriftscala.Perspective
|
||||
import com.twitter.gizmoduck.thriftscala.User
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.common.UserId
|
||||
|
||||
case object ViewerVerbsAuthor {
|
||||
def apply(
|
||||
authorId: UserId,
|
||||
viewerIdOpt: Option[UserId],
|
||||
relationship: (UserId, UserId) => Stitch[Boolean],
|
||||
relationshipCounter: Counter
|
||||
): Stitch[Boolean] = {
|
||||
relationshipCounter.incr()
|
||||
|
||||
viewerIdOpt match {
|
||||
case Some(viewerId) => relationship(viewerId, authorId)
|
||||
case _ => Stitch.False
|
||||
}
|
||||
}
|
||||
|
||||
def apply(
|
||||
author: User,
|
||||
viewerId: Option[UserId],
|
||||
checkPerspective: Perspective => Option[Boolean],
|
||||
relationship: (UserId, UserId) => Stitch[Boolean],
|
||||
relationshipCounter: Counter
|
||||
): Stitch[Boolean] = {
|
||||
author.perspective match {
|
||||
case Some(perspective) =>
|
||||
checkPerspective(perspective) match {
|
||||
case Some(status) =>
|
||||
relationshipCounter.incr()
|
||||
Stitch.value(status)
|
||||
case None =>
|
||||
ViewerVerbsAuthor(author.id, viewerId, relationship, relationshipCounter)
|
||||
}
|
||||
case None => ViewerVerbsAuthor(author.id, viewerId, relationship, relationshipCounter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case object AuthorVerbsViewer {
|
||||
|
||||
def apply(
|
||||
authorId: UserId,
|
||||
viewerIdOpt: Option[UserId],
|
||||
relationship: (UserId, UserId) => Stitch[Boolean],
|
||||
relationshipCounter: Counter
|
||||
): Stitch[Boolean] = {
|
||||
relationshipCounter.incr()
|
||||
|
||||
viewerIdOpt match {
|
||||
case Some(viewerId) => relationship(authorId, viewerId)
|
||||
case _ => Stitch.False
|
||||
}
|
||||
}
|
||||
def apply(
|
||||
author: User,
|
||||
viewerId: Option[UserId],
|
||||
checkPerspective: Perspective => Option[Boolean],
|
||||
relationship: (UserId, UserId) => Stitch[Boolean],
|
||||
relationshipCounter: Counter
|
||||
): Stitch[Boolean] = {
|
||||
author.perspective match {
|
||||
case Some(perspective) =>
|
||||
checkPerspective(perspective) match {
|
||||
case Some(status) =>
|
||||
relationshipCounter.incr()
|
||||
Stitch.value(status)
|
||||
case None =>
|
||||
AuthorVerbsViewer(author.id, viewerId, relationship, relationshipCounter)
|
||||
}
|
||||
case None => AuthorVerbsViewer(author.id, viewerId, relationship, relationshipCounter)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package com.twitter.visibility.builder.users
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.features._
|
||||
import com.twitter.visibility.context.thriftscala.SearchContext
|
||||
|
||||
class SearchFeatures(statsReceiver: StatsReceiver) {
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("search_features")
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
private[this] val rawQueryCounter =
|
||||
scopedStatsReceiver.scope(RawQuery.name).counter("requests")
|
||||
|
||||
def forSearchContext(
|
||||
searchContext: Option[SearchContext]
|
||||
): FeatureMapBuilder => FeatureMapBuilder = { builder =>
|
||||
requests.incr()
|
||||
searchContext match {
|
||||
case Some(context: SearchContext) =>
|
||||
rawQueryCounter.incr()
|
||||
builder
|
||||
.withConstantFeature(RawQuery, context.rawQuery)
|
||||
case _ => builder
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
package com.twitter.visibility.builder.users
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.user_result.UserVisibilityResultHelper
|
||||
import com.twitter.visibility.features.AuthorBlocksViewer
|
||||
import com.twitter.visibility.features.AuthorIsDeactivated
|
||||
import com.twitter.visibility.features.AuthorIsErased
|
||||
import com.twitter.visibility.features.AuthorIsOffboarded
|
||||
import com.twitter.visibility.features.AuthorIsProtected
|
||||
import com.twitter.visibility.features.AuthorIsSuspended
|
||||
import com.twitter.visibility.features.AuthorIsUnavailable
|
||||
import com.twitter.visibility.features.ViewerBlocksAuthor
|
||||
import com.twitter.visibility.features.ViewerMutesAuthor
|
||||
import com.twitter.visibility.models.UserUnavailableStateEnum
|
||||
|
||||
case class UserUnavailableFeatures(statsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("user_unavailable_features")
|
||||
private[this] val suspendedAuthorStats = scopedStatsReceiver.scope("suspended_author")
|
||||
private[this] val deactivatedAuthorStats = scopedStatsReceiver.scope("deactivated_author")
|
||||
private[this] val offboardedAuthorStats = scopedStatsReceiver.scope("offboarded_author")
|
||||
private[this] val erasedAuthorStats = scopedStatsReceiver.scope("erased_author")
|
||||
private[this] val protectedAuthorStats = scopedStatsReceiver.scope("protected_author")
|
||||
private[this] val authorBlocksViewerStats = scopedStatsReceiver.scope("author_blocks_viewer")
|
||||
private[this] val viewerBlocksAuthorStats = scopedStatsReceiver.scope("viewer_blocks_author")
|
||||
private[this] val viewerMutesAuthorStats = scopedStatsReceiver.scope("viewer_mutes_author")
|
||||
private[this] val unavailableStats = scopedStatsReceiver.scope("unavailable")
|
||||
|
||||
def forState(state: UserUnavailableStateEnum): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
builder =>
|
||||
builder
|
||||
.withConstantFeature(AuthorIsSuspended, isSuspended(state))
|
||||
.withConstantFeature(AuthorIsDeactivated, isDeactivated(state))
|
||||
.withConstantFeature(AuthorIsOffboarded, isOffboarded(state))
|
||||
.withConstantFeature(AuthorIsErased, isErased(state))
|
||||
.withConstantFeature(AuthorIsProtected, isProtected(state))
|
||||
.withConstantFeature(AuthorBlocksViewer, authorBlocksViewer(state))
|
||||
.withConstantFeature(ViewerBlocksAuthor, viewerBlocksAuthor(state))
|
||||
.withConstantFeature(ViewerMutesAuthor, viewerMutesAuthor(state))
|
||||
.withConstantFeature(AuthorIsUnavailable, isUnavailable(state))
|
||||
}
|
||||
|
||||
private[this] def isSuspended(state: UserUnavailableStateEnum): Boolean =
|
||||
state match {
|
||||
case UserUnavailableStateEnum.Suspended =>
|
||||
suspendedAuthorStats.counter().incr()
|
||||
true
|
||||
case UserUnavailableStateEnum.Filtered(result)
|
||||
if UserVisibilityResultHelper.isDropSuspendedAuthor(result) =>
|
||||
suspendedAuthorStats.counter().incr()
|
||||
suspendedAuthorStats.counter("filtered").incr()
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private[this] def isDeactivated(state: UserUnavailableStateEnum): Boolean =
|
||||
state match {
|
||||
case UserUnavailableStateEnum.Deactivated =>
|
||||
deactivatedAuthorStats.counter().incr()
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private[this] def isOffboarded(state: UserUnavailableStateEnum): Boolean =
|
||||
state match {
|
||||
case UserUnavailableStateEnum.Offboarded =>
|
||||
offboardedAuthorStats.counter().incr()
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private[this] def isErased(state: UserUnavailableStateEnum): Boolean =
|
||||
state match {
|
||||
case UserUnavailableStateEnum.Erased =>
|
||||
erasedAuthorStats.counter().incr()
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private[this] def isProtected(state: UserUnavailableStateEnum): Boolean =
|
||||
state match {
|
||||
case UserUnavailableStateEnum.Protected =>
|
||||
protectedAuthorStats.counter().incr()
|
||||
true
|
||||
case UserUnavailableStateEnum.Filtered(result)
|
||||
if UserVisibilityResultHelper.isDropProtectedAuthor(result) =>
|
||||
protectedAuthorStats.counter().incr()
|
||||
protectedAuthorStats.counter("filtered").incr()
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private[this] def authorBlocksViewer(state: UserUnavailableStateEnum): Boolean =
|
||||
state match {
|
||||
case UserUnavailableStateEnum.AuthorBlocksViewer =>
|
||||
authorBlocksViewerStats.counter().incr()
|
||||
true
|
||||
case UserUnavailableStateEnum.Filtered(result)
|
||||
if UserVisibilityResultHelper.isDropAuthorBlocksViewer(result) =>
|
||||
authorBlocksViewerStats.counter().incr()
|
||||
authorBlocksViewerStats.counter("filtered").incr()
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private[this] def viewerBlocksAuthor(state: UserUnavailableStateEnum): Boolean =
|
||||
state match {
|
||||
case UserUnavailableStateEnum.ViewerBlocksAuthor =>
|
||||
viewerBlocksAuthorStats.counter().incr()
|
||||
true
|
||||
case UserUnavailableStateEnum.Filtered(result)
|
||||
if UserVisibilityResultHelper.isDropViewerBlocksAuthor(result) =>
|
||||
viewerBlocksAuthorStats.counter().incr()
|
||||
viewerBlocksAuthorStats.counter("filtered").incr()
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private[this] def viewerMutesAuthor(state: UserUnavailableStateEnum): Boolean =
|
||||
state match {
|
||||
case UserUnavailableStateEnum.ViewerMutesAuthor =>
|
||||
viewerMutesAuthorStats.counter().incr()
|
||||
true
|
||||
case UserUnavailableStateEnum.Filtered(result)
|
||||
if UserVisibilityResultHelper.isDropViewerMutesAuthor(result) =>
|
||||
viewerMutesAuthorStats.counter().incr()
|
||||
viewerMutesAuthorStats.counter("filtered").incr()
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private[this] def isUnavailable(state: UserUnavailableStateEnum): Boolean =
|
||||
state match {
|
||||
case UserUnavailableStateEnum.Unavailable =>
|
||||
unavailableStats.counter().incr()
|
||||
true
|
||||
case UserUnavailableStateEnum.Filtered(result)
|
||||
if UserVisibilityResultHelper.isDropUnspecifiedAuthor(result) =>
|
||||
unavailableStats.counter().incr()
|
||||
unavailableStats.counter("filtered").incr()
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
package com.twitter.visibility.builder.users
|
||||
|
||||
import com.twitter.finagle.stats.Counter
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.gizmoduck.thriftscala.AdvancedFilters
|
||||
import com.twitter.gizmoduck.thriftscala.MentionFilter
|
||||
import com.twitter.stitch.NotFound
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.UserSource
|
||||
import com.twitter.visibility.features.ViewerFiltersDefaultProfileImage
|
||||
import com.twitter.visibility.features.ViewerFiltersNewUsers
|
||||
import com.twitter.visibility.features.ViewerFiltersNoConfirmedEmail
|
||||
import com.twitter.visibility.features.ViewerFiltersNoConfirmedPhone
|
||||
import com.twitter.visibility.features.ViewerFiltersNotFollowedBy
|
||||
import com.twitter.visibility.features.ViewerMentionFilter
|
||||
|
||||
class ViewerAdvancedFilteringFeatures(userSource: UserSource, statsReceiver: StatsReceiver) {
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("viewer_advanced_filtering_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val viewerFiltersNoConfirmedEmail =
|
||||
scopedStatsReceiver.scope(ViewerFiltersNoConfirmedEmail.name).counter("requests")
|
||||
private[this] val viewerFiltersNoConfirmedPhone =
|
||||
scopedStatsReceiver.scope(ViewerFiltersNoConfirmedPhone.name).counter("requests")
|
||||
private[this] val viewerFiltersDefaultProfileImage =
|
||||
scopedStatsReceiver.scope(ViewerFiltersDefaultProfileImage.name).counter("requests")
|
||||
private[this] val viewerFiltersNewUsers =
|
||||
scopedStatsReceiver.scope(ViewerFiltersNewUsers.name).counter("requests")
|
||||
private[this] val viewerFiltersNotFollowedBy =
|
||||
scopedStatsReceiver.scope(ViewerFiltersNotFollowedBy.name).counter("requests")
|
||||
private[this] val viewerMentionFilter =
|
||||
scopedStatsReceiver.scope(ViewerMentionFilter.name).counter("requests")
|
||||
|
||||
def forViewerId(viewerId: Option[Long]): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
requests.incr()
|
||||
|
||||
_.withFeature(ViewerFiltersNoConfirmedEmail, viewerFiltersNoConfirmedEmail(viewerId))
|
||||
.withFeature(ViewerFiltersNoConfirmedPhone, viewerFiltersNoConfirmedPhone(viewerId))
|
||||
.withFeature(ViewerFiltersDefaultProfileImage, viewerFiltersDefaultProfileImage(viewerId))
|
||||
.withFeature(ViewerFiltersNewUsers, viewerFiltersNewUsers(viewerId))
|
||||
.withFeature(ViewerFiltersNotFollowedBy, viewerFiltersNotFollowedBy(viewerId))
|
||||
.withFeature(ViewerMentionFilter, viewerMentionFilter(viewerId))
|
||||
}
|
||||
|
||||
def viewerFiltersNoConfirmedEmail(viewerId: Option[Long]): Stitch[Boolean] =
|
||||
viewerAdvancedFilters(viewerId, af => af.filterNoConfirmedEmail, viewerFiltersNoConfirmedEmail)
|
||||
|
||||
def viewerFiltersNoConfirmedPhone(viewerId: Option[Long]): Stitch[Boolean] =
|
||||
viewerAdvancedFilters(viewerId, af => af.filterNoConfirmedPhone, viewerFiltersNoConfirmedPhone)
|
||||
|
||||
def viewerFiltersDefaultProfileImage(viewerId: Option[Long]): Stitch[Boolean] =
|
||||
viewerAdvancedFilters(
|
||||
viewerId,
|
||||
af => af.filterDefaultProfileImage,
|
||||
viewerFiltersDefaultProfileImage
|
||||
)
|
||||
|
||||
def viewerFiltersNewUsers(viewerId: Option[Long]): Stitch[Boolean] =
|
||||
viewerAdvancedFilters(viewerId, af => af.filterNewUsers, viewerFiltersNewUsers)
|
||||
|
||||
def viewerFiltersNotFollowedBy(viewerId: Option[Long]): Stitch[Boolean] =
|
||||
viewerAdvancedFilters(viewerId, af => af.filterNotFollowedBy, viewerFiltersNotFollowedBy)
|
||||
|
||||
def viewerMentionFilter(viewerId: Option[Long]): Stitch[MentionFilter] = {
|
||||
viewerMentionFilter.incr()
|
||||
viewerId match {
|
||||
case Some(id) =>
|
||||
userSource.getMentionFilter(id).handle {
|
||||
case NotFound =>
|
||||
MentionFilter.Unfiltered
|
||||
}
|
||||
case _ => Stitch.value(MentionFilter.Unfiltered)
|
||||
}
|
||||
}
|
||||
|
||||
private[this] def viewerAdvancedFilters(
|
||||
viewerId: Option[Long],
|
||||
advancedFilterCheck: AdvancedFilters => Boolean,
|
||||
featureCounter: Counter
|
||||
): Stitch[Boolean] = {
|
||||
featureCounter.incr()
|
||||
|
||||
val advancedFilters = viewerId match {
|
||||
case Some(id) => userSource.getAdvancedFilters(id)
|
||||
case _ => Stitch.value(AdvancedFilters())
|
||||
}
|
||||
|
||||
advancedFilters.map(advancedFilterCheck)
|
||||
}
|
||||
}
|
|
@ -1,245 +0,0 @@
|
|||
package com.twitter.visibility.builder.users
|
||||
|
||||
import com.twitter.finagle.stats.Counter
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.gizmoduck.thriftscala.Label
|
||||
import com.twitter.gizmoduck.thriftscala.Safety
|
||||
import com.twitter.gizmoduck.thriftscala.UniversalQualityFiltering
|
||||
import com.twitter.gizmoduck.thriftscala.User
|
||||
import com.twitter.gizmoduck.thriftscala.UserType
|
||||
import com.twitter.stitch.NotFound
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.UserId
|
||||
import com.twitter.visibility.common.UserSource
|
||||
import com.twitter.visibility.features._
|
||||
import com.twitter.visibility.interfaces.common.blender.BlenderVFRequestContext
|
||||
import com.twitter.visibility.interfaces.common.search.SearchVFRequestContext
|
||||
import com.twitter.visibility.models.UserAge
|
||||
import com.twitter.visibility.models.ViewerContext
|
||||
|
||||
class ViewerFeatures(userSource: UserSource, statsReceiver: StatsReceiver) {
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("viewer_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val viewerIdCount =
|
||||
scopedStatsReceiver.scope(ViewerId.name).counter("requests")
|
||||
private[this] val requestCountryCode =
|
||||
scopedStatsReceiver.scope(RequestCountryCode.name).counter("requests")
|
||||
private[this] val requestIsVerifiedCrawler =
|
||||
scopedStatsReceiver.scope(RequestIsVerifiedCrawler.name).counter("requests")
|
||||
private[this] val viewerUserLabels =
|
||||
scopedStatsReceiver.scope(ViewerUserLabels.name).counter("requests")
|
||||
private[this] val viewerIsDeactivated =
|
||||
scopedStatsReceiver.scope(ViewerIsDeactivated.name).counter("requests")
|
||||
private[this] val viewerIsProtected =
|
||||
scopedStatsReceiver.scope(ViewerIsProtected.name).counter("requests")
|
||||
private[this] val viewerIsSuspended =
|
||||
scopedStatsReceiver.scope(ViewerIsSuspended.name).counter("requests")
|
||||
private[this] val viewerRoles =
|
||||
scopedStatsReceiver.scope(ViewerRoles.name).counter("requests")
|
||||
private[this] val viewerCountryCode =
|
||||
scopedStatsReceiver.scope(ViewerCountryCode.name).counter("requests")
|
||||
private[this] val viewerAge =
|
||||
scopedStatsReceiver.scope(ViewerAge.name).counter("requests")
|
||||
private[this] val viewerHasUniversalQualityFilterEnabled =
|
||||
scopedStatsReceiver.scope(ViewerHasUniversalQualityFilterEnabled.name).counter("requests")
|
||||
private[this] val viewerIsSoftUserCtr =
|
||||
scopedStatsReceiver.scope(ViewerIsSoftUser.name).counter("requests")
|
||||
|
||||
def forViewerBlenderContext(
|
||||
blenderContext: BlenderVFRequestContext,
|
||||
viewerContext: ViewerContext
|
||||
): FeatureMapBuilder => FeatureMapBuilder =
|
||||
forViewerContext(viewerContext)
|
||||
.andThen(
|
||||
_.withConstantFeature(
|
||||
ViewerOptInBlocking,
|
||||
blenderContext.userSearchSafetySettings.optInBlocking)
|
||||
.withConstantFeature(
|
||||
ViewerOptInFiltering,
|
||||
blenderContext.userSearchSafetySettings.optInFiltering)
|
||||
)
|
||||
|
||||
def forViewerSearchContext(
|
||||
searchContext: SearchVFRequestContext,
|
||||
viewerContext: ViewerContext
|
||||
): FeatureMapBuilder => FeatureMapBuilder =
|
||||
forViewerContext(viewerContext)
|
||||
.andThen(
|
||||
_.withConstantFeature(
|
||||
ViewerOptInBlocking,
|
||||
searchContext.userSearchSafetySettings.optInBlocking)
|
||||
.withConstantFeature(
|
||||
ViewerOptInFiltering,
|
||||
searchContext.userSearchSafetySettings.optInFiltering)
|
||||
)
|
||||
|
||||
def forViewerContext(viewerContext: ViewerContext): FeatureMapBuilder => FeatureMapBuilder =
|
||||
forViewerId(viewerContext.userId)
|
||||
.andThen(
|
||||
_.withConstantFeature(RequestCountryCode, requestCountryCode(viewerContext))
|
||||
).andThen(
|
||||
_.withConstantFeature(RequestIsVerifiedCrawler, requestIsVerifiedCrawler(viewerContext))
|
||||
)
|
||||
|
||||
def forViewerId(viewerId: Option[UserId]): FeatureMapBuilder => FeatureMapBuilder = { builder =>
|
||||
requests.incr()
|
||||
|
||||
val builderWithFeatures = builder
|
||||
.withConstantFeature(ViewerId, viewerId)
|
||||
.withFeature(ViewerIsProtected, viewerIsProtected(viewerId))
|
||||
.withFeature(
|
||||
ViewerHasUniversalQualityFilterEnabled,
|
||||
viewerHasUniversalQualityFilterEnabled(viewerId)
|
||||
)
|
||||
.withFeature(ViewerIsSuspended, viewerIsSuspended(viewerId))
|
||||
.withFeature(ViewerIsDeactivated, viewerIsDeactivated(viewerId))
|
||||
.withFeature(ViewerUserLabels, viewerUserLabels(viewerId))
|
||||
.withFeature(ViewerRoles, viewerRoles(viewerId))
|
||||
.withFeature(ViewerAge, viewerAgeInYears(viewerId))
|
||||
.withFeature(ViewerIsSoftUser, viewerIsSoftUser(viewerId))
|
||||
|
||||
viewerId match {
|
||||
case Some(_) =>
|
||||
viewerIdCount.incr()
|
||||
builderWithFeatures
|
||||
.withFeature(ViewerCountryCode, viewerCountryCode(viewerId))
|
||||
|
||||
case _ =>
|
||||
builderWithFeatures
|
||||
}
|
||||
}
|
||||
|
||||
def forViewerNoDefaults(viewerOpt: Option[User]): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
builder =>
|
||||
requests.incr()
|
||||
|
||||
viewerOpt match {
|
||||
case Some(viewer) =>
|
||||
builder
|
||||
.withConstantFeature(ViewerId, viewer.id)
|
||||
.withConstantFeature(ViewerIsProtected, viewerIsProtectedOpt(viewer))
|
||||
.withConstantFeature(ViewerIsSuspended, viewerIsSuspendedOpt(viewer))
|
||||
.withConstantFeature(ViewerIsDeactivated, viewerIsDeactivatedOpt(viewer))
|
||||
.withConstantFeature(ViewerCountryCode, viewerCountryCode(viewer))
|
||||
case None =>
|
||||
builder
|
||||
.withConstantFeature(ViewerIsProtected, false)
|
||||
.withConstantFeature(ViewerIsSuspended, false)
|
||||
.withConstantFeature(ViewerIsDeactivated, false)
|
||||
}
|
||||
}
|
||||
|
||||
private def checkSafetyValue(
|
||||
viewerId: Option[UserId],
|
||||
safetyCheck: Safety => Boolean,
|
||||
featureCounter: Counter
|
||||
): Stitch[Boolean] =
|
||||
viewerId match {
|
||||
case Some(id) =>
|
||||
userSource.getSafety(id).map(safetyCheck).ensure {
|
||||
featureCounter.incr()
|
||||
}
|
||||
case None => Stitch.False
|
||||
}
|
||||
|
||||
private def checkSafetyValue(
|
||||
viewer: User,
|
||||
safetyCheck: Safety => Boolean
|
||||
): Boolean = {
|
||||
viewer.safety.exists(safetyCheck)
|
||||
}
|
||||
|
||||
def viewerIsProtected(viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
checkSafetyValue(viewerId, s => s.isProtected, viewerIsProtected)
|
||||
|
||||
def viewerIsProtected(viewer: User): Boolean =
|
||||
checkSafetyValue(viewer, s => s.isProtected)
|
||||
|
||||
def viewerIsProtectedOpt(viewer: User): Option[Boolean] =
|
||||
viewer.safety.map(_.isProtected)
|
||||
|
||||
def viewerIsDeactivated(viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
checkSafetyValue(viewerId, s => s.deactivated, viewerIsDeactivated)
|
||||
|
||||
def viewerIsDeactivated(viewer: User): Boolean =
|
||||
checkSafetyValue(viewer, s => s.deactivated)
|
||||
|
||||
def viewerIsDeactivatedOpt(viewer: User): Option[Boolean] =
|
||||
viewer.safety.map(_.deactivated)
|
||||
|
||||
def viewerHasUniversalQualityFilterEnabled(viewerId: Option[UserId]): Stitch[Boolean] =
|
||||
checkSafetyValue(
|
||||
viewerId,
|
||||
s => s.universalQualityFiltering == UniversalQualityFiltering.Enabled,
|
||||
viewerHasUniversalQualityFilterEnabled
|
||||
)
|
||||
|
||||
def viewerUserLabels(viewerIdOpt: Option[UserId]): Stitch[Seq[Label]] =
|
||||
viewerIdOpt match {
|
||||
case Some(viewerId) =>
|
||||
userSource
|
||||
.getLabels(viewerId).map(_.labels)
|
||||
.handle {
|
||||
case NotFound => Seq.empty
|
||||
}.ensure {
|
||||
viewerUserLabels.incr()
|
||||
}
|
||||
case _ => Stitch.value(Seq.empty)
|
||||
}
|
||||
|
||||
def viewerAgeInYears(viewerId: Option[UserId]): Stitch[UserAge] =
|
||||
(viewerId match {
|
||||
case Some(id) =>
|
||||
userSource
|
||||
.getExtendedProfile(id).map(_.ageInYears)
|
||||
.handle {
|
||||
case NotFound => None
|
||||
}.ensure {
|
||||
viewerAge.incr()
|
||||
}
|
||||
case _ => Stitch.value(None)
|
||||
}).map(UserAge)
|
||||
|
||||
def viewerIsSoftUser(viewerId: Option[UserId]): Stitch[Boolean] = {
|
||||
viewerId match {
|
||||
case Some(id) =>
|
||||
userSource
|
||||
.getUserType(id).map { userType =>
|
||||
userType == UserType.Soft
|
||||
}.ensure {
|
||||
viewerIsSoftUserCtr.incr()
|
||||
}
|
||||
case _ => Stitch.False
|
||||
}
|
||||
}
|
||||
|
||||
def requestCountryCode(viewerContext: ViewerContext): Option[String] = {
|
||||
requestCountryCode.incr()
|
||||
viewerContext.requestCountryCode
|
||||
}
|
||||
|
||||
def requestIsVerifiedCrawler(viewerContext: ViewerContext): Boolean = {
|
||||
requestIsVerifiedCrawler.incr()
|
||||
viewerContext.isVerifiedCrawler
|
||||
}
|
||||
|
||||
def viewerCountryCode(viewerId: Option[UserId]): Stitch[String] =
|
||||
viewerId match {
|
||||
case Some(id) =>
|
||||
userSource
|
||||
.getAccount(id).map(_.countryCode).flatMap {
|
||||
case Some(countryCode) => Stitch.value(countryCode.toLowerCase)
|
||||
case _ => Stitch.NotFound
|
||||
}.ensure {
|
||||
viewerCountryCode.incr()
|
||||
}
|
||||
|
||||
case _ => Stitch.NotFound
|
||||
}
|
||||
|
||||
def viewerCountryCode(viewer: User): Option[String] =
|
||||
viewer.account.flatMap(_.countryCode)
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package com.twitter.visibility.builder.users
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.UserId
|
||||
import com.twitter.visibility.common.UserSearchSafetySource
|
||||
import com.twitter.visibility.features.ViewerId
|
||||
import com.twitter.visibility.features.ViewerOptInBlocking
|
||||
import com.twitter.visibility.features.ViewerOptInFiltering
|
||||
|
||||
class ViewerSearchSafetyFeatures(
|
||||
userSearchSafetySource: UserSearchSafetySource,
|
||||
statsReceiver: StatsReceiver) {
|
||||
private[this] val scopedStatsReceiver = statsReceiver.scope("viewer_search_safety_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
private[this] val viewerOptInBlockingRequests =
|
||||
scopedStatsReceiver.scope(ViewerOptInBlocking.name).counter("requests")
|
||||
|
||||
private[this] val viewerOptInFilteringRequests =
|
||||
scopedStatsReceiver.scope(ViewerOptInFiltering.name).counter("requests")
|
||||
|
||||
def forViewerId(viewerId: Option[UserId]): FeatureMapBuilder => FeatureMapBuilder = { builder =>
|
||||
requests.incr()
|
||||
|
||||
builder
|
||||
.withConstantFeature(ViewerId, viewerId)
|
||||
.withFeature(ViewerOptInBlocking, viewerOptInBlocking(viewerId))
|
||||
.withFeature(ViewerOptInFiltering, viewerOptInFiltering(viewerId))
|
||||
}
|
||||
|
||||
def viewerOptInBlocking(viewerId: Option[UserId]): Stitch[Boolean] = {
|
||||
viewerOptInBlockingRequests.incr()
|
||||
viewerId match {
|
||||
case Some(userId) => userSearchSafetySource.optInBlocking(userId)
|
||||
case _ => Stitch.False
|
||||
}
|
||||
}
|
||||
|
||||
def viewerOptInFiltering(viewerId: Option[UserId]): Stitch[Boolean] = {
|
||||
viewerOptInFilteringRequests.incr()
|
||||
viewerId match {
|
||||
case Some(userId) => userSearchSafetySource.optInFiltering(userId)
|
||||
case _ => Stitch.False
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package com.twitter.visibility.builder.users
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.stitch.NotFound
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.common.UserId
|
||||
import com.twitter.visibility.common.UserSensitiveMediaSettingsSource
|
||||
import com.twitter.visibility.features.ViewerId
|
||||
import com.twitter.visibility.features.ViewerSensitiveMediaSettings
|
||||
import com.twitter.visibility.models.UserSensitiveMediaSettings
|
||||
|
||||
|
||||
class ViewerSensitiveMediaSettingsFeatures(
|
||||
userSensitiveMediaSettingsSource: UserSensitiveMediaSettingsSource,
|
||||
statsReceiver: StatsReceiver) {
|
||||
private[this] val scopedStatsReceiver =
|
||||
statsReceiver.scope("viewer_sensitive_media_settings_features")
|
||||
|
||||
private[this] val requests = scopedStatsReceiver.counter("requests")
|
||||
|
||||
def forViewerId(viewerId: Option[UserId]): FeatureMapBuilder => FeatureMapBuilder = { builder =>
|
||||
requests.incr()
|
||||
|
||||
builder
|
||||
.withConstantFeature(ViewerId, viewerId)
|
||||
.withFeature(ViewerSensitiveMediaSettings, viewerSensitiveMediaSettings(viewerId))
|
||||
}
|
||||
|
||||
def viewerSensitiveMediaSettings(viewerId: Option[UserId]): Stitch[UserSensitiveMediaSettings] = {
|
||||
(viewerId match {
|
||||
case Some(userId) =>
|
||||
userSensitiveMediaSettingsSource
|
||||
.userSensitiveMediaSettings(userId)
|
||||
.handle {
|
||||
case NotFound => None
|
||||
}
|
||||
case _ => Stitch.value(None)
|
||||
}).map(UserSensitiveMediaSettings)
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"abdecider/src/main/scala",
|
||||
"configapi/configapi-abdecider",
|
||||
"configapi/configapi-core",
|
||||
"configapi/configapi-featureswitches:v2",
|
||||
"decider",
|
||||
"featureswitches/featureswitches-core/src/main/scala",
|
||||
"finagle/finagle-stats",
|
||||
"servo/decider/src/main/scala",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/configs",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/models",
|
||||
],
|
||||
)
|
|
@ -1,43 +0,0 @@
|
|||
package com.twitter.visibility.configapi
|
||||
|
||||
import com.twitter.decider.Decider
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.logging.Logger
|
||||
import com.twitter.servo.decider.DeciderGateBuilder
|
||||
import com.twitter.timelines.configapi.CompositeConfig
|
||||
import com.twitter.timelines.configapi.Config
|
||||
import com.twitter.util.Memoize
|
||||
import com.twitter.visibility.configapi.configs.VisibilityDeciders
|
||||
import com.twitter.visibility.configapi.configs.VisibilityExperimentsConfig
|
||||
import com.twitter.visibility.configapi.configs.VisibilityFeatureSwitches
|
||||
import com.twitter.visibility.models.SafetyLevel
|
||||
|
||||
object ConfigBuilder {
|
||||
|
||||
def apply(statsReceiver: StatsReceiver, decider: Decider, logger: Logger): ConfigBuilder = {
|
||||
val deciderGateBuilder: DeciderGateBuilder =
|
||||
new DeciderGateBuilder(decider)
|
||||
|
||||
new ConfigBuilder(
|
||||
deciderGateBuilder,
|
||||
statsReceiver,
|
||||
logger
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigBuilder(
|
||||
deciderGateBuilder: DeciderGateBuilder,
|
||||
statsReceiver: StatsReceiver,
|
||||
logger: Logger) {
|
||||
|
||||
def buildMemoized: SafetyLevel => Config = Memoize(build)
|
||||
|
||||
def build(safetyLevel: SafetyLevel): Config = {
|
||||
new CompositeConfig(
|
||||
VisibilityExperimentsConfig.config(safetyLevel) :+
|
||||
VisibilityDeciders.config(deciderGateBuilder, logger, statsReceiver, safetyLevel) :+
|
||||
VisibilityFeatureSwitches.config(statsReceiver, logger)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package com.twitter.visibility.configapi
|
||||
|
||||
import com.twitter.abdecider.LoggingABDecider
|
||||
import com.twitter.decider.Decider
|
||||
import com.twitter.featureswitches.v2.FeatureSwitches
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.logging.Logger
|
||||
import com.twitter.servo.util.MemoizingStatsReceiver
|
||||
import com.twitter.timelines.configapi.Params
|
||||
import com.twitter.visibility.models.SafetyLevel
|
||||
import com.twitter.visibility.models.UnitOfDiversion
|
||||
import com.twitter.visibility.models.ViewerContext
|
||||
|
||||
object VisibilityParams {
|
||||
def apply(
|
||||
log: Logger,
|
||||
statsReceiver: StatsReceiver,
|
||||
decider: Decider,
|
||||
abDecider: LoggingABDecider,
|
||||
featureSwitches: FeatureSwitches
|
||||
): VisibilityParams =
|
||||
new VisibilityParams(log, statsReceiver, decider, abDecider, featureSwitches)
|
||||
}
|
||||
|
||||
class VisibilityParams(
|
||||
log: Logger,
|
||||
statsReceiver: StatsReceiver,
|
||||
decider: Decider,
|
||||
abDecider: LoggingABDecider,
|
||||
featureSwitches: FeatureSwitches) {
|
||||
|
||||
private[this] val contextFactory = new VisibilityRequestContextFactory(
|
||||
abDecider,
|
||||
featureSwitches
|
||||
)
|
||||
|
||||
private[this] val configBuilder = ConfigBuilder(statsReceiver.scope("config"), decider, log)
|
||||
|
||||
private[this] val paramStats: MemoizingStatsReceiver = new MemoizingStatsReceiver(
|
||||
statsReceiver.scope("configapi_params"))
|
||||
|
||||
def apply(
|
||||
viewerContext: ViewerContext,
|
||||
safetyLevel: SafetyLevel,
|
||||
unitsOfDiversion: Seq[UnitOfDiversion] = Seq.empty
|
||||
): Params = {
|
||||
val config = configBuilder.build(safetyLevel)
|
||||
val requestContext = contextFactory(viewerContext, safetyLevel, unitsOfDiversion)
|
||||
config.apply(requestContext, paramStats)
|
||||
}
|
||||
|
||||
def memoized(
|
||||
viewerContext: ViewerContext,
|
||||
safetyLevel: SafetyLevel,
|
||||
unitsOfDiversion: Seq[UnitOfDiversion] = Seq.empty
|
||||
): Params = {
|
||||
val config = configBuilder.buildMemoized(safetyLevel)
|
||||
val requestContext = contextFactory(viewerContext, safetyLevel, unitsOfDiversion)
|
||||
config.apply(requestContext, paramStats)
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package com.twitter.visibility.configapi
|
||||
|
||||
import com.twitter.timelines.configapi._
|
||||
|
||||
case class VisibilityRequestContext(
|
||||
userId: Option[Long],
|
||||
guestId: Option[Long],
|
||||
experimentContext: ExperimentContext = NullExperimentContext,
|
||||
featureContext: FeatureContext = NullFeatureContext)
|
||||
extends BaseRequestContext
|
||||
with WithUserId
|
||||
with WithGuestId
|
||||
with WithExperimentContext
|
||||
with WithFeatureContext
|
|
@ -1,64 +0,0 @@
|
|||
package com.twitter.visibility.configapi
|
||||
|
||||
import com.twitter.abdecider.LoggingABDecider
|
||||
import com.twitter.featureswitches.FSRecipient
|
||||
import com.twitter.featureswitches.v2.FeatureSwitches
|
||||
import com.twitter.timelines.configapi.abdecider.UserRecipientExperimentContextFactory
|
||||
import com.twitter.timelines.configapi.featureswitches.v2.FeatureSwitchResultsFeatureContext
|
||||
import com.twitter.timelines.configapi.FeatureContext
|
||||
import com.twitter.timelines.configapi.NullExperimentContext
|
||||
import com.twitter.timelines.configapi.UseFeatureContextExperimentContext
|
||||
import com.twitter.visibility.models.SafetyLevel
|
||||
import com.twitter.visibility.models.UnitOfDiversion
|
||||
import com.twitter.visibility.models.ViewerContext
|
||||
|
||||
class VisibilityRequestContextFactory(
|
||||
loggingABDecider: LoggingABDecider,
|
||||
featureSwitches: FeatureSwitches) {
|
||||
private val userExperimentContextFactory = new UserRecipientExperimentContextFactory(
|
||||
loggingABDecider
|
||||
)
|
||||
private[this] def getFeatureContext(
|
||||
context: ViewerContext,
|
||||
safetyLevel: SafetyLevel,
|
||||
unitsOfDiversion: Seq[UnitOfDiversion]
|
||||
): FeatureContext = {
|
||||
val uodCustomFields = unitsOfDiversion.map(_.apply)
|
||||
val recipient = FSRecipient(
|
||||
userId = context.userId,
|
||||
guestId = context.guestId,
|
||||
userAgent = context.fsUserAgent,
|
||||
clientApplicationId = context.clientApplicationId,
|
||||
countryCode = context.requestCountryCode,
|
||||
deviceId = context.deviceId,
|
||||
languageCode = context.requestLanguageCode,
|
||||
isTwoffice = Some(context.isTwOffice),
|
||||
userRoles = context.userRoles,
|
||||
).withCustomFields(("safety_level", safetyLevel.name), uodCustomFields: _*)
|
||||
|
||||
val results = featureSwitches.matchRecipient(recipient)
|
||||
new FeatureSwitchResultsFeatureContext(results)
|
||||
}
|
||||
|
||||
def apply(
|
||||
context: ViewerContext,
|
||||
safetyLevel: SafetyLevel,
|
||||
unitsOfDiversion: Seq[UnitOfDiversion] = Seq.empty
|
||||
): VisibilityRequestContext = {
|
||||
val experimentContextBase =
|
||||
context.userId
|
||||
.map(userId => userExperimentContextFactory.apply(userId)).getOrElse(NullExperimentContext)
|
||||
|
||||
val featureContext = getFeatureContext(context, safetyLevel, unitsOfDiversion)
|
||||
|
||||
val experimentContext =
|
||||
UseFeatureContextExperimentContext(experimentContextBase, featureContext)
|
||||
|
||||
VisibilityRequestContext(
|
||||
context.userId,
|
||||
context.guestId,
|
||||
experimentContext,
|
||||
featureContext
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"configapi/configapi-core",
|
||||
"configapi/configapi-decider",
|
||||
"decider",
|
||||
"finagle/finagle-stats",
|
||||
"servo/decider",
|
||||
"servo/decider/src/main/scala",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/params",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/models",
|
||||
],
|
||||
)
|
File diff suppressed because it is too large
Load Diff
|
@ -1,26 +0,0 @@
|
|||
package com.twitter.visibility.configapi.configs
|
||||
|
||||
import com.twitter.timelines.configapi.Config
|
||||
import com.twitter.timelines.configapi.ExperimentConfigBuilder
|
||||
import com.twitter.timelines.configapi.Param
|
||||
import com.twitter.visibility.configapi.params.VisibilityExperiment
|
||||
import com.twitter.visibility.models.SafetyLevel
|
||||
|
||||
object ExperimentsHelper {
|
||||
|
||||
def mkABExperimentConfig(experiment: VisibilityExperiment, param: Param[Boolean]): Config = {
|
||||
ExperimentConfigBuilder(experiment)
|
||||
.addBucket(
|
||||
experiment.ControlBucket,
|
||||
param := true
|
||||
)
|
||||
.addBucket(
|
||||
experiment.TreatmentBucket,
|
||||
param := false
|
||||
)
|
||||
.build
|
||||
}
|
||||
|
||||
def mkABExperimentConfig(experiment: VisibilityExperiment, safetyLevel: SafetyLevel): Config =
|
||||
mkABExperimentConfig(experiment, safetyLevel.enabledParam)
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
package com.twitter.visibility.configapi.configs
|
||||
|
||||
import com.twitter.decider.Decider
|
||||
import com.twitter.servo.gate.DeciderGate
|
||||
import com.twitter.servo.util.Gate
|
||||
|
||||
case class VisibilityDeciderGates(decider: Decider) {
|
||||
import DeciderKey._
|
||||
|
||||
private[this] def feature(deciderKey: Value) = decider.feature(deciderKey.toString)
|
||||
|
||||
val enableFetchTweetReportedPerspective: Gate[Unit] =
|
||||
DeciderGate.linear(feature(DeciderKey.EnableFetchTweetReportedPerspective))
|
||||
val enableFetchTweetMediaMetadata: Gate[Unit] =
|
||||
DeciderGate.linear(feature(DeciderKey.EnableFetchTweetMediaMetadata))
|
||||
val enableFollowCheckInMutedKeyword: Gate[Unit] =
|
||||
DeciderGate.linear(feature(DeciderKey.VisibilityLibraryEnableFollowCheckInMutedKeyword))
|
||||
val enableMediaInterstitialComposition: Gate[Unit] =
|
||||
DeciderGate.linear(feature(DeciderKey.VisibilityLibraryEnableMediaInterstitialComposition))
|
||||
val enableExperimentalRuleEngine: Gate[Unit] =
|
||||
DeciderGate.linear(feature(DeciderKey.EnableExperimentalRuleEngine))
|
||||
|
||||
val enableLocalizedInterstitialGenerator: Gate[Unit] =
|
||||
DeciderGate.linear(feature(DeciderKey.EnableLocalizedInterstitialGenerator))
|
||||
|
||||
val enableShortCircuitingTVL: Gate[Unit] =
|
||||
DeciderGate.linear(feature(EnableShortCircuitingFromTweetVisibilityLibrary))
|
||||
val enableVerdictLoggerTVL = DeciderGate.linear(
|
||||
feature(DeciderKey.EnableVerdictLoggerEventPublisherInstantiationFromTweetVisibilityLibrary))
|
||||
val enableVerdictScribingTVL =
|
||||
DeciderGate.linear(feature(DeciderKey.EnableVerdictScribingFromTweetVisibilityLibrary))
|
||||
val enableBackendLimitedActions =
|
||||
DeciderGate.linear(feature(DeciderKey.EnableBackendLimitedActions))
|
||||
val enableMemoizeSafetyLevelParams: Gate[Unit] =
|
||||
DeciderGate.linear(feature(DeciderKey.EnableMemoizeSafetyLevelParams))
|
||||
|
||||
val enableShortCircuitingBVL: Gate[Unit] =
|
||||
DeciderGate.linear(feature(EnableShortCircuitingFromBlenderVisibilityLibrary))
|
||||
val enableVerdictLoggerBVL = DeciderGate.linear(
|
||||
feature(DeciderKey.EnableVerdictLoggerEventPublisherInstantiationFromBlenderVisibilityLibrary))
|
||||
val enableVerdictScribingBVL =
|
||||
DeciderGate.linear(feature(DeciderKey.EnableVerdictScribingFromBlenderVisibilityLibrary))
|
||||
|
||||
val enableShortCircuitingSVL: Gate[Unit] =
|
||||
DeciderGate.linear(feature(EnableShortCircuitingFromSearchVisibilityLibrary))
|
||||
val enableVerdictLoggerSVL = DeciderGate.linear(
|
||||
feature(DeciderKey.EnableVerdictLoggerEventPublisherInstantiationFromSearchVisibilityLibrary))
|
||||
val enableVerdictScribingSVL =
|
||||
DeciderGate.linear(feature(DeciderKey.EnableVerdictScribingFromSearchVisibilityLibrary))
|
||||
|
||||
val enableShortCircuitingTCVL: Gate[Unit] =
|
||||
DeciderGate.linear(feature(EnableShortCircuitingFromTimelineConversationsVisibilityLibrary))
|
||||
val enableVerdictLoggerTCVL = DeciderGate.linear(feature(
|
||||
DeciderKey.EnableVerdictLoggerEventPublisherInstantiationFromTimelineConversationsVisibilityLibrary))
|
||||
val enableVerdictScribingTCVL =
|
||||
DeciderGate.linear(
|
||||
feature(DeciderKey.EnableVerdictScribingFromTimelineConversationsVisibilityLibrary))
|
||||
|
||||
val enableCardVisibilityLibraryCardUriParsing =
|
||||
DeciderGate.linear(feature(DeciderKey.EnableCardVisibilityLibraryCardUriParsing))
|
||||
|
||||
val enableConvosLocalizedInterstitial: Gate[Unit] =
|
||||
DeciderGate.linear(feature(DeciderKey.EnableConvosLocalizedInterstitial))
|
||||
|
||||
val enableConvosLegacyInterstitial: Gate[Unit] =
|
||||
DeciderGate.linear(feature(DeciderKey.EnableConvosLegacyInterstitial))
|
||||
|
||||
val disableLegacyInterstitialFilteredReason: Gate[Unit] =
|
||||
DeciderGate.linear(feature(DeciderKey.DisableLegacyInterstitialFilteredReason))
|
||||
|
||||
val enableLocalizedInterstitialInUserStateLibrary: Gate[Unit] =
|
||||
DeciderGate.linear(feature(DeciderKey.EnableLocalizedInterstitialInUserStateLib))
|
||||
}
|
|
@ -1,390 +0,0 @@
|
|||
package com.twitter.visibility.configapi.configs
|
||||
|
||||
import com.twitter.decider.Recipient
|
||||
import com.twitter.decider.SimpleRecipient
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.logging.Logger
|
||||
import com.twitter.servo.decider.DeciderGateBuilder
|
||||
import com.twitter.timelines.configapi.BaseConfigBuilder
|
||||
import com.twitter.timelines.configapi.BaseRequestContext
|
||||
import com.twitter.timelines.configapi.Config
|
||||
import com.twitter.timelines.configapi.Param
|
||||
import com.twitter.timelines.configapi.WithGuestId
|
||||
import com.twitter.timelines.configapi.WithUserId
|
||||
import com.twitter.timelines.configapi.decider.DeciderSwitchOverrideValue
|
||||
import com.twitter.timelines.configapi.decider.GuestRecipient
|
||||
import com.twitter.timelines.configapi.decider.RecipientBuilder
|
||||
import com.twitter.visibility.configapi.params.RuleParams
|
||||
import com.twitter.visibility.configapi.params.TimelineConversationsDownrankingSpecificParams
|
||||
import com.twitter.visibility.models.SafetyLevel
|
||||
import com.twitter.visibility.models.SafetyLevel._
|
||||
|
||||
private[visibility] object VisibilityDeciders {
|
||||
val SafetyLevelToDeciderMap: Map[SafetyLevel, DeciderKey.Value] = Map(
|
||||
AllSubscribedLists -> DeciderKey.EnableAllSubscribedListsSafetyLevel,
|
||||
AccessInternalPromotedContent -> DeciderKey.EnableAccessInternalPromotedContentSafetyLevel,
|
||||
AdsBusinessSettings -> DeciderKey.EnableAdsBusinessSettingsSafetyLevel,
|
||||
AdsCampaign -> DeciderKey.EnableAdsCampaignSafetyLevel,
|
||||
AdsManager -> DeciderKey.EnableAdsManagerSafetyLevel,
|
||||
AdsReportingDashboard -> DeciderKey.EnableAdsReportingDashboardSafetyLevel,
|
||||
Appeals -> DeciderKey.EnableAppealsSafetyLevel,
|
||||
ArticleTweetTimeline -> DeciderKey.EnableArticleTweetTimelineSafetyLevel,
|
||||
BaseQig -> DeciderKey.EnableBaseQig,
|
||||
BirdwatchNoteAuthor -> DeciderKey.EnableBirdwatchNoteAuthorSafetyLevel,
|
||||
BirdwatchNoteTweetsTimeline -> DeciderKey.EnableBirdwatchNoteTweetsTimelineSafetyLevel,
|
||||
BirdwatchNeedsYourHelpNotifications -> DeciderKey.EnableBirdwatchNeedsYourHelpNotificationsSafetyLevel,
|
||||
BlockMuteUsersTimeline -> DeciderKey.EnableBlockMuteUsersTimelineSafetyLevel,
|
||||
BrandSafety -> DeciderKey.EnableBrandSafetySafetyLevel,
|
||||
CardPollVoting -> DeciderKey.EnableCardPollVotingSafetyLevel,
|
||||
CardsService -> DeciderKey.EnableCardsServiceSafetyLevel,
|
||||
Communities -> DeciderKey.EnableCommunitiesSafetyLevel,
|
||||
ContentControlToolInstall -> DeciderKey.EnableContentControlToolInstallSafetyLevel,
|
||||
ConversationFocalPrehydration -> DeciderKey.EnableConversationFocalPrehydrationSafetyLevel,
|
||||
ConversationFocalTweet -> DeciderKey.EnableConversationFocalTweetSafetyLevel,
|
||||
ConversationInjectedTweet -> DeciderKey.EnableConversationInjectedTweetSafetyLevel,
|
||||
ConversationReply -> DeciderKey.EnableConversationReplySafetyLevel,
|
||||
CuratedTrendsRepresentativeTweet -> DeciderKey.EnableCuratedTrendsRepresentativeTweet,
|
||||
CurationPolicyViolations -> DeciderKey.EnableCurationPolicyViolations,
|
||||
DeprecatedSafetyLevel -> DeciderKey.EnableDeprecatedSafetyLevelSafetyLevel,
|
||||
DevPlatformGetListTweets -> DeciderKey.EnableDevPlatformGetListTweetsSafetyLevel,
|
||||
DesFollowingAndFollowersUserList -> DeciderKey.EnableDesFollowingAndFollowersUserListSafetyLevel,
|
||||
DesHomeTimeline -> DeciderKey.EnableDesHomeTimelineSafetyLevel,
|
||||
DesQuoteTweetTimeline -> DeciderKey.EnableDesQuoteTweetTimelineSafetyLevel,
|
||||
DesRealtime -> DeciderKey.EnableDesRealtimeSafetyLevel,
|
||||
DesRealtimeSpamEnrichment -> DeciderKey.EnableDesRealtimeSpamEnrichmentSafetyLevel,
|
||||
DesRealtimeTweetFilter -> DeciderKey.EnableDesRealtimeTweetFilterSafetyLevel,
|
||||
DesRetweetingUsers -> DeciderKey.EnableDesRetweetingUsersSafetyLevel,
|
||||
DesTweetDetail -> DeciderKey.EnableDesTweetDetailSafetyLevel,
|
||||
DesTweetLikingUsers -> DeciderKey.EnableDesTweetLikingUsersSafetyLevel,
|
||||
DesUserBookmarks -> DeciderKey.EnableDesUserBookmarksSafetyLevel,
|
||||
DesUserLikedTweets -> DeciderKey.EnableDesUserLikedTweetsSafetyLevel,
|
||||
DesUserMentions -> DeciderKey.EnableDesUserMentionsSafetyLevel,
|
||||
DesUserTweets -> DeciderKey.EnableDesUserTweetsSafetyLevel,
|
||||
DevPlatformComplianceStream -> DeciderKey.EnableDevPlatformComplianceStreamSafetyLevel,
|
||||
DirectMessages -> DeciderKey.EnableDirectMessagesSafetyLevel,
|
||||
DirectMessagesConversationList -> DeciderKey.EnableDirectMessagesConversationListSafetyLevel,
|
||||
DirectMessagesConversationTimeline -> DeciderKey.EnableDirectMessagesConversationTimelineSafetyLevel,
|
||||
DirectMessagesInbox -> DeciderKey.EnableDirectMessagesInboxSafetyLevel,
|
||||
DirectMessagesMutedUsers -> DeciderKey.EnableDirectMessagesMutedUsersSafetyLevel,
|
||||
DirectMessagesPinned -> DeciderKey.EnableDirectMessagesPinnedSafetyLevel,
|
||||
DirectMessagesSearch -> DeciderKey.EnableDirectMessagesSearchSafetyLevel,
|
||||
EditHistoryTimeline -> DeciderKey.EnableEditHistoryTimelineSafetyLevel,
|
||||
ElevatedQuoteTweetTimeline -> DeciderKey.EnableElevatedQuoteTweetTimelineSafetyLevel,
|
||||
EmbeddedTweet -> DeciderKey.EnableEmbeddedTweetSafetyLevel,
|
||||
EmbedsPublicInterestNotice -> DeciderKey.EnableEmbedsPublicInterestNoticeSafetyLevel,
|
||||
EmbedTweetMarkup -> DeciderKey.EnableEmbedTweetMarkupSafetyLevel,
|
||||
FilterAll -> DeciderKey.EnableFilterAllSafetyLevel,
|
||||
FilterAllPlaceholder -> DeciderKey.EnableFilterAllPlaceholderSafetyLevel,
|
||||
FilterNone -> DeciderKey.EnableFilterNoneSafetyLevel,
|
||||
FilterDefault -> DeciderKey.EnableFilterDefaultSafetyLevel,
|
||||
FollowedTopicsTimeline -> DeciderKey.EnableFollowedTopicsTimelineSafetyLevel,
|
||||
FollowerConnections -> DeciderKey.EnableFollowerConnectionsSafetyLevel,
|
||||
FollowingAndFollowersUserList -> DeciderKey.EnableFollowingAndFollowersUserListSafetyLevel,
|
||||
ForDevelopmentOnly -> DeciderKey.EnableForDevelopmentOnlySafetyLevel,
|
||||
FriendsFollowingList -> DeciderKey.EnableFriendsFollowingListSafetyLevel,
|
||||
GraphqlDefault -> DeciderKey.EnableGraphqlDefaultSafetyLevel,
|
||||
GryphonDecksAndColumns -> DeciderKey.EnableGryphonDecksAndColumnsSafetyLevel,
|
||||
HumanizationNudge -> DeciderKey.EnableHumanizationNudgeSafetyLevel,
|
||||
KitchenSinkDevelopment -> DeciderKey.EnableKitchenSinkDevelopmentSafetyLevel,
|
||||
ListHeader -> DeciderKey.EnableListHeaderSafetyLevel,
|
||||
ListMemberships -> DeciderKey.EnableListMembershipsSafetyLevel,
|
||||
ListOwnerships -> DeciderKey.EnableListOwnershipsSafetyLevel,
|
||||
ListRecommendations -> DeciderKey.EnableListRecommendationsSafetyLevel,
|
||||
ListSearch -> DeciderKey.EnableListSearchSafetyLevel,
|
||||
ListSubscriptions -> DeciderKey.EnableListSubscriptionsSafetyLevel,
|
||||
LiveVideoTimeline -> DeciderKey.EnableLiveVideoTimelineSafetyLevel,
|
||||
LivePipelineEngagementCounts -> DeciderKey.EnableLivePipelineEngagementCountsSafetyLevel,
|
||||
MagicRecs -> DeciderKey.EnableMagicRecsSafetyLevel,
|
||||
MagicRecsAggressive -> DeciderKey.EnableMagicRecsAggressiveSafetyLevel,
|
||||
MagicRecsAggressiveV2 -> DeciderKey.EnableMagicRecsAggressiveV2SafetyLevel,
|
||||
MagicRecsV2 -> DeciderKey.EnableMagicRecsV2SafetyLevel,
|
||||
Minimal -> DeciderKey.EnableMinimalSafetyLevel,
|
||||
ModeratedTweetsTimeline -> DeciderKey.EnableModeratedTweetsTimelineSafetyLevel,
|
||||
Moments -> DeciderKey.EnableMomentsSafetyLevel,
|
||||
NearbyTimeline -> DeciderKey.EnableNearbyTimelineSafetyLevel,
|
||||
NewUserExperience -> DeciderKey.EnableNewUserExperienceSafetyLevel,
|
||||
NotificationsIbis -> DeciderKey.EnableNotificationsIbisSafetyLevel,
|
||||
NotificationsPlatform -> DeciderKey.EnableNotificationsPlatformSafetyLevel,
|
||||
NotificationsPlatformPush -> DeciderKey.EnableNotificationsPlatformPushSafetyLevel,
|
||||
NotificationsQig -> DeciderKey.EnableNotificationsQig,
|
||||
NotificationsRead -> DeciderKey.EnableNotificationsReadSafetyLevel,
|
||||
NotificationsTimelineDeviceFollow -> DeciderKey.EnableNotificationsTimelineDeviceFollowSafetyLevel,
|
||||
NotificationsWrite -> DeciderKey.EnableNotificationsWriteSafetyLevel,
|
||||
NotificationsWriterV2 -> DeciderKey.EnableNotificationsWriterV2SafetyLevel,
|
||||
NotificationsWriterTweetHydrator -> DeciderKey.EnableNotificationsWriterTweetHydratorSafetyLevel,
|
||||
ProfileMixerMedia -> DeciderKey.EnableProfileMixeMediaSafetyLevel,
|
||||
ProfileMixerFavorites -> DeciderKey.EnableProfileMixerFavoritesSafetyLevel,
|
||||
QuickPromoteTweetEligibility -> DeciderKey.EnableQuickPromoteTweetEligibilitySafetyLevel,
|
||||
QuoteTweetTimeline -> DeciderKey.EnableQuoteTweetTimelineSafetyLevel,
|
||||
QuotedTweetRules -> DeciderKey.EnableQuotedTweetRulesSafetyLevel,
|
||||
Recommendations -> DeciderKey.EnableRecommendationsSafetyLevel,
|
||||
RecosVideo -> DeciderKey.EnableRecosVideoSafetyLevel,
|
||||
RecosWritePath -> DeciderKey.EnableRecosWritePathSafetyLevel,
|
||||
RepliesGrouping -> DeciderKey.EnableRepliesGroupingSafetyLevel,
|
||||
ReportCenter -> DeciderKey.EnableReportCenterSafetyLevel,
|
||||
ReturningUserExperience -> DeciderKey.EnableReturningUserExperienceSafetyLevel,
|
||||
ReturningUserExperienceFocalTweet -> DeciderKey.EnableReturningUserExperienceFocalTweetSafetyLevel,
|
||||
Revenue -> DeciderKey.EnableRevenueSafetyLevel,
|
||||
RitoActionedTweetTimeline -> DeciderKey.EnableRitoActionedTweetTimelineSafetyLevel,
|
||||
SafeSearchMinimal -> DeciderKey.EnableSafeSearchMinimalSafetyLevel,
|
||||
SafeSearchStrict -> DeciderKey.EnableSafeSearchStrictSafetyLevel,
|
||||
SearchMixerSrpMinimal -> DeciderKey.EnableSearchMixerSrpMinimalSafetyLevel,
|
||||
SearchMixerSrpStrict -> DeciderKey.EnableSearchMixerSrpStrictSafetyLevel,
|
||||
SearchHydration -> DeciderKey.EnableSearchHydration,
|
||||
SearchLatest -> DeciderKey.EnableSearchLatest,
|
||||
SearchPeopleSrp -> DeciderKey.EnableSearchPeopleSrp,
|
||||
SearchPeopleTypeahead -> DeciderKey.EnableSearchPeopleTypeahead,
|
||||
SearchPhoto -> DeciderKey.EnableSearchPhoto,
|
||||
SearchTrendTakeoverPromotedTweet -> DeciderKey.EnableSearchTrendTakeoverPromotedTweet,
|
||||
SearchTop -> DeciderKey.EnableSearchTop,
|
||||
SearchTopQig -> DeciderKey.EnableSearchTopQig,
|
||||
SearchVideo -> DeciderKey.EnableSearchVideo,
|
||||
SearchBlenderUserRules -> DeciderKey.EnableSearchLatestUserRules,
|
||||
SearchLatestUserRules -> DeciderKey.EnableSearchLatestUserRules,
|
||||
ShoppingManagerSpyMode -> DeciderKey.EnableShoppingManagerSpyModeSafetyLevel,
|
||||
SignalsReactions -> DeciderKey.EnableSignalsReactions,
|
||||
SignalsTweetReactingUsers -> DeciderKey.EnableSignalsTweetReactingUsers,
|
||||
SocialProof -> DeciderKey.EnableSocialProof,
|
||||
SoftInterventionPivot -> DeciderKey.EnableSoftInterventionPivot,
|
||||
SpaceFleetline -> DeciderKey.EnableSpaceFleetlineSafetyLevel,
|
||||
SpaceHomeTimelineUpranking -> DeciderKey.EnableSpaceHomeTimelineUprankingSafetyLevel,
|
||||
SpaceJoinScreen -> DeciderKey.EnableSpaceJoinScreenSafetyLevel,
|
||||
SpaceNotifications -> DeciderKey.EnableSpaceNotificationsSafetyLevel,
|
||||
Spaces -> DeciderKey.EnableSpacesSafetyLevel,
|
||||
SpacesParticipants -> DeciderKey.EnableSpacesParticipantsSafetyLevel,
|
||||
SpacesSellerApplicationStatus -> DeciderKey.EnableSpacesSellerApplicationStatus,
|
||||
SpacesSharing -> DeciderKey.EnableSpacesSharingSafetyLevel,
|
||||
SpaceTweetAvatarHomeTimeline -> DeciderKey.EnableSpaceTweetAvatarHomeTimelineSafetyLevel,
|
||||
StickersTimeline -> DeciderKey.EnableStickersTimelineSafetyLevel,
|
||||
StratoExtLimitedEngagements -> DeciderKey.EnableStratoExtLimitedEngagementsSafetyLevel,
|
||||
StreamServices -> DeciderKey.EnableStreamServicesSafetyLevel,
|
||||
SuperFollowerConnections -> DeciderKey.EnableSuperFollowerConnectionsSafetyLevel,
|
||||
SuperLike -> DeciderKey.EnableSuperLikeSafetyLevel,
|
||||
Test -> DeciderKey.EnableTestSafetyLevel,
|
||||
TimelineContentControls -> DeciderKey.EnableTimelineContentControlsSafetyLevel,
|
||||
TimelineConversations -> DeciderKey.EnableTimelineConversationsSafetyLevel,
|
||||
TimelineConversationsDownranking -> DeciderKey.EnableTimelineConversationsDownrankingSafetyLevel,
|
||||
TimelineConversationsDownrankingMinimal -> DeciderKey.EnableTimelineConversationsDownrankingMinimalSafetyLevel,
|
||||
TimelineFollowingActivity -> DeciderKey.EnableTimelineFollowingActivitySafetyLevel,
|
||||
TimelineHome -> DeciderKey.EnableTimelineHomeSafetyLevel,
|
||||
TimelineHomeCommunities -> DeciderKey.EnableTimelineHomeCommunitiesSafetyLevel,
|
||||
TimelineHomeHydration -> DeciderKey.EnableTimelineHomeHydrationSafetyLevel,
|
||||
TimelineHomePromotedHydration -> DeciderKey.EnableTimelineHomePromotedHydrationSafetyLevel,
|
||||
TimelineHomeRecommendations -> DeciderKey.EnableTimelineHomeRecommendationsSafetyLevel,
|
||||
TimelineHomeTopicFollowRecommendations -> DeciderKey.EnableTimelineHomeTopicFollowRecommendationsSafetyLevel,
|
||||
TimelineScorer -> DeciderKey.EnableTimelineScorerSafetyLevel,
|
||||
TopicsLandingPageTopicRecommendations -> DeciderKey.EnableTopicsLandingPageTopicRecommendationsSafetyLevel,
|
||||
ExploreRecommendations -> DeciderKey.EnableExploreRecommendationsSafetyLevel,
|
||||
TimelineInjection -> DeciderKey.EnableTimelineInjectionSafetyLevel,
|
||||
TimelineMentions -> DeciderKey.EnableTimelineMentionsSafetyLevel,
|
||||
TimelineModeratedTweetsHydration -> DeciderKey.EnableTimelineModeratedTweetsHydrationSafetyLevel,
|
||||
TimelineHomeLatest -> DeciderKey.EnableTimelineHomeLatestSafetyLevel,
|
||||
TimelineLikedBy -> DeciderKey.EnableTimelineLikedBySafetyLevel,
|
||||
TimelineRetweetedBy -> DeciderKey.EnableTimelineRetweetedBySafetyLevel,
|
||||
TimelineSuperLikedBy -> DeciderKey.EnableTimelineSuperLikedBySafetyLevel,
|
||||
TimelineBookmark -> DeciderKey.EnableTimelineBookmarkSafetyLevel,
|
||||
TimelineMedia -> DeciderKey.EnableTimelineMediaSafetyLevel,
|
||||
TimelineReactiveBlending -> DeciderKey.EnableTimelineReactiveBlendingSafetyLevel,
|
||||
TimelineFavorites -> DeciderKey.EnableTimelineFavoritesSafetyLevel,
|
||||
TimelineFavoritesSelfView -> DeciderKey.EnableSelfViewTimelineFavoritesSafetyLevel,
|
||||
TimelineLists -> DeciderKey.EnableTimelineListsSafetyLevel,
|
||||
TimelineProfile -> DeciderKey.EnableTimelineProfileSafetyLevel,
|
||||
TimelineProfileAll -> DeciderKey.EnableTimelineProfileAllSafetyLevel,
|
||||
TimelineProfileSpaces -> DeciderKey.EnableTimelineProfileSpacesSafetyLevel,
|
||||
TimelineProfileSuperFollows -> DeciderKey.EnableTimelineProfileSuperFollowsSafetyLevel,
|
||||
TimelineFocalTweet -> DeciderKey.EnableTweetTimelineFocalTweetSafetyLevel,
|
||||
TweetDetailWithInjectionsHydration -> DeciderKey.EnableTweetDetailWithInjectionsHydrationSafetyLevel,
|
||||
Tombstoning -> DeciderKey.EnableTombstoningSafetyLevel,
|
||||
TopicRecommendations -> DeciderKey.EnableTopicRecommendationsSafetyLevel,
|
||||
TrendsRepresentativeTweet -> DeciderKey.EnableTrendsRepresentativeTweetSafetyLevel,
|
||||
TrustedFriendsUserList -> DeciderKey.EnableTrustedFriendsUserListSafetyLevel,
|
||||
TwitterDelegateUserList -> DeciderKey.EnableTwitterDelegateUserListSafetyLevel,
|
||||
TweetDetail -> DeciderKey.EnableTweetDetailSafetyLevel,
|
||||
TweetDetailNonToo -> DeciderKey.EnableTweetDetailNonTooSafetyLevel,
|
||||
TweetEngagers -> DeciderKey.EnableTweetEngagersSafetyLevel,
|
||||
TweetReplyNudge -> DeciderKey.EnableTweetReplyNudgeSafetyLevel,
|
||||
TweetScopedTimeline -> DeciderKey.EnableTweetScopedTimelineSafetyLevel,
|
||||
TweetWritesApi -> DeciderKey.EnableTweetWritesApiSafetyLevel,
|
||||
TwitterArticleCompose -> DeciderKey.EnableTwitterArticleComposeSafetyLevel,
|
||||
TwitterArticleProfileTab -> DeciderKey.EnableTwitterArticleProfileTabSafetyLevel,
|
||||
TwitterArticleRead -> DeciderKey.EnableTwitterArticleReadSafetyLevel,
|
||||
UserProfileHeader -> DeciderKey.EnableUserProfileHeaderSafetyLevel,
|
||||
UserMilestoneRecommendation -> DeciderKey.EnableUserMilestoneRecommendationSafetyLevel,
|
||||
UserScopedTimeline -> DeciderKey.EnableUserScopedTimelineSafetyLevel,
|
||||
UserSearchSrp -> DeciderKey.EnableUserSearchSrpSafetyLevel,
|
||||
UserSearchTypeahead -> DeciderKey.EnableUserSearchTypeaheadSafetyLevel,
|
||||
UserSelfViewOnly -> DeciderKey.EnableUserSelfViewOnlySafetyLevel,
|
||||
UserSettings -> DeciderKey.EnableUserSettingsSafetyLevel,
|
||||
VideoAds -> DeciderKey.EnableVideoAdsSafetyLevel,
|
||||
WritePathLimitedActionsEnforcement -> DeciderKey.EnableWritePathLimitedActionsEnforcementSafetyLevel,
|
||||
ZipbirdConsumerArchives -> DeciderKey.EnableZipbirdConsumerArchivesSafetyLevel,
|
||||
TweetAward -> DeciderKey.EnableTweetAwardSafetyLevel,
|
||||
)
|
||||
|
||||
val BoolToDeciderMap: Map[Param[Boolean], DeciderKey.Value] = Map(
|
||||
RuleParams.TweetConversationControlEnabledParam ->
|
||||
DeciderKey.EnableTweetConversationControlRules,
|
||||
RuleParams.CommunityTweetsEnabledParam ->
|
||||
DeciderKey.EnableCommunityTweetsControlRules,
|
||||
RuleParams.DropCommunityTweetWithUndefinedCommunityRuleEnabledParam ->
|
||||
DeciderKey.EnableDropCommunityTweetWithUndefinedCommunityRule,
|
||||
TimelineConversationsDownrankingSpecificParams.EnablePSpammyTweetDownrankConvosLowQualityParam ->
|
||||
DeciderKey.EnablePSpammyTweetDownrankConvosLowQuality,
|
||||
RuleParams.EnableHighPSpammyTweetScoreSearchTweetLabelDropRuleParam ->
|
||||
DeciderKey.EnableHighPSpammyTweetScoreSearchTweetLabelDropRule,
|
||||
TimelineConversationsDownrankingSpecificParams.EnableRitoActionedTweetDownrankConvosLowQualityParam ->
|
||||
DeciderKey.EnableRitoActionedTweetDownrankConvosLowQuality,
|
||||
RuleParams.EnableSmyteSpamTweetRuleParam ->
|
||||
DeciderKey.EnableSmyteSpamTweetRule,
|
||||
RuleParams.EnableHighSpammyTweetContentScoreSearchLatestTweetLabelDropRuleParam ->
|
||||
DeciderKey.EnableHighSpammyTweetContentScoreSearchLatestTweetLabelDropRule,
|
||||
RuleParams.EnableHighSpammyTweetContentScoreSearchTopTweetLabelDropRuleParam ->
|
||||
DeciderKey.EnableHighSpammyTweetContentScoreSearchTopTweetLabelDropRule,
|
||||
RuleParams.EnableHighSpammyTweetContentScoreTrendsTopTweetLabelDropRuleParam ->
|
||||
DeciderKey.EnableHighSpammyTweetContentScoreTrendsTopTweetLabelDropRule,
|
||||
RuleParams.EnableHighSpammyTweetContentScoreTrendsLatestTweetLabelDropRuleParam ->
|
||||
DeciderKey.EnableHighSpammyTweetContentScoreTrendsLatestTweetLabelDropRule,
|
||||
TimelineConversationsDownrankingSpecificParams.EnableHighSpammyTweetContentScoreConvoDownrankAbusiveQualityRuleParam ->
|
||||
DeciderKey.EnableHighSpammyTweetContentScoreConvoDownrankAbusiveQualityRule,
|
||||
TimelineConversationsDownrankingSpecificParams.EnableHighCryptospamScoreConvoDownrankAbusiveQualityRuleParam ->
|
||||
DeciderKey.EnableHighCryptospamScoreConvoDownrankAbusiveQualityRule,
|
||||
RuleParams.EnableGoreAndViolenceTopicHighRecallTweetLabelRule ->
|
||||
DeciderKey.EnableGoreAndViolenceTopicHighRecallTweetLabelRule,
|
||||
RuleParams.EnableLimitRepliesFollowersConversationRule ->
|
||||
DeciderKey.EnableLimitRepliesFollowersConversationRule,
|
||||
RuleParams.EnableSearchBasicBlockMuteRulesParam -> DeciderKey.EnableSearchBasicBlockMuteRules,
|
||||
RuleParams.EnableBlinkBadDownrankingRuleParam ->
|
||||
DeciderKey.EnableBlinkBadDownrankingRule,
|
||||
RuleParams.EnableBlinkWorstDownrankingRuleParam ->
|
||||
DeciderKey.EnableBlinkWorstDownrankingRule,
|
||||
RuleParams.EnableCopypastaSpamDownrankConvosAbusiveQualityRule ->
|
||||
DeciderKey.EnableCopypastaSpamDownrankConvosAbusiveQualityRule,
|
||||
RuleParams.EnableCopypastaSpamSearchDropRule ->
|
||||
DeciderKey.EnableCopypastaSpamSearchDropRule,
|
||||
RuleParams.EnableSpammyUserModelTweetDropRuleParam ->
|
||||
DeciderKey.EnableSpammyUserModelHighPrecisionDropTweetRule,
|
||||
RuleParams.EnableAvoidNsfwRulesParam ->
|
||||
DeciderKey.EnableAvoidNsfwRules,
|
||||
RuleParams.EnableReportedTweetInterstitialRule ->
|
||||
DeciderKey.EnableReportedTweetInterstitialRule,
|
||||
RuleParams.EnableReportedTweetInterstitialSearchRule ->
|
||||
DeciderKey.EnableReportedTweetInterstitialSearchRule,
|
||||
RuleParams.EnableDropExclusiveTweetContentRule ->
|
||||
DeciderKey.EnableDropExclusiveTweetContentRule,
|
||||
RuleParams.EnableDropExclusiveTweetContentRuleFailClosed ->
|
||||
DeciderKey.EnableDropExclusiveTweetContentRuleFailClosed,
|
||||
RuleParams.EnableTombstoneExclusiveQtProfileTimelineParam ->
|
||||
DeciderKey.EnableTombstoneExclusiveQtProfileTimelineParam,
|
||||
RuleParams.EnableDropAllExclusiveTweetsRuleParam -> DeciderKey.EnableDropAllExclusiveTweetsRule,
|
||||
RuleParams.EnableDropAllExclusiveTweetsRuleFailClosedParam -> DeciderKey.EnableDropAllExclusiveTweetsRuleFailClosed,
|
||||
RuleParams.EnableDownrankSpamReplySectioningRuleParam ->
|
||||
DeciderKey.EnableDownrankSpamReplySectioningRule,
|
||||
RuleParams.EnableNsfwTextSectioningRuleParam ->
|
||||
DeciderKey.EnableNsfwTextSectioningRule,
|
||||
RuleParams.EnableSearchIpiSafeSearchWithoutUserInQueryDropRule -> DeciderKey.EnableSearchIpiSafeSearchWithoutUserInQueryDropRule,
|
||||
RuleParams.EnableTimelineHomePromotedTweetHealthEnforcementRules -> DeciderKey.EnableTimelineHomePromotedTweetHealthEnforcementRules,
|
||||
RuleParams.EnableMutedKeywordFilteringSpaceTitleNotificationsRuleParam -> DeciderKey.EnableMutedKeywordFilteringSpaceTitleNotificationsRule,
|
||||
RuleParams.EnableDropTweetsWithGeoRestrictedMediaRuleParam -> DeciderKey.EnableDropTweetsWithGeoRestrictedMediaRule,
|
||||
RuleParams.EnableDropAllTrustedFriendsTweetsRuleParam -> DeciderKey.EnableDropAllTrustedFriendsTweetsRule,
|
||||
RuleParams.EnableDropTrustedFriendsTweetContentRuleParam -> DeciderKey.EnableDropTrustedFriendsTweetContentRule,
|
||||
RuleParams.EnableDropAllCollabInvitationTweetsRuleParam -> DeciderKey.EnableDropCollabInvitationTweetsRule,
|
||||
RuleParams.EnableNsfwTextHighPrecisionDropRuleParam -> DeciderKey.EnableNsfwTextHighPrecisionDropRule,
|
||||
RuleParams.EnableLikelyIvsUserLabelDropRule -> DeciderKey.EnableLikelyIvsUserLabelDropRule,
|
||||
RuleParams.EnableCardUriRootDomainCardDenylistRule -> DeciderKey.EnableCardUriRootDomainDenylistRule,
|
||||
RuleParams.EnableCommunityNonMemberPollCardRule -> DeciderKey.EnableCommunityNonMemberPollCardRule,
|
||||
RuleParams.EnableCommunityNonMemberPollCardRuleFailClosed -> DeciderKey.EnableCommunityNonMemberPollCardRuleFailClosed,
|
||||
RuleParams.EnableExperimentalNudgeEnabledParam -> DeciderKey.EnableExperimentalNudgeLabelRule,
|
||||
RuleParams.NsfwHighPrecisionUserLabelAvoidTweetRuleEnabledParam -> DeciderKey.NsfwHighPrecisionUserLabelAvoidTweetRuleEnabledParam,
|
||||
RuleParams.EnableNewAdAvoidanceRulesParam -> DeciderKey.EnableNewAdAvoidanceRules,
|
||||
RuleParams.EnableNsfaHighRecallAdAvoidanceParam -> DeciderKey.EnableNsfaHighRecallAdAvoidanceParam,
|
||||
RuleParams.EnableNsfaKeywordsHighPrecisionAdAvoidanceParam -> DeciderKey.EnableNsfaKeywordsHighPrecisionAdAvoidanceParam,
|
||||
RuleParams.EnableStaleTweetDropRuleParam -> DeciderKey.EnableStaleTweetDropRuleParam,
|
||||
RuleParams.EnableStaleTweetDropRuleFailClosedParam -> DeciderKey.EnableStaleTweetDropRuleFailClosedParam,
|
||||
RuleParams.EnableDeleteStateTweetRulesParam -> DeciderKey.EnableDeleteStateTweetRules,
|
||||
RuleParams.EnableSpacesSharingNsfwDropRulesParam -> DeciderKey.EnableSpacesSharingNsfwDropRulesParam,
|
||||
RuleParams.EnableViewerIsSoftUserDropRuleParam -> DeciderKey.EnableViewerIsSoftUserDropRuleParam,
|
||||
RuleParams.EnablePdnaQuotedTweetTombstoneRuleParam -> DeciderKey.EnablePdnaQuotedTweetTombstoneRule,
|
||||
RuleParams.EnableSpamQuotedTweetTombstoneRuleParam -> DeciderKey.EnableSpamQuotedTweetTombstoneRule,
|
||||
RuleParams.EnableNsfwHpQuotedTweetDropRuleParam -> DeciderKey.EnableNsfwHpQuotedTweetDropRule,
|
||||
RuleParams.EnableNsfwHpQuotedTweetTombstoneRuleParam -> DeciderKey.EnableNsfwHpQuotedTweetTombstoneRule,
|
||||
RuleParams.EnableInnerQuotedTweetViewerBlocksAuthorInterstitialRuleParam -> DeciderKey.EnableInnerQuotedTweetViewerBlocksAuthorInterstitialRule,
|
||||
RuleParams.EnableInnerQuotedTweetViewerMutesAuthorInterstitialRuleParam -> DeciderKey.EnableInnerQuotedTweetViewerMutesAuthorInterstitialRule,
|
||||
RuleParams.EnableToxicReplyFilteringConversationRulesParam -> DeciderKey.VisibilityLibraryEnableToxicReplyFilterConversation,
|
||||
RuleParams.EnableToxicReplyFilteringNotificationsRulesParam -> DeciderKey.VisibilityLibraryEnableToxicReplyFilterNotifications,
|
||||
RuleParams.EnableLegacySensitiveMediaHomeTimelineRulesParam -> DeciderKey.EnableLegacySensitiveMediaRulesHomeTimeline,
|
||||
RuleParams.EnableNewSensitiveMediaSettingsInterstitialsHomeTimelineRulesParam -> DeciderKey.EnableNewSensitiveMediaSettingsInterstitialRulesHomeTimeline,
|
||||
RuleParams.EnableLegacySensitiveMediaConversationRulesParam -> DeciderKey.EnableLegacySensitiveMediaRulesConversation,
|
||||
RuleParams.EnableNewSensitiveMediaSettingsInterstitialsConversationRulesParam -> DeciderKey.EnableNewSensitiveMediaSettingsInterstitialRulesConversation,
|
||||
RuleParams.EnableLegacySensitiveMediaProfileTimelineRulesParam -> DeciderKey.EnableLegacySensitiveMediaRulesProfileTimeline,
|
||||
RuleParams.EnableNewSensitiveMediaSettingsInterstitialsProfileTimelineRulesParam -> DeciderKey.EnableNewSensitiveMediaSettingsInterstitialRulesProfileTimeline,
|
||||
RuleParams.EnableLegacySensitiveMediaTweetDetailRulesParam -> DeciderKey.EnableLegacySensitiveMediaRulesTweetDetail,
|
||||
RuleParams.EnableNewSensitiveMediaSettingsInterstitialsTweetDetailRulesParam -> DeciderKey.EnableNewSensitiveMediaSettingsInterstitialRulesTweetDetail,
|
||||
RuleParams.EnableLegacySensitiveMediaDirectMessagesRulesParam -> DeciderKey.EnableLegacySensitiveMediaRulesDirectMessages,
|
||||
RuleParams.EnableAbusiveBehaviorDropRuleParam -> DeciderKey.EnableAbusiveBehaviorDropRule,
|
||||
RuleParams.EnableAbusiveBehaviorInterstitialRuleParam -> DeciderKey.EnableAbusiveBehaviorInterstitialRule,
|
||||
RuleParams.EnableAbusiveBehaviorLimitedEngagementsRuleParam -> DeciderKey.EnableAbusiveBehaviorLimitedEngagementsRule,
|
||||
RuleParams.EnableNotGraduatedDownrankConvosAbusiveQualityRuleParam -> DeciderKey.EnableNotGraduatedDownrankConvosAbusiveQualityRule,
|
||||
RuleParams.EnableNotGraduatedSearchDropRuleParam -> DeciderKey.EnableNotGraduatedSearchDropRule,
|
||||
RuleParams.EnableNotGraduatedDropRuleParam -> DeciderKey.EnableNotGraduatedDropRule,
|
||||
RuleParams.EnableFosnrRuleParam -> DeciderKey.EnableFosnrRules,
|
||||
RuleParams.EnableAuthorBlocksViewerDropRuleParam -> DeciderKey.EnableAuthorBlocksViewerDropRule
|
||||
)
|
||||
|
||||
def config(
|
||||
deciderGateBuilder: DeciderGateBuilder,
|
||||
logger: Logger,
|
||||
statsReceiver: StatsReceiver,
|
||||
SafetyLevel: SafetyLevel
|
||||
): Config = {
|
||||
|
||||
object UserOrGuestOrRequest extends RecipientBuilder {
|
||||
private val scopedStats = statsReceiver.scope("decider_recipient")
|
||||
private val userIdDefinedCounter = scopedStats.counter("user_id_defined")
|
||||
private val userIdNotDefinedCounter = scopedStats.counter("user_id_undefined")
|
||||
private val guestIdDefinedCounter = scopedStats.counter("guest_id_defined")
|
||||
private val guestIdNotDefinedCounter = scopedStats.counter("guest_id_undefined")
|
||||
private val noIdCounter = scopedStats.counter("no_id_defined")
|
||||
|
||||
def apply(requestContext: BaseRequestContext): Option[Recipient] = requestContext match {
|
||||
case c: WithUserId if c.userId.isDefined =>
|
||||
userIdDefinedCounter.incr()
|
||||
c.userId.map(SimpleRecipient)
|
||||
case c: WithGuestId if c.guestId.isDefined =>
|
||||
guestIdDefinedCounter.incr()
|
||||
c.guestId.map(GuestRecipient)
|
||||
case c: WithGuestId =>
|
||||
guestIdNotDefinedCounter.incr()
|
||||
RecipientBuilder.Request(c)
|
||||
case _: WithUserId =>
|
||||
userIdNotDefinedCounter.incr()
|
||||
None
|
||||
case _ =>
|
||||
logger.warning("Request Context with no user or guest id trait found: " + requestContext)
|
||||
noIdCounter.incr()
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
val boolOverrides = BoolToDeciderMap.map {
|
||||
case (param, deciderKey) =>
|
||||
param.optionalOverrideValue(
|
||||
DeciderSwitchOverrideValue(
|
||||
feature = deciderGateBuilder.keyToFeature(deciderKey),
|
||||
enabledValue = true,
|
||||
disabledValueOption = Some(false),
|
||||
recipientBuilder = UserOrGuestOrRequest
|
||||
)
|
||||
)
|
||||
}.toSeq
|
||||
|
||||
val safetyLevelOverride = SafetyLevel.enabledParam.optionalOverrideValue(
|
||||
DeciderSwitchOverrideValue(
|
||||
feature = deciderGateBuilder.keyToFeature(SafetyLevelToDeciderMap(SafetyLevel)),
|
||||
enabledValue = true,
|
||||
recipientBuilder = UserOrGuestOrRequest
|
||||
)
|
||||
)
|
||||
|
||||
BaseConfigBuilder(boolOverrides :+ safetyLevelOverride).build("VisibilityDeciders")
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package com.twitter.visibility.configapi.configs
|
||||
|
||||
import com.twitter.timelines.configapi.Config
|
||||
import com.twitter.visibility.configapi.params.RuleParams._
|
||||
import com.twitter.visibility.configapi.params.VisibilityExperiments._
|
||||
import com.twitter.visibility.models.SafetyLevel
|
||||
import com.twitter.visibility.models.SafetyLevel._
|
||||
|
||||
private[visibility] object VisibilityExperimentsConfig {
|
||||
import ExperimentsHelper._
|
||||
|
||||
val TestExperimentConfig: Config = mkABExperimentConfig(TestExperiment, TestHoldbackParam)
|
||||
|
||||
val NotGraduatedUserLabelRuleHoldbackExperimentConfig: Config =
|
||||
mkABExperimentConfig(
|
||||
NotGraduatedUserLabelRuleExperiment,
|
||||
NotGraduatedUserLabelRuleHoldbackExperimentParam
|
||||
)
|
||||
|
||||
def config(safetyLevel: SafetyLevel): Seq[Config] = {
|
||||
|
||||
val experimentConfigs = safetyLevel match {
|
||||
|
||||
case Test =>
|
||||
Seq(TestExperimentConfig)
|
||||
|
||||
case _ => Seq(NotGraduatedUserLabelRuleHoldbackExperimentConfig)
|
||||
}
|
||||
|
||||
experimentConfigs
|
||||
}
|
||||
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
package com.twitter.visibility.configapi.configs
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.logging.Logger
|
||||
import com.twitter.timelines.configapi._
|
||||
import com.twitter.util.Time
|
||||
import com.twitter.visibility.configapi.params.FSEnumRuleParam
|
||||
import com.twitter.visibility.configapi.params.FSRuleParams._
|
||||
|
||||
private[visibility] object VisibilityFeatureSwitches {
|
||||
|
||||
val booleanFsOverrides: Seq[OptionalOverride[Boolean]] =
|
||||
FeatureSwitchOverrideUtil.getBooleanFSOverrides(
|
||||
AgeGatingAdultContentExperimentRuleEnabledParam,
|
||||
CommunityTweetCommunityUnavailableLimitedActionsRulesEnabledParam,
|
||||
CommunityTweetDropProtectedRuleEnabledParam,
|
||||
CommunityTweetDropRuleEnabledParam,
|
||||
CommunityTweetLimitedActionsRulesEnabledParam,
|
||||
CommunityTweetMemberRemovedLimitedActionsRulesEnabledParam,
|
||||
CommunityTweetNonMemberLimitedActionsRuleEnabledParam,
|
||||
NsfwAgeBasedDropRulesHoldbackParam,
|
||||
SkipTweetDetailLimitedEngagementRuleEnabledParam,
|
||||
StaleTweetLimitedActionsRulesEnabledParam,
|
||||
TrustedFriendsTweetLimitedEngagementsRuleEnabledParam,
|
||||
FosnrFallbackDropRulesEnabledParam,
|
||||
FosnrRulesEnabledParam
|
||||
)
|
||||
|
||||
val doubleFsOverrides: Seq[OptionalOverride[Double]] =
|
||||
FeatureSwitchOverrideUtil.getBoundedDoubleFSOverrides(
|
||||
HighSpammyTweetContentScoreSearchTopProdTweetLabelDropRuleThresholdParam,
|
||||
HighSpammyTweetContentScoreSearchLatestProdTweetLabelDropRuleThresholdParam,
|
||||
HighSpammyTweetContentScoreTrendTopTweetLabelDropRuleThresholdParam,
|
||||
HighSpammyTweetContentScoreTrendLatestTweetLabelDropRuleThresholdParam,
|
||||
HighSpammyTweetContentScoreConvoDownrankAbusiveQualityThresholdParam,
|
||||
HighToxicityModelScoreSpaceThresholdParam,
|
||||
AdAvoidanceHighToxicityModelScoreThresholdParam,
|
||||
AdAvoidanceReportedTweetModelScoreThresholdParam,
|
||||
)
|
||||
|
||||
val timeFsOverrides: Seq[OptionalOverride[Time]] =
|
||||
FeatureSwitchOverrideUtil.getTimeFromStringFSOverrides()
|
||||
|
||||
val stringSeqFeatureSwitchOverrides: Seq[OptionalOverride[Seq[String]]] =
|
||||
FeatureSwitchOverrideUtil.getStringSeqFSOverrides(
|
||||
CountrySpecificNsfwContentGatingCountriesParam,
|
||||
AgeGatingAdultContentExperimentCountriesParam,
|
||||
CardUriRootDomainDenyListParam
|
||||
)
|
||||
|
||||
val enumFsParams: Seq[FSEnumRuleParam[_ <: Enumeration]] = Seq()
|
||||
|
||||
val mkOptionalEnumFsOverrides: (StatsReceiver, Logger) => Seq[OptionalOverride[_]] = {
|
||||
(statsReceiver: StatsReceiver, logger: Logger) =>
|
||||
FeatureSwitchOverrideUtil.getEnumFSOverrides(
|
||||
statsReceiver,
|
||||
logger,
|
||||
enumFsParams: _*
|
||||
)
|
||||
}
|
||||
|
||||
def overrides(statsReceiver: StatsReceiver, logger: Logger): Seq[OptionalOverride[_]] = {
|
||||
val enumOverrides = mkOptionalEnumFsOverrides(statsReceiver, logger)
|
||||
booleanFsOverrides ++
|
||||
doubleFsOverrides ++
|
||||
timeFsOverrides ++
|
||||
stringSeqFeatureSwitchOverrides ++
|
||||
enumOverrides
|
||||
}
|
||||
|
||||
def config(statsReceiver: StatsReceiver, logger: Logger): BaseConfig =
|
||||
BaseConfigBuilder(overrides(statsReceiver.scope("features_switches"), logger))
|
||||
.build("VisibilityFeatureSwitches")
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"decider",
|
||||
],
|
||||
)
|
|
@ -1,24 +0,0 @@
|
|||
package com.twitter.visibility.configapi.configs.overrides
|
||||
|
||||
import com.twitter.decider.LocalOverrides
|
||||
|
||||
object VisibilityLibraryDeciderOverrides
|
||||
extends LocalOverrides.Namespace("visibility-library", "") {
|
||||
|
||||
val EnableLocalizedTombstoneOnVisibilityResults = feature(
|
||||
"visibility_library_enable_localized_tombstones_on_visibility_results")
|
||||
|
||||
val EnableLocalizedInterstitialGenerator: LocalOverrides.Override =
|
||||
feature("visibility_library_enable_localized_interstitial_generator")
|
||||
|
||||
val EnableInnerQuotedTweetViewerBlocksAuthorInterstitialRule: LocalOverrides.Override =
|
||||
feature("visibility_library_enable_inner_quoted_tweet_viewer_blocks_author_interstitial_rule")
|
||||
val EnableInnerQuotedTweetViewerMutesAuthorInterstitialRule: LocalOverrides.Override =
|
||||
feature("visibility_library_enable_inner_quoted_tweet_viewer_mutes_author_interstitial_rule")
|
||||
|
||||
val EnableBackendLimitedActions: LocalOverrides.Override =
|
||||
feature("visibility_library_enable_backend_limited_actions")
|
||||
|
||||
val disableLegacyInterstitialFilteredReason: LocalOverrides.Override = feature(
|
||||
"visibility_library_disable_legacy_interstitial_filtered_reason")
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"configapi/configapi-core",
|
||||
"finagle/finagle-stats",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common:model_thresholds",
|
||||
],
|
||||
exports = [
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common:model_thresholds",
|
||||
],
|
||||
)
|
|
@ -1,213 +0,0 @@
|
|||
package com.twitter.visibility.configapi.params
|
||||
|
||||
import com.twitter.timelines.configapi.Bounded
|
||||
import com.twitter.timelines.configapi.FSBoundedParam
|
||||
import com.twitter.timelines.configapi.FSName
|
||||
import com.twitter.timelines.configapi.FeatureName
|
||||
import com.twitter.timelines.configapi.HasTimeConversion
|
||||
import com.twitter.timelines.configapi.TimeConversion
|
||||
import com.twitter.util.Time
|
||||
import com.twitter.visibility.common.ModelScoreThresholds
|
||||
|
||||
private[visibility] object FeatureSwitchKey extends Enumeration {
|
||||
type FeatureSwitchKey = String
|
||||
|
||||
final val HighSpammyTweetContentScoreSearchTopProdTweetLabelDropFuleThreshold =
|
||||
"high_spammy_tweet_content_score_search_top_prod_tweet_label_drop_rule_threshold"
|
||||
final val HighSpammyTweetContentScoreSearchLatestProdTweetLabelDropRuleThreshold =
|
||||
"high_spammy_tweet_content_score_search_latest_prod_tweet_label_drop_rule_threshold"
|
||||
final val HighSpammyTweetContentScoreTrendTopTweetLabelDropRuleThreshold =
|
||||
"high_spammy_tweet_content_score_trend_top_tweet_label_drop_rule_threshold"
|
||||
final val HighSpammyTweetContentScoreTrendLatestTweetLabelDropRuleThreshold =
|
||||
"high_spammy_tweet_content_score_trend_latest_tweet_label_drop_rule_threshold"
|
||||
final val HighSpammyTweetContentScoreConvoDownrankAbusiveQualityThreshold =
|
||||
"high_spammy_tweet_content_score_convos_downranking_abusive_quality_threshold"
|
||||
|
||||
final val NsfwAgeBasedDropRulesHoldbackParam =
|
||||
"nsfw_age_based_drop_rules_holdback"
|
||||
|
||||
final val CommunityTweetDropRuleEnabled =
|
||||
"community_tweet_drop_rule_enabled"
|
||||
final val CommunityTweetDropProtectedRuleEnabled =
|
||||
"community_tweet_drop_protected_rule_enabled"
|
||||
final val CommunityTweetLimitedActionsRulesEnabled =
|
||||
"community_tweet_limited_actions_rules_enabled"
|
||||
final val CommunityTweetMemberRemovedLimitedActionsRulesEnabled =
|
||||
"community_tweet_member_removed_limited_actions_rules_enabled"
|
||||
final val CommunityTweetCommunityUnavailableLimitedActionsRulesEnabled =
|
||||
"community_tweet_community_unavailable_limited_actions_rules_enabled"
|
||||
final val CommunityTweetNonMemberLimitedActionsRuleEnabled =
|
||||
"community_tweet_non_member_limited_actions_rule_enabled"
|
||||
|
||||
final val TrustedFriendsTweetLimitedEngagementsRuleEnabled =
|
||||
"trusted_friends_tweet_limited_engagements_rule_enabled"
|
||||
|
||||
final val CountrySpecificNsfwContentGatingCountries =
|
||||
"country_specific_nsfw_content_gating_countries"
|
||||
|
||||
final val AgeGatingAdultContentExperimentCountries =
|
||||
"age_gating_adult_content_experiment_countries"
|
||||
final val AgeGatingAdultContentExperimentEnabled =
|
||||
"age_gating_adult_content_experiment_enabled"
|
||||
|
||||
final val HighToxicityModelScoreSpaceThreshold =
|
||||
"high_toxicity_model_score_space_threshold"
|
||||
|
||||
final val CardUriRootDomainDenyList = "card_uri_root_domain_deny_list"
|
||||
|
||||
final val SkipTweetDetailLimitedEngagementsRuleEnabled =
|
||||
"skip_tweet_detail_limited_engagements_rule_enabled"
|
||||
|
||||
final val AdAvoidanceHighToxicityModelScoreThreshold =
|
||||
"ad_avoidance_model_thresholds_high_toxicity_model"
|
||||
final val AdAvoidanceReportedTweetModelScoreThreshold =
|
||||
"ad_avoidance_model_thresholds_reported_tweet_model"
|
||||
|
||||
final val StaleTweetLimitedActionsRulesEnabled =
|
||||
"stale_tweet_limited_actions_rules_enabled"
|
||||
|
||||
final val FosnrFallbackDropRulesEnabled =
|
||||
"freedom_of_speech_not_reach_fallback_drop_rules_enabled"
|
||||
final val FosnrRulesEnabled =
|
||||
"freedom_of_speech_not_reach_rules_enabled"
|
||||
}
|
||||
|
||||
abstract class FSRuleParam[T](override val name: FeatureName, override val default: T)
|
||||
extends RuleParam(default)
|
||||
with FSName
|
||||
|
||||
abstract class FSBoundedRuleParam[T](
|
||||
override val name: FeatureName,
|
||||
override val default: T,
|
||||
override val min: T,
|
||||
override val max: T
|
||||
)(
|
||||
implicit override val ordering: Ordering[T])
|
||||
extends RuleParam(default)
|
||||
with Bounded[T]
|
||||
with FSName
|
||||
|
||||
abstract class FSTimeRuleParam[T](
|
||||
override val name: FeatureName,
|
||||
override val default: Time,
|
||||
override val timeConversion: TimeConversion[T])
|
||||
extends RuleParam(default)
|
||||
with HasTimeConversion[T]
|
||||
with FSName
|
||||
|
||||
abstract class FSEnumRuleParam[T <: Enumeration](
|
||||
override val name: FeatureName,
|
||||
override val default: T#Value,
|
||||
override val enum: T)
|
||||
extends EnumRuleParam(default, enum)
|
||||
with FSName
|
||||
|
||||
private[visibility] object FSRuleParams {
|
||||
object HighSpammyTweetContentScoreSearchTopProdTweetLabelDropRuleThresholdParam
|
||||
extends FSBoundedParam(
|
||||
FeatureSwitchKey.HighSpammyTweetContentScoreSearchTopProdTweetLabelDropFuleThreshold,
|
||||
default = ModelScoreThresholds.HighSpammyTweetContentScoreDefaultThreshold,
|
||||
min = 0,
|
||||
max = 1)
|
||||
object HighSpammyTweetContentScoreSearchLatestProdTweetLabelDropRuleThresholdParam
|
||||
extends FSBoundedParam(
|
||||
FeatureSwitchKey.HighSpammyTweetContentScoreSearchLatestProdTweetLabelDropRuleThreshold,
|
||||
default = ModelScoreThresholds.HighSpammyTweetContentScoreDefaultThreshold,
|
||||
min = 0,
|
||||
max = 1)
|
||||
object HighSpammyTweetContentScoreTrendTopTweetLabelDropRuleThresholdParam
|
||||
extends FSBoundedParam(
|
||||
FeatureSwitchKey.HighSpammyTweetContentScoreTrendTopTweetLabelDropRuleThreshold,
|
||||
default = ModelScoreThresholds.HighSpammyTweetContentScoreDefaultThreshold,
|
||||
min = 0,
|
||||
max = 1)
|
||||
object HighSpammyTweetContentScoreTrendLatestTweetLabelDropRuleThresholdParam
|
||||
extends FSBoundedParam(
|
||||
FeatureSwitchKey.HighSpammyTweetContentScoreTrendLatestTweetLabelDropRuleThreshold,
|
||||
default = ModelScoreThresholds.HighSpammyTweetContentScoreDefaultThreshold,
|
||||
min = 0,
|
||||
max = 1)
|
||||
object HighSpammyTweetContentScoreConvoDownrankAbusiveQualityThresholdParam
|
||||
extends FSBoundedParam(
|
||||
FeatureSwitchKey.HighSpammyTweetContentScoreConvoDownrankAbusiveQualityThreshold,
|
||||
default = ModelScoreThresholds.HighSpammyTweetContentScoreDefaultThreshold,
|
||||
min = 0,
|
||||
max = 1)
|
||||
|
||||
object CommunityTweetDropRuleEnabledParam
|
||||
extends FSRuleParam(FeatureSwitchKey.CommunityTweetDropRuleEnabled, true)
|
||||
|
||||
object CommunityTweetDropProtectedRuleEnabledParam
|
||||
extends FSRuleParam(FeatureSwitchKey.CommunityTweetDropProtectedRuleEnabled, true)
|
||||
|
||||
object CommunityTweetLimitedActionsRulesEnabledParam
|
||||
extends FSRuleParam(FeatureSwitchKey.CommunityTweetLimitedActionsRulesEnabled, false)
|
||||
|
||||
object CommunityTweetMemberRemovedLimitedActionsRulesEnabledParam
|
||||
extends FSRuleParam(
|
||||
FeatureSwitchKey.CommunityTweetMemberRemovedLimitedActionsRulesEnabled,
|
||||
false)
|
||||
|
||||
object CommunityTweetCommunityUnavailableLimitedActionsRulesEnabledParam
|
||||
extends FSRuleParam(
|
||||
FeatureSwitchKey.CommunityTweetCommunityUnavailableLimitedActionsRulesEnabled,
|
||||
false)
|
||||
|
||||
object CommunityTweetNonMemberLimitedActionsRuleEnabledParam
|
||||
extends FSRuleParam(FeatureSwitchKey.CommunityTweetNonMemberLimitedActionsRuleEnabled, false)
|
||||
|
||||
object TrustedFriendsTweetLimitedEngagementsRuleEnabledParam
|
||||
extends FSRuleParam(FeatureSwitchKey.TrustedFriendsTweetLimitedEngagementsRuleEnabled, false)
|
||||
|
||||
object SkipTweetDetailLimitedEngagementRuleEnabledParam
|
||||
extends FSRuleParam(FeatureSwitchKey.SkipTweetDetailLimitedEngagementsRuleEnabled, false)
|
||||
|
||||
|
||||
object NsfwAgeBasedDropRulesHoldbackParam
|
||||
extends FSRuleParam(FeatureSwitchKey.NsfwAgeBasedDropRulesHoldbackParam, true)
|
||||
|
||||
object CountrySpecificNsfwContentGatingCountriesParam
|
||||
extends FSRuleParam[Seq[String]](
|
||||
FeatureSwitchKey.CountrySpecificNsfwContentGatingCountries,
|
||||
default = Seq("au"))
|
||||
|
||||
object AgeGatingAdultContentExperimentCountriesParam
|
||||
extends FSRuleParam[Seq[String]](
|
||||
FeatureSwitchKey.AgeGatingAdultContentExperimentCountries,
|
||||
default = Seq.empty)
|
||||
object AgeGatingAdultContentExperimentRuleEnabledParam
|
||||
extends FSRuleParam(FeatureSwitchKey.AgeGatingAdultContentExperimentEnabled, default = false)
|
||||
|
||||
object HighToxicityModelScoreSpaceThresholdParam
|
||||
extends FSBoundedParam(
|
||||
FeatureSwitchKey.HighToxicityModelScoreSpaceThreshold,
|
||||
default = ModelScoreThresholds.HighToxicityModelScoreSpaceDefaultThreshold,
|
||||
min = 0,
|
||||
max = 1)
|
||||
|
||||
object CardUriRootDomainDenyListParam
|
||||
extends FSRuleParam[Seq[String]](
|
||||
FeatureSwitchKey.CardUriRootDomainDenyList,
|
||||
default = Seq.empty)
|
||||
|
||||
object AdAvoidanceHighToxicityModelScoreThresholdParam
|
||||
extends FSBoundedParam(
|
||||
FeatureSwitchKey.AdAvoidanceHighToxicityModelScoreThreshold,
|
||||
default = ModelScoreThresholds.AdAvoidanceHighToxicityModelScoreDefaultThreshold,
|
||||
min = 0,
|
||||
max = 1)
|
||||
|
||||
object AdAvoidanceReportedTweetModelScoreThresholdParam
|
||||
extends FSBoundedParam(
|
||||
FeatureSwitchKey.AdAvoidanceReportedTweetModelScoreThreshold,
|
||||
default = ModelScoreThresholds.AdAvoidanceReportedTweetModelScoreDefaultThreshold,
|
||||
min = 0,
|
||||
max = 1)
|
||||
|
||||
object StaleTweetLimitedActionsRulesEnabledParam
|
||||
extends FSRuleParam(FeatureSwitchKey.StaleTweetLimitedActionsRulesEnabled, false)
|
||||
|
||||
object FosnrFallbackDropRulesEnabledParam
|
||||
extends FSRuleParam(FeatureSwitchKey.FosnrFallbackDropRulesEnabled, false)
|
||||
object FosnrRulesEnabledParam extends FSRuleParam(FeatureSwitchKey.FosnrRulesEnabled, true)
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package com.twitter.visibility.configapi.params
|
||||
|
||||
import com.twitter.timelines.configapi.Param
|
||||
|
||||
abstract class GlobalParam[T](override val default: T) extends Param(default) {
|
||||
override val statName: String = s"GlobalParam/${this.getClass.getSimpleName}"
|
||||
}
|
||||
|
||||
private[visibility] object GlobalParams {
|
||||
object EnableFetchingLabelMap extends GlobalParam(false)
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package com.twitter.visibility.configapi.params
|
||||
|
||||
import com.twitter.timelines.configapi.Param
|
||||
|
||||
abstract class LabelSourceParam(override val default: Boolean) extends Param(default) {
|
||||
override val statName: String = s"LabelSourceParam/${this.getClass.getSimpleName}"
|
||||
}
|
||||
|
||||
private[visibility] object LabelSourceParams {
|
||||
object FilterLabelsFromBot7174Param extends LabelSourceParam(false)
|
||||
|
||||
object FilterTweetsSmyteAutomationParamA extends LabelSourceParam(false)
|
||||
object FilterTweetsSmyteAutomationParamB extends LabelSourceParam(false)
|
||||
object FilterTweetsSmyteAutomationParamAB extends LabelSourceParam(false)
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
package com.twitter.visibility.configapi.params
|
||||
|
||||
import com.twitter.timelines.configapi.EnumParam
|
||||
import com.twitter.timelines.configapi.Param
|
||||
|
||||
abstract class RuleParam[T](override val default: T) extends Param(default) {
|
||||
override val statName: String = s"RuleParam/${this.getClass.getSimpleName}"
|
||||
}
|
||||
|
||||
abstract class EnumRuleParam[T <: Enumeration](override val default: T#Value, override val enum: T)
|
||||
extends EnumParam(default, enum) {
|
||||
override val statName: String = s"RuleParam/${this.getClass.getSimpleName}"
|
||||
}
|
||||
|
||||
private[visibility] object RuleParams {
|
||||
object True extends RuleParam(true)
|
||||
object False extends RuleParam(false)
|
||||
|
||||
object TestHoldbackParam extends RuleParam(true)
|
||||
|
||||
object TweetConversationControlEnabledParam extends RuleParam(default = false)
|
||||
|
||||
object EnableLimitRepliesFollowersConversationRule extends RuleParam(default = false)
|
||||
|
||||
object CommunityTweetsEnabledParam extends RuleParam(default = false)
|
||||
|
||||
object DropCommunityTweetWithUndefinedCommunityRuleEnabledParam extends RuleParam(default = false)
|
||||
|
||||
object EnableHighPSpammyTweetScoreSearchTweetLabelDropRuleParam extends RuleParam(false)
|
||||
|
||||
object EnableSmyteSpamTweetRuleParam extends RuleParam(false)
|
||||
|
||||
object EnableHighSpammyTweetContentScoreSearchLatestTweetLabelDropRuleParam
|
||||
extends RuleParam(false)
|
||||
|
||||
object EnableHighSpammyTweetContentScoreSearchTopTweetLabelDropRuleParam extends RuleParam(false)
|
||||
|
||||
object NotGraduatedUserLabelRuleHoldbackExperimentParam extends RuleParam(default = false)
|
||||
|
||||
object EnableGoreAndViolenceTopicHighRecallTweetLabelRule extends RuleParam(default = false)
|
||||
|
||||
object EnableBlinkBadDownrankingRuleParam extends RuleParam(false)
|
||||
object EnableBlinkWorstDownrankingRuleParam extends RuleParam(false)
|
||||
|
||||
object EnableHighSpammyTweetContentScoreTrendsTopTweetLabelDropRuleParam
|
||||
extends RuleParam(default = false)
|
||||
|
||||
object EnableHighSpammyTweetContentScoreTrendsLatestTweetLabelDropRuleParam
|
||||
extends RuleParam(default = false)
|
||||
|
||||
object EnableCopypastaSpamDownrankConvosAbusiveQualityRule extends RuleParam(default = false)
|
||||
object EnableCopypastaSpamSearchDropRule extends RuleParam(default = false)
|
||||
|
||||
object EnableSpammyUserModelTweetDropRuleParam extends RuleParam(default = false)
|
||||
|
||||
object EnableAvoidNsfwRulesParam extends RuleParam(false)
|
||||
|
||||
object EnableReportedTweetInterstitialRule extends RuleParam(default = false)
|
||||
|
||||
object EnableReportedTweetInterstitialSearchRule extends RuleParam(default = false)
|
||||
|
||||
object EnableDropExclusiveTweetContentRule extends RuleParam(default = false)
|
||||
|
||||
object EnableDropExclusiveTweetContentRuleFailClosed extends RuleParam(default = false)
|
||||
|
||||
object EnableTombstoneExclusiveQtProfileTimelineParam extends RuleParam(default = false)
|
||||
|
||||
object EnableDropAllExclusiveTweetsRuleParam extends RuleParam(false)
|
||||
object EnableDropAllExclusiveTweetsRuleFailClosedParam extends RuleParam(false)
|
||||
|
||||
object EnableDownrankSpamReplySectioningRuleParam extends RuleParam(default = false)
|
||||
object EnableNsfwTextSectioningRuleParam extends RuleParam(default = false)
|
||||
|
||||
object EnableSearchIpiSafeSearchWithoutUserInQueryDropRule extends RuleParam(false)
|
||||
|
||||
object PromotedTweetHealthEnforcementHoldback extends RuleParam(default = true)
|
||||
object EnableTimelineHomePromotedTweetHealthEnforcementRules extends RuleParam(default = false)
|
||||
|
||||
object EnableMutedKeywordFilteringSpaceTitleNotificationsRuleParam extends RuleParam(false)
|
||||
|
||||
object EnableDropTweetsWithGeoRestrictedMediaRuleParam extends RuleParam(default = false)
|
||||
|
||||
object EnableDropAllTrustedFriendsTweetsRuleParam extends RuleParam(false)
|
||||
object EnableDropTrustedFriendsTweetContentRuleParam extends RuleParam(false)
|
||||
|
||||
object EnableDropAllCollabInvitationTweetsRuleParam extends RuleParam(false)
|
||||
|
||||
object EnableNsfwTextHighPrecisionDropRuleParam extends RuleParam(false)
|
||||
|
||||
object EnableLikelyIvsUserLabelDropRule extends RuleParam(false)
|
||||
|
||||
object EnableCardUriRootDomainCardDenylistRule extends RuleParam(false)
|
||||
object EnableCommunityNonMemberPollCardRule extends RuleParam(false)
|
||||
object EnableCommunityNonMemberPollCardRuleFailClosed extends RuleParam(false)
|
||||
|
||||
object EnableExperimentalNudgeEnabledParam extends RuleParam(false)
|
||||
|
||||
object NsfwHighPrecisionUserLabelAvoidTweetRuleEnabledParam extends RuleParam(default = false)
|
||||
|
||||
object EnableNewAdAvoidanceRulesParam extends RuleParam(false)
|
||||
|
||||
object EnableNsfaHighRecallAdAvoidanceParam extends RuleParam(false)
|
||||
|
||||
object EnableNsfaKeywordsHighPrecisionAdAvoidanceParam extends RuleParam(false)
|
||||
|
||||
object EnableStaleTweetDropRuleParam extends RuleParam(false)
|
||||
object EnableStaleTweetDropRuleFailClosedParam extends RuleParam(false)
|
||||
|
||||
object EnableDeleteStateTweetRulesParam extends RuleParam(default = false)
|
||||
|
||||
object EnableSpacesSharingNsfwDropRulesParam extends RuleParam(default = true)
|
||||
|
||||
object EnableViewerIsSoftUserDropRuleParam extends RuleParam(default = false)
|
||||
|
||||
object EnablePdnaQuotedTweetTombstoneRuleParam extends RuleParam(default = true)
|
||||
object EnableSpamQuotedTweetTombstoneRuleParam extends RuleParam(default = true)
|
||||
|
||||
object EnableNsfwHpQuotedTweetDropRuleParam extends RuleParam(default = false)
|
||||
object EnableNsfwHpQuotedTweetTombstoneRuleParam extends RuleParam(default = false)
|
||||
|
||||
object EnableInnerQuotedTweetViewerBlocksAuthorInterstitialRuleParam
|
||||
extends RuleParam(default = false)
|
||||
object EnableInnerQuotedTweetViewerMutesAuthorInterstitialRuleParam
|
||||
extends RuleParam(default = false)
|
||||
|
||||
|
||||
object EnableNewSensitiveMediaSettingsInterstitialsHomeTimelineRulesParam extends RuleParam(false)
|
||||
|
||||
object EnableNewSensitiveMediaSettingsInterstitialsConversationRulesParam extends RuleParam(false)
|
||||
|
||||
object EnableNewSensitiveMediaSettingsInterstitialsProfileTimelineRulesParam
|
||||
extends RuleParam(false)
|
||||
|
||||
object EnableNewSensitiveMediaSettingsInterstitialsTweetDetailRulesParam extends RuleParam(false)
|
||||
|
||||
object EnableLegacySensitiveMediaHomeTimelineRulesParam extends RuleParam(true)
|
||||
|
||||
object EnableLegacySensitiveMediaConversationRulesParam extends RuleParam(true)
|
||||
|
||||
object EnableLegacySensitiveMediaProfileTimelineRulesParam extends RuleParam(true)
|
||||
|
||||
object EnableLegacySensitiveMediaTweetDetailRulesParam extends RuleParam(true)
|
||||
|
||||
object EnableLegacySensitiveMediaDirectMessagesRulesParam extends RuleParam(true)
|
||||
|
||||
object EnableToxicReplyFilteringConversationRulesParam extends RuleParam(false)
|
||||
object EnableToxicReplyFilteringNotificationsRulesParam extends RuleParam(false)
|
||||
|
||||
object EnableSearchQueryMatchesTweetAuthorConditionParam extends RuleParam(default = false)
|
||||
|
||||
object EnableSearchBasicBlockMuteRulesParam extends RuleParam(default = false)
|
||||
|
||||
object EnableAbusiveBehaviorDropRuleParam extends RuleParam(default = false)
|
||||
object EnableAbusiveBehaviorInterstitialRuleParam extends RuleParam(default = false)
|
||||
object EnableAbusiveBehaviorLimitedEngagementsRuleParam extends RuleParam(default = false)
|
||||
|
||||
object EnableNotGraduatedDownrankConvosAbusiveQualityRuleParam extends RuleParam(default = false)
|
||||
object EnableNotGraduatedSearchDropRuleParam extends RuleParam(default = false)
|
||||
object EnableNotGraduatedDropRuleParam extends RuleParam(default = false)
|
||||
|
||||
object EnableFosnrRuleParam extends RuleParam(default = false)
|
||||
|
||||
object EnableAuthorBlocksViewerDropRuleParam extends RuleParam(default = false)
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
package com.twitter.visibility.configapi.params
|
||||
|
||||
import com.twitter.timelines.configapi.Param
|
||||
|
||||
abstract class SafetyLevelParam(override val default: Boolean) extends Param(default) {
|
||||
override val statName: String = s"SafetyLevelParam/${this.getClass.getSimpleName}"
|
||||
}
|
||||
|
||||
private[visibility] object SafetyLevelParams {
|
||||
object EnableAccessInternalPromotedContentSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableAdsBusinessSettingsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableAdsCampaignSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableAdsManagerSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableAdsReportingDashboardSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableAllSubscribedListsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableAppealsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableArticleTweetTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableBaseQigSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableBirdwatchNoteAuthorSafetyLevel extends SafetyLevelParam(false)
|
||||
object EnableBirdwatchNoteTweetsTimelineSafetyLevel extends SafetyLevelParam(false)
|
||||
object EnableBirdwatchNeedsYourHelpNotificationsSafetyLevel extends SafetyLevelParam(false)
|
||||
object EnableBlockMuteUsersTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableBrandSafetySafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableCardPollVotingSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableCardsServiceSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableCommunitiesSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableContentControlToolInstallSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableConversationFocalPrehydrationSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableConversationFocalTweetSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableConversationInjectedTweetSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableConversationReplySafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableCuratedTrendsRepresentativeTweet extends SafetyLevelParam(default = false)
|
||||
object EnableCurationPolicyViolations extends SafetyLevelParam(default = false)
|
||||
object EnableDevPlatformGetListTweetsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDESFollowingAndFollowersUserListSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDESHomeTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDESQuoteTweetTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDESRealtimeSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDESRealtimeSpamEnrichmentSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDESRealtimeTweetFilterSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDESRetweetingUsersSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDesTweetDetailSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDESTweetLikingUsersSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDESUserBookmarksSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDESUserLikedTweetSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDESUserMentionsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDESUserTweetsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDevPlatformComplianceStreamSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDirectMessagesSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDirectMessagesConversationListSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDirectMessagesConversationTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDirectMessagesInboxSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDirectMessagesMutedUsersSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDirectMessagesPinnedSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableDirectMessagesSearchSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableEditHistoryTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableElevatedQuoteTweetTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableEmbeddedTweetSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableEmbedsPublicInterestNoticeSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableEmbedTweetMarkupSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableWritePathLimitedActionsEnforcementSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableFilterAllSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableFilterAllPlaceholderSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableFilterDefaultSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableFilterNoneSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableFollowedTopicsTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableFollowerConnectionsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableFollowingAndFollowersUserListSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableForDevelopmentOnlySafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableFriendsFollowingListSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableGraphqlDefaultSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableGryphonDecksAndColumnsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableHumanizationNudgeSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableKitchenSinkDevelopmentSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableListHeaderSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableListMembershipsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableListOwnershipsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableListRecommendationsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableListSearchSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableListSubscriptionsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableLivePipelineEngagementCountsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableLiveVideoTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableMagicRecsAggressiveSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableMagicRecsAggressiveV2SafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableMagicRecsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableMagicRecsV2SafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableMinimalSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableModeratedTweetsTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableMomentsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableNearbySafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableNewUserExperienceSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableNotificationsIbisSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableNotificationsPlatformSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableNotificationsPlatformPushSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableNotificationsQigSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableNotificationsReadSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableNotificationsTimelineDeviceFollowSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableNotificationsWriteSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableNotificationsWriterTweetHydratorSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableNotificationsWriterV2SafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableQuickPromoteTweetEligibilitySafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableQuoteTweetTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableRecommendationsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableRecommendationsWithoutNsfaSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableRecosVideoSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableRecosWritePathSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableRepliesGroupingSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableReportCenterSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableReturningUserExperienceFocalTweetSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableReturningUserExperienceSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableRevenueSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableRitoActionedTweetTimelineParam extends SafetyLevelParam(false)
|
||||
object EnableSafeSearchMinimalSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSafeSearchStrictSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSearchHydrationSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSearchLatestSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSearchTopSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSearchTopQigSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSearchPhotoSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object SearchTrendTakeoverPromotedTweetSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSearchVideoSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSearchBlenderUserRulesSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSearchLatestUserRulesSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSearchPeopleSearchResultPageSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSearchPeopleTypeaheadSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableUserSearchSrpSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableUserSearchTypeaheadSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSearchMixerSrpMinimalSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSearchMixerSrpStrictSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableShoppingManagerSpyModeSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSignalsReactionsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSignalsTweetReactingUsersSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSocialProofSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSoftInterventionPivotSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSpaceFleetlineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSpaceHomeTimelineUprankingSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSpaceJoinScreenSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSpaceNotificationsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSpacesSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSpacesParticipantsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSpaceNotificationSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSpacesSellerApplicationStatusSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSpacesSharingSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSpaceTweetAvatarHomeTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableStickersTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableStratoExtLimitedEngagementsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableStreamServicesSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSuperFollowerConnectionsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableSuperLikeSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTestSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineBookmarkSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineConversationsDownrankingSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineContentControlsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineConversationsDownrankingMinimalSafetyLevelParam
|
||||
extends SafetyLevelParam(false)
|
||||
object EnableTimelineConversationsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineFavoritesSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineFavoritesSelfViewSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineFocalTweetSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineFollowingActivitySafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineHomeCommunitiesSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineHomeHydrationSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineHomeLatestSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineHomePromotedHydrationSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineHomeRecommendationsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineHomeSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineHomeTopicFollowRecommendationsSafetyLevelParam
|
||||
extends SafetyLevelParam(false)
|
||||
object EnableTimelineScorerSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTopicsLandingPageTopicRecommendationsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableExploreRecommendationsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineInjectionSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineLikedBySafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineListsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineMediaSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineMentionsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineModeratedTweetsHydrationSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineProfileSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineProfileAllSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineProfileSpacesSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineProfileSuperFollowsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineReactiveBlendingSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineRetweetedBySafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTimelineSuperLikedBySafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTombstoningSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTopicRecommendationsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTrendsRepresentativeTweetSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTrustedFriendsUserListSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTwitterDelegateUserListSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTweetDetailSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTweetDetailNonTooSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTweetDetailWithInjectionsHydrationSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTweetEngagersSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTweetReplyNudgeParam extends SafetyLevelParam(false)
|
||||
object EnableTweetScopedTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTweetWritesApiSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTwitterArticleComposeSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTwitterArticleProfileTabSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTwitterArticleReadSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableUserProfileHeaderSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableProfileMixerMediaSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableProfileMixerFavoritesSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableUserMilestoneRecommendationSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableUserScopedTimelineSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableUserSelfViewOnlySafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableUserSettingsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableVideoAdsSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableZipbirdConsumerArchivesSafetyLevelParam extends SafetyLevelParam(false)
|
||||
object EnableTweetAwardSafetyLevelParam extends SafetyLevelParam(false)
|
||||
|
||||
object EnableDeprecatedSafetyLevel extends SafetyLevelParam(true)
|
||||
object EnableQuotedTweetRulesParam extends SafetyLevelParam(true)
|
||||
object EnableUnsupportedSafetyLevel extends SafetyLevelParam(true)
|
||||
object EnableUnknownSafetyLevel$ extends SafetyLevelParam(true)
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package com.twitter.visibility.configapi.params
|
||||
|
||||
private[visibility] object TimelineConversationsDownrankingSpecificParams {
|
||||
|
||||
object EnablePSpammyTweetDownrankConvosLowQualityParam extends RuleParam(false)
|
||||
|
||||
object EnableRitoActionedTweetDownrankConvosLowQualityParam extends RuleParam(false)
|
||||
|
||||
object EnableHighSpammyTweetContentScoreConvoDownrankAbusiveQualityRuleParam
|
||||
extends RuleParam(false)
|
||||
|
||||
object EnableHighCryptospamScoreConvoDownrankAbusiveQualityRuleParam extends RuleParam(false)
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package com.twitter.visibility.configapi.params
|
||||
|
||||
import com.twitter.timelines.configapi.BucketName
|
||||
import com.twitter.timelines.configapi.Experiment
|
||||
import com.twitter.timelines.configapi.UseFeatureContext
|
||||
|
||||
object VisibilityExperiment {
|
||||
val Control = "control"
|
||||
val Treatment = "treatment"
|
||||
}
|
||||
|
||||
abstract class VisibilityExperiment(experimentKey: String)
|
||||
extends Experiment(experimentKey)
|
||||
with UseFeatureContext {
|
||||
val TreatmentBucket: String = VisibilityExperiment.Treatment
|
||||
override def experimentBuckets: Set[BucketName] = Set(TreatmentBucket)
|
||||
val ControlBucket: String = VisibilityExperiment.Control
|
||||
override def controlBuckets: Set[BucketName] = Set(ControlBucket)
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package com.twitter.visibility.configapi.params
|
||||
|
||||
private[visibility] object VisibilityExperiments {
|
||||
|
||||
case object TestExperiment extends VisibilityExperiment("vf_test_ddg_7727")
|
||||
|
||||
object CommonBucketId extends Enumeration {
|
||||
type CommonBucketId = Value
|
||||
val Control = Value("control")
|
||||
val Treatment = Value("treatment")
|
||||
val None = Value("none")
|
||||
}
|
||||
|
||||
case object NotGraduatedUserLabelRuleExperiment
|
||||
extends VisibilityExperiment("not_graduated_user_holdback_16332")
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"abdecider/src/main/scala",
|
||||
"configapi/configapi-core",
|
||||
"servo/util/src/main/scala",
|
||||
"src/thrift/com/twitter/search/common:constants-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
|
||||
"src/thrift/com/twitter/tweetypie:tweet-scala",
|
||||
"stitch/stitch-core",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/params",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/features",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/models",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/rules",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/rules/providers",
|
||||
],
|
||||
)
|
|
@ -1,26 +0,0 @@
|
|||
package com.twitter.visibility.engine
|
||||
|
||||
import com.twitter.servo.util.Gate
|
||||
import com.twitter.spam.rtf.thriftscala.{SafetyLevel => ThriftSafetyLevel}
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.VisibilityResult
|
||||
import com.twitter.visibility.builder.VisibilityResultBuilder
|
||||
import com.twitter.visibility.models.SafetyLevel
|
||||
import com.twitter.visibility.rules.EvaluationContext
|
||||
import com.twitter.visibility.rules.Rule
|
||||
|
||||
trait DeciderableVisibilityRuleEngine {
|
||||
def apply(
|
||||
evaluationContext: EvaluationContext,
|
||||
safetyLevel: SafetyLevel,
|
||||
visibilityResultBuilder: VisibilityResultBuilder,
|
||||
enableShortCircuiting: Gate[Unit] = Gate.True,
|
||||
preprocessedRules: Option[Seq[Rule]] = None
|
||||
): Stitch[VisibilityResult]
|
||||
|
||||
def apply(
|
||||
evaluationContext: EvaluationContext,
|
||||
thriftSafetyLevel: ThriftSafetyLevel,
|
||||
visibilityResultBuilder: VisibilityResultBuilder
|
||||
): Stitch[VisibilityResult]
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
package com.twitter.visibility.engine
|
||||
|
||||
import com.twitter.finagle.stats.NullStatsReceiver
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.finagle.stats.Verbosity
|
||||
import com.twitter.servo.util.Gate
|
||||
import com.twitter.servo.util.MemoizingStatsReceiver
|
||||
import com.twitter.visibility.builder.VisibilityResult
|
||||
import com.twitter.visibility.features.Feature
|
||||
import com.twitter.visibility.models.SafetyLevel
|
||||
import com.twitter.visibility.rules.NotEvaluated
|
||||
import com.twitter.visibility.rules.RuleResult
|
||||
import com.twitter.visibility.rules.State
|
||||
import com.twitter.visibility.rules.State.Disabled
|
||||
import com.twitter.visibility.rules.State.FeatureFailed
|
||||
import com.twitter.visibility.rules.State.MissingFeature
|
||||
import com.twitter.visibility.rules.State.RuleFailed
|
||||
import com.twitter.visibility.rules.Action
|
||||
|
||||
|
||||
case class VisibilityResultsMetricRecorder(
|
||||
statsReceiver: StatsReceiver,
|
||||
captureDebugStats: Gate[Unit]) {
|
||||
|
||||
private val scopedStatsReceiver = new MemoizingStatsReceiver(
|
||||
statsReceiver.scope("visibility_rule_engine")
|
||||
)
|
||||
private val actionStats: StatsReceiver = scopedStatsReceiver.scope("by_action")
|
||||
private val featureFailureReceiver: StatsReceiver =
|
||||
scopedStatsReceiver.scope("feature_failed")
|
||||
private val safetyLevelStatsReceiver: StatsReceiver =
|
||||
scopedStatsReceiver.scope("from_safety_level")
|
||||
private val ruleStatsReceiver: StatsReceiver = scopedStatsReceiver.scope("for_rule")
|
||||
private val ruleFailureReceiver: StatsReceiver =
|
||||
scopedStatsReceiver.scope("rule_failures")
|
||||
private val failClosedReceiver: StatsReceiver =
|
||||
scopedStatsReceiver.scope("fail_closed")
|
||||
private val ruleStatsBySafetyLevelReceiver: StatsReceiver =
|
||||
scopedStatsReceiver.scope("for_rule_by_safety_level")
|
||||
|
||||
def recordSuccess(
|
||||
safetyLevel: SafetyLevel,
|
||||
result: VisibilityResult
|
||||
): Unit = {
|
||||
recordAction(safetyLevel, result.verdict.fullName)
|
||||
|
||||
val isFeatureFailure = result.ruleResultMap.values
|
||||
.collectFirst {
|
||||
case RuleResult(_, FeatureFailed(_)) =>
|
||||
ruleFailureReceiver.counter("feature_failed").incr()
|
||||
true
|
||||
}.getOrElse(false)
|
||||
|
||||
val isMissingFeature = result.ruleResultMap.values
|
||||
.collectFirst {
|
||||
case RuleResult(_, MissingFeature(_)) =>
|
||||
ruleFailureReceiver.counter("missing_feature").incr()
|
||||
true
|
||||
}.getOrElse(false)
|
||||
|
||||
val isRuleFailed = result.ruleResultMap.values
|
||||
.collectFirst {
|
||||
case RuleResult(_, RuleFailed(_)) =>
|
||||
ruleFailureReceiver.counter("rule_failed").incr()
|
||||
true
|
||||
}.getOrElse(false)
|
||||
|
||||
if (isFeatureFailure || isMissingFeature || isRuleFailed) {
|
||||
ruleFailureReceiver.counter().incr()
|
||||
}
|
||||
|
||||
if (captureDebugStats()) {
|
||||
val ruleBySafetyLevelStat =
|
||||
ruleStatsBySafetyLevelReceiver.scope(safetyLevel.name)
|
||||
result.ruleResultMap.foreach {
|
||||
case (rule, ruleResult) => {
|
||||
ruleBySafetyLevelStat
|
||||
.scope(rule.name)
|
||||
.scope("action")
|
||||
.counter(Verbosity.Debug, ruleResult.action.fullName).incr()
|
||||
ruleBySafetyLevelStat
|
||||
.scope(rule.name)
|
||||
.scope("state")
|
||||
.counter(Verbosity.Debug, ruleResult.state.name).incr()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def recordFailedFeature(
|
||||
failedFeature: Feature[_],
|
||||
exception: Throwable
|
||||
): Unit = {
|
||||
featureFailureReceiver.counter().incr()
|
||||
|
||||
val featureStat = featureFailureReceiver.scope(failedFeature.name)
|
||||
featureStat.counter().incr()
|
||||
featureStat.counter(exception.getClass.getName).incr()
|
||||
}
|
||||
|
||||
def recordAction(
|
||||
safetyLevel: SafetyLevel,
|
||||
action: String
|
||||
): Unit = {
|
||||
safetyLevelStatsReceiver.scope(safetyLevel.name).counter(action).incr()
|
||||
actionStats.counter(action).incr()
|
||||
}
|
||||
|
||||
def recordUnknownSafetyLevel(
|
||||
safetyLevel: SafetyLevel
|
||||
): Unit = {
|
||||
safetyLevelStatsReceiver
|
||||
.scope("unknown_safety_level")
|
||||
.counter(safetyLevel.name.toLowerCase).incr()
|
||||
}
|
||||
|
||||
def recordRuleMissingFeatures(
|
||||
ruleName: String,
|
||||
missingFeatures: Set[Feature[_]]
|
||||
): Unit = {
|
||||
val ruleStat = ruleStatsReceiver.scope(ruleName)
|
||||
missingFeatures.foreach { featureId =>
|
||||
ruleStat.scope("missing_feature").counter(featureId.name).incr()
|
||||
}
|
||||
ruleStat.scope("action").counter(NotEvaluated.fullName).incr()
|
||||
ruleStat.scope("state").counter(MissingFeature(missingFeatures).name).incr()
|
||||
}
|
||||
|
||||
def recordRuleFailedFeatures(
|
||||
ruleName: String,
|
||||
failedFeatures: Map[Feature[_], Throwable]
|
||||
): Unit = {
|
||||
val ruleStat = ruleStatsReceiver.scope(ruleName)
|
||||
|
||||
ruleStat.scope("action").counter(NotEvaluated.fullName).incr()
|
||||
ruleStat.scope("state").counter(FeatureFailed(failedFeatures).name).incr()
|
||||
}
|
||||
|
||||
def recordFailClosed(rule: String, state: State) {
|
||||
failClosedReceiver.scope(state.name).counter(rule).incr();
|
||||
}
|
||||
|
||||
def recordRuleEvaluation(
|
||||
ruleName: String,
|
||||
action: Action,
|
||||
state: State
|
||||
): Unit = {
|
||||
val ruleStat = ruleStatsReceiver.scope(ruleName)
|
||||
ruleStat.scope("action").counter(action.fullName).incr()
|
||||
ruleStat.scope("state").counter(state.name).incr()
|
||||
}
|
||||
|
||||
|
||||
def recordRuleFallbackAction(
|
||||
ruleName: String
|
||||
): Unit = {
|
||||
val ruleStat = ruleStatsReceiver.scope(ruleName)
|
||||
ruleStat.counter("fallback_action").incr()
|
||||
}
|
||||
|
||||
def recordRuleHoldBack(
|
||||
ruleName: String
|
||||
): Unit = {
|
||||
ruleStatsReceiver.scope(ruleName).counter("heldback").incr()
|
||||
}
|
||||
|
||||
def recordRuleFailed(
|
||||
ruleName: String
|
||||
): Unit = {
|
||||
ruleStatsReceiver.scope(ruleName).counter("failed").incr()
|
||||
}
|
||||
|
||||
def recordDisabledRule(
|
||||
ruleName: String
|
||||
): Unit = recordRuleEvaluation(ruleName, NotEvaluated, Disabled)
|
||||
}
|
||||
|
||||
object NullVisibilityResultsMetricsRecorder
|
||||
extends VisibilityResultsMetricRecorder(NullStatsReceiver, Gate.False)
|
|
@ -1,266 +0,0 @@
|
|||
package com.twitter.visibility.engine
|
||||
|
||||
import com.twitter.servo.util.Gate
|
||||
import com.twitter.spam.rtf.thriftscala.{SafetyLevel => ThriftSafetyLevel}
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.visibility.builder.VisibilityResult
|
||||
import com.twitter.visibility.builder.VisibilityResultBuilder
|
||||
import com.twitter.visibility.features._
|
||||
import com.twitter.visibility.models.SafetyLevel
|
||||
import com.twitter.visibility.models.SafetyLevel.DeprecatedSafetyLevel
|
||||
import com.twitter.visibility.rules.EvaluationContext
|
||||
import com.twitter.visibility.rules.State._
|
||||
import com.twitter.visibility.rules._
|
||||
import com.twitter.visibility.rules.providers.ProvidedEvaluationContext
|
||||
import com.twitter.visibility.rules.providers.PolicyProvider
|
||||
|
||||
class VisibilityRuleEngine private[VisibilityRuleEngine] (
|
||||
rulePreprocessor: VisibilityRulePreprocessor,
|
||||
metricsRecorder: VisibilityResultsMetricRecorder,
|
||||
enableComposableActions: Gate[Unit],
|
||||
enableFailClosed: Gate[Unit],
|
||||
policyProviderOpt: Option[PolicyProvider] = None)
|
||||
extends DeciderableVisibilityRuleEngine {
|
||||
|
||||
private[visibility] def apply(
|
||||
evaluationContext: ProvidedEvaluationContext,
|
||||
visibilityPolicy: VisibilityPolicy,
|
||||
visibilityResultBuilder: VisibilityResultBuilder,
|
||||
enableShortCircuiting: Gate[Unit],
|
||||
preprocessedRules: Option[Seq[Rule]]
|
||||
): Stitch[VisibilityResult] = {
|
||||
val (resultBuilder, rules) = preprocessedRules match {
|
||||
case Some(r) =>
|
||||
(visibilityResultBuilder, r)
|
||||
case None =>
|
||||
rulePreprocessor.evaluate(evaluationContext, visibilityPolicy, visibilityResultBuilder)
|
||||
}
|
||||
evaluate(evaluationContext, resultBuilder, rules, enableShortCircuiting)
|
||||
}
|
||||
|
||||
def apply(
|
||||
evaluationContext: EvaluationContext,
|
||||
safetyLevel: SafetyLevel,
|
||||
visibilityResultBuilder: VisibilityResultBuilder,
|
||||
enableShortCircuiting: Gate[Unit] = Gate.True,
|
||||
preprocessedRules: Option[Seq[Rule]] = None
|
||||
): Stitch[VisibilityResult] = {
|
||||
val visibilityPolicy = policyProviderOpt match {
|
||||
case Some(policyProvider) =>
|
||||
policyProvider.policyForSurface(safetyLevel)
|
||||
case None => RuleBase.RuleMap(safetyLevel)
|
||||
}
|
||||
if (evaluationContext.params(safetyLevel.enabledParam)) {
|
||||
apply(
|
||||
ProvidedEvaluationContext.injectRuntimeRulesIntoEvaluationContext(
|
||||
evaluationContext = evaluationContext,
|
||||
safetyLevel = Some(safetyLevel),
|
||||
policyProviderOpt = policyProviderOpt
|
||||
),
|
||||
visibilityPolicy,
|
||||
visibilityResultBuilder,
|
||||
enableShortCircuiting,
|
||||
preprocessedRules
|
||||
).onSuccess { result =>
|
||||
metricsRecorder.recordSuccess(safetyLevel, result)
|
||||
}
|
||||
.onFailure { _ =>
|
||||
metricsRecorder.recordAction(safetyLevel, "failure")
|
||||
}
|
||||
} else {
|
||||
metricsRecorder.recordAction(safetyLevel, "disabled")
|
||||
val rules: Seq[Rule] = visibilityPolicy.forContentId(visibilityResultBuilder.contentId)
|
||||
Stitch.value(
|
||||
visibilityResultBuilder
|
||||
.withRuleResultMap(rules.map(r => r -> RuleResult(Allow, Skipped)).toMap)
|
||||
.withVerdict(verdict = Allow)
|
||||
.withFinished(finished = true)
|
||||
.build
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def apply(
|
||||
evaluationContext: EvaluationContext,
|
||||
thriftSafetyLevel: ThriftSafetyLevel,
|
||||
visibilityResultBuilder: VisibilityResultBuilder
|
||||
): Stitch[VisibilityResult] = {
|
||||
val safetyLevel: SafetyLevel = SafetyLevel.fromThrift(thriftSafetyLevel)
|
||||
safetyLevel match {
|
||||
case DeprecatedSafetyLevel =>
|
||||
metricsRecorder.recordUnknownSafetyLevel(safetyLevel)
|
||||
Stitch.value(
|
||||
visibilityResultBuilder
|
||||
.withVerdict(verdict = Allow)
|
||||
.withFinished(finished = true)
|
||||
.build
|
||||
)
|
||||
|
||||
case thriftSafetyLevel: SafetyLevel =>
|
||||
this(
|
||||
ProvidedEvaluationContext.injectRuntimeRulesIntoEvaluationContext(
|
||||
evaluationContext = evaluationContext,
|
||||
safetyLevel = Some(safetyLevel),
|
||||
policyProviderOpt = policyProviderOpt
|
||||
),
|
||||
thriftSafetyLevel,
|
||||
visibilityResultBuilder
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private[visibility] def evaluateRules(
|
||||
evaluationContext: ProvidedEvaluationContext,
|
||||
resolvedFeatureMap: Map[Feature[_], Any],
|
||||
failedFeatures: Map[Feature[_], Throwable],
|
||||
resultBuilderWithoutFailedFeatures: VisibilityResultBuilder,
|
||||
preprocessedRules: Seq[Rule],
|
||||
enableShortCircuiting: Gate[Unit]
|
||||
): VisibilityResultBuilder = {
|
||||
preprocessedRules
|
||||
.foldLeft(resultBuilderWithoutFailedFeatures) { (builder, rule) =>
|
||||
builder.ruleResults.get(rule) match {
|
||||
case Some(RuleResult(_, state)) if state == Evaluated || state == ShortCircuited =>
|
||||
builder
|
||||
|
||||
case _ =>
|
||||
val failedFeatureDependencies: Map[Feature[_], Throwable] =
|
||||
failedFeatures.filterKeys(key => rule.featureDependencies.contains(key))
|
||||
|
||||
val shortCircuit =
|
||||
builder.finished && enableShortCircuiting() &&
|
||||
!(enableComposableActions() && builder.isVerdictComposable())
|
||||
|
||||
if (failedFeatureDependencies.nonEmpty && rule.fallbackActionBuilder.isEmpty) {
|
||||
metricsRecorder.recordRuleFailedFeatures(rule.name, failedFeatureDependencies)
|
||||
builder.withRuleResult(
|
||||
rule,
|
||||
RuleResult(NotEvaluated, FeatureFailed(failedFeatureDependencies)))
|
||||
|
||||
} else if (shortCircuit) {
|
||||
|
||||
metricsRecorder.recordRuleEvaluation(rule.name, NotEvaluated, ShortCircuited)
|
||||
builder.withRuleResult(rule, RuleResult(builder.verdict, ShortCircuited))
|
||||
} else {
|
||||
|
||||
if (failedFeatureDependencies.nonEmpty && rule.fallbackActionBuilder.nonEmpty) {
|
||||
metricsRecorder.recordRuleFallbackAction(rule.name)
|
||||
}
|
||||
|
||||
|
||||
val ruleResult =
|
||||
rule.evaluate(evaluationContext, resolvedFeatureMap)
|
||||
metricsRecorder
|
||||
.recordRuleEvaluation(rule.name, ruleResult.action, ruleResult.state)
|
||||
val nextBuilder = (ruleResult.action, builder.finished) match {
|
||||
case (NotEvaluated | Allow, _) =>
|
||||
ruleResult.state match {
|
||||
case Heldback =>
|
||||
metricsRecorder.recordRuleHoldBack(rule.name)
|
||||
case RuleFailed(_) =>
|
||||
metricsRecorder.recordRuleFailed(rule.name)
|
||||
case _ =>
|
||||
}
|
||||
builder.withRuleResult(rule, ruleResult)
|
||||
|
||||
case (_, true) =>
|
||||
builder
|
||||
.withRuleResult(rule, ruleResult)
|
||||
.withSecondaryVerdict(ruleResult.action, rule)
|
||||
|
||||
case _ =>
|
||||
builder
|
||||
.withRuleResult(rule, ruleResult)
|
||||
.withVerdict(ruleResult.action, Some(rule))
|
||||
.withFinished(true)
|
||||
}
|
||||
|
||||
nextBuilder
|
||||
}
|
||||
}
|
||||
}.withResolvedFeatureMap(resolvedFeatureMap)
|
||||
}
|
||||
|
||||
private[visibility] def evaluateFailClosed(
|
||||
evaluationContext: ProvidedEvaluationContext
|
||||
): VisibilityResultBuilder => Stitch[VisibilityResultBuilder] = { builder =>
|
||||
builder.failClosedException(evaluationContext) match {
|
||||
case Some(e: FailClosedException) if enableFailClosed() =>
|
||||
metricsRecorder.recordFailClosed(e.getRuleName, e.getState);
|
||||
Stitch.exception(e)
|
||||
case _ => Stitch.value(builder)
|
||||
}
|
||||
}
|
||||
|
||||
private[visibility] def checkMarkFinished(
|
||||
builder: VisibilityResultBuilder
|
||||
): VisibilityResult = {
|
||||
val allRulesEvaluated: Boolean = builder.ruleResults.values.forall {
|
||||
case RuleResult(_, state) =>
|
||||
state == Evaluated || state == Disabled || state == Skipped
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
|
||||
if (allRulesEvaluated) {
|
||||
builder.withFinished(true).build
|
||||
} else {
|
||||
builder.build
|
||||
}
|
||||
}
|
||||
|
||||
private[visibility] def evaluate(
|
||||
evaluationContext: ProvidedEvaluationContext,
|
||||
visibilityResultBuilder: VisibilityResultBuilder,
|
||||
preprocessedRules: Seq[Rule],
|
||||
enableShortCircuiting: Gate[Unit] = Gate.True
|
||||
): Stitch[VisibilityResult] = {
|
||||
|
||||
val finalBuilder =
|
||||
FeatureMap.resolve(visibilityResultBuilder.features, evaluationContext.statsReceiver).map {
|
||||
resolvedFeatureMap =>
|
||||
val (failedFeatureMap, successfulFeatureMap) = resolvedFeatureMap.constantMap.partition({
|
||||
case (_, _: FeatureFailedPlaceholderObject) => true
|
||||
case _ => false
|
||||
})
|
||||
|
||||
val failedFeatures: Map[Feature[_], Throwable] =
|
||||
failedFeatureMap.mapValues({
|
||||
case failurePlaceholder: FeatureFailedPlaceholderObject =>
|
||||
failurePlaceholder.throwable
|
||||
})
|
||||
|
||||
val resultBuilderWithoutFailedFeatures =
|
||||
visibilityResultBuilder.withFeatureMap(ResolvedFeatureMap(successfulFeatureMap))
|
||||
|
||||
evaluateRules(
|
||||
evaluationContext,
|
||||
successfulFeatureMap,
|
||||
failedFeatures,
|
||||
resultBuilderWithoutFailedFeatures,
|
||||
preprocessedRules,
|
||||
enableShortCircuiting
|
||||
)
|
||||
}
|
||||
|
||||
finalBuilder.flatMap(evaluateFailClosed(evaluationContext)).map(checkMarkFinished)
|
||||
}
|
||||
}
|
||||
|
||||
object VisibilityRuleEngine {
|
||||
|
||||
def apply(
|
||||
rulePreprocessor: Option[VisibilityRulePreprocessor] = None,
|
||||
metricsRecorder: VisibilityResultsMetricRecorder = NullVisibilityResultsMetricsRecorder,
|
||||
enableComposableActions: Gate[Unit] = Gate.False,
|
||||
enableFailClosed: Gate[Unit] = Gate.False,
|
||||
policyProviderOpt: Option[PolicyProvider] = None,
|
||||
): VisibilityRuleEngine = {
|
||||
new VisibilityRuleEngine(
|
||||
rulePreprocessor.getOrElse(VisibilityRulePreprocessor(metricsRecorder)),
|
||||
metricsRecorder,
|
||||
enableComposableActions,
|
||||
enableFailClosed,
|
||||
policyProviderOpt = policyProviderOpt)
|
||||
}
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
package com.twitter.visibility.engine
|
||||
|
||||
import com.twitter.abdecider.NullABDecider
|
||||
import com.twitter.util.Return
|
||||
import com.twitter.util.Throw
|
||||
import com.twitter.util.Try
|
||||
import com.twitter.visibility.builder.VisibilityResultBuilder
|
||||
import com.twitter.visibility.features._
|
||||
import com.twitter.visibility.models.SafetyLevel
|
||||
import com.twitter.visibility.rules.Rule.DisabledRuleResult
|
||||
import com.twitter.visibility.rules.Rule.EvaluatedRuleResult
|
||||
import com.twitter.visibility.rules.State._
|
||||
import com.twitter.visibility.rules._
|
||||
import com.twitter.visibility.rules.providers.ProvidedEvaluationContext
|
||||
import com.twitter.visibility.rules.providers.PolicyProvider
|
||||
|
||||
class VisibilityRulePreprocessor private (
|
||||
metricsRecorder: VisibilityResultsMetricRecorder,
|
||||
policyProviderOpt: Option[PolicyProvider] = None) {
|
||||
|
||||
private[engine] def filterEvaluableRules(
|
||||
evaluationContext: ProvidedEvaluationContext,
|
||||
resultBuilder: VisibilityResultBuilder,
|
||||
rules: Seq[Rule]
|
||||
): (VisibilityResultBuilder, Seq[Rule]) = {
|
||||
val (builder, ruleList) = rules.foldLeft((resultBuilder, Seq.empty[Rule])) {
|
||||
case ((builder, nextPassRules), rule) =>
|
||||
if (evaluationContext.ruleEnabledInContext(rule)) {
|
||||
val missingFeatures: Set[Feature[_]] = rule.featureDependencies.collect {
|
||||
case feature: Feature[_] if !builder.featureMap.contains(feature) => feature
|
||||
}
|
||||
|
||||
if (missingFeatures.isEmpty) {
|
||||
(builder, nextPassRules :+ rule)
|
||||
} else {
|
||||
metricsRecorder.recordRuleMissingFeatures(rule.name, missingFeatures)
|
||||
(
|
||||
builder.withRuleResult(
|
||||
rule,
|
||||
RuleResult(NotEvaluated, MissingFeature(missingFeatures))
|
||||
),
|
||||
nextPassRules
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(builder.withRuleResult(rule, DisabledRuleResult), nextPassRules)
|
||||
}
|
||||
}
|
||||
(builder, ruleList)
|
||||
}
|
||||
|
||||
private[visibility] def preFilterRules(
|
||||
evaluationContext: ProvidedEvaluationContext,
|
||||
resolvedFeatureMap: Map[Feature[_], Any],
|
||||
resultBuilder: VisibilityResultBuilder,
|
||||
rules: Seq[Rule]
|
||||
): (VisibilityResultBuilder, Seq[Rule]) = {
|
||||
val isResolvedFeatureMap = resultBuilder.featureMap.isInstanceOf[ResolvedFeatureMap]
|
||||
val (builder, ruleList) = rules.foldLeft((resultBuilder, Seq.empty[Rule])) {
|
||||
case ((builder, nextPassRules), rule) =>
|
||||
rule.preFilter(evaluationContext, resolvedFeatureMap, NullABDecider) match {
|
||||
case NeedsFullEvaluation =>
|
||||
(builder, nextPassRules :+ rule)
|
||||
case NotFiltered =>
|
||||
(builder, nextPassRules :+ rule)
|
||||
case Filtered if isResolvedFeatureMap =>
|
||||
(builder, nextPassRules :+ rule)
|
||||
case Filtered =>
|
||||
(builder.withRuleResult(rule, EvaluatedRuleResult), nextPassRules)
|
||||
}
|
||||
}
|
||||
(builder, ruleList)
|
||||
}
|
||||
|
||||
private[visibility] def evaluate(
|
||||
evaluationContext: ProvidedEvaluationContext,
|
||||
safetyLevel: SafetyLevel,
|
||||
resultBuilder: VisibilityResultBuilder
|
||||
): (VisibilityResultBuilder, Seq[Rule]) = {
|
||||
val visibilityPolicy = policyProviderOpt match {
|
||||
case Some(policyProvider) =>
|
||||
policyProvider.policyForSurface(safetyLevel)
|
||||
case None => RuleBase.RuleMap(safetyLevel)
|
||||
}
|
||||
|
||||
if (evaluationContext.params(safetyLevel.enabledParam)) {
|
||||
evaluate(evaluationContext, visibilityPolicy, resultBuilder)
|
||||
} else {
|
||||
metricsRecorder.recordAction(safetyLevel, "disabled")
|
||||
|
||||
val rules: Seq[Rule] = visibilityPolicy.forContentId(resultBuilder.contentId)
|
||||
val skippedResultBuilder = resultBuilder
|
||||
.withRuleResultMap(rules.map(r => r -> RuleResult(Allow, Skipped)).toMap)
|
||||
.withVerdict(verdict = Allow)
|
||||
.withFinished(finished = true)
|
||||
|
||||
(skippedResultBuilder, rules)
|
||||
}
|
||||
}
|
||||
|
||||
private[visibility] def evaluate(
|
||||
evaluationContext: ProvidedEvaluationContext,
|
||||
visibilityPolicy: VisibilityPolicy,
|
||||
resultBuilder: VisibilityResultBuilder,
|
||||
): (VisibilityResultBuilder, Seq[Rule]) = {
|
||||
|
||||
val rules: Seq[Rule] = visibilityPolicy.forContentId(resultBuilder.contentId)
|
||||
|
||||
val (secondPassBuilder, secondPassRules) =
|
||||
filterEvaluableRules(evaluationContext, resultBuilder, rules)
|
||||
|
||||
val secondPassFeatureMap = secondPassBuilder.featureMap
|
||||
|
||||
val secondPassConstantFeatures: Set[Feature[_]] = RuleBase
|
||||
.getFeaturesForRules(secondPassRules)
|
||||
.filter(secondPassFeatureMap.containsConstant(_))
|
||||
|
||||
val secondPassFeatureValues: Set[(Feature[_], Any)] = secondPassConstantFeatures.map {
|
||||
feature =>
|
||||
Try(secondPassFeatureMap.getConstant(feature)) match {
|
||||
case Return(value) => (feature, value)
|
||||
case Throw(ex) =>
|
||||
metricsRecorder.recordFailedFeature(feature, ex)
|
||||
(feature, FeatureFailedPlaceholderObject(ex))
|
||||
}
|
||||
}
|
||||
|
||||
val resolvedFeatureMap: Map[Feature[_], Any] =
|
||||
secondPassFeatureValues.filterNot {
|
||||
case (_, value) => value.isInstanceOf[FeatureFailedPlaceholderObject]
|
||||
}.toMap
|
||||
|
||||
val (preFilteredResultBuilder, preFilteredRules) = preFilterRules(
|
||||
evaluationContext,
|
||||
resolvedFeatureMap,
|
||||
secondPassBuilder,
|
||||
secondPassRules
|
||||
)
|
||||
|
||||
val preFilteredFeatureMap =
|
||||
RuleBase.removeUnusedFeaturesFromFeatureMap(
|
||||
preFilteredResultBuilder.featureMap,
|
||||
preFilteredRules)
|
||||
|
||||
(preFilteredResultBuilder.withFeatureMap(preFilteredFeatureMap), preFilteredRules)
|
||||
}
|
||||
}
|
||||
|
||||
object VisibilityRulePreprocessor {
|
||||
def apply(
|
||||
metricsRecorder: VisibilityResultsMetricRecorder,
|
||||
policyProviderOpt: Option[PolicyProvider] = None
|
||||
): VisibilityRulePreprocessor = {
|
||||
new VisibilityRulePreprocessor(metricsRecorder, policyProviderOpt)
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package com.twitter.visibility.features
|
||||
|
||||
import com.twitter.gizmoduck.thriftscala.MentionFilter
|
||||
import com.twitter.util.Duration
|
||||
|
||||
case object ViewerFiltersNoConfirmedEmail extends Feature[Boolean]
|
||||
|
||||
case object ViewerFiltersNoConfirmedPhone extends Feature[Boolean]
|
||||
|
||||
case object ViewerFiltersDefaultProfileImage extends Feature[Boolean]
|
||||
|
||||
case object ViewerFiltersNewUsers extends Feature[Boolean]
|
||||
|
||||
case object ViewerFiltersNotFollowedBy extends Feature[Boolean]
|
||||
|
||||
case object ViewerMentionFilter extends Feature[MentionFilter]
|
||||
|
||||
case object AuthorHasConfirmedEmail extends Feature[Boolean]
|
||||
|
||||
case object AuthorHasVerifiedPhone extends Feature[Boolean]
|
||||
|
||||
case object AuthorHasDefaultProfileImage extends Feature[Boolean]
|
||||
|
||||
case object AuthorAccountAge extends Feature[Duration]
|
|
@ -1,17 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/com/squareup/okhttp:okhttp3",
|
||||
"finagle/finagle-mux/src/main/scala",
|
||||
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
|
||||
"src/thrift/com/twitter/search/common:constants-scala",
|
||||
"src/thrift/com/twitter/tweetypie:tweet-scala",
|
||||
"stitch/stitch-core",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/models",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/util",
|
||||
],
|
||||
)
|
|
@ -1,11 +0,0 @@
|
|||
package com.twitter.visibility.features
|
||||
|
||||
import com.twitter.visibility.util.NamingUtils
|
||||
|
||||
abstract class Feature[T] protected ()(implicit val manifest: Manifest[T]) {
|
||||
|
||||
lazy val name: String = NamingUtils.getFriendlyName(this)
|
||||
|
||||
override lazy val toString: String =
|
||||
"Feature[%s](name=%s)".format(manifest, getClass.getSimpleName)
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
package com.twitter.visibility.features
|
||||
|
||||
import com.twitter.finagle.mux.ClientDiscardedRequestException
|
||||
import com.twitter.finagle.stats.NullStatsReceiver
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.stitch.Stitch
|
||||
import scala.language.existentials
|
||||
|
||||
class MissingFeatureException(feature: Feature[_]) extends Exception("Missing value for " + feature)
|
||||
|
||||
case class FeatureFailedException(feature: Feature[_], exception: Throwable) extends Exception
|
||||
|
||||
private[visibility] case class FeatureFailedPlaceholderObject(throwable: Throwable)
|
||||
|
||||
class FeatureMap(
|
||||
val map: Map[Feature[_], Stitch[_]],
|
||||
val constantMap: Map[Feature[_], Any]) {
|
||||
|
||||
def contains[T](feature: Feature[T]): Boolean =
|
||||
constantMap.contains(feature) || map.contains(feature)
|
||||
|
||||
def containsConstant[T](feature: Feature[T]): Boolean = constantMap.contains(feature)
|
||||
|
||||
lazy val size: Int = keys.size
|
||||
|
||||
lazy val keys: Set[Feature[_]] = constantMap.keySet ++ map.keySet
|
||||
|
||||
def get[T](feature: Feature[T]): Stitch[T] = {
|
||||
map.get(feature) match {
|
||||
case _ if constantMap.contains(feature) =>
|
||||
Stitch.value(getConstant(feature))
|
||||
case Some(x) =>
|
||||
x.asInstanceOf[Stitch[T]]
|
||||
case _ =>
|
||||
Stitch.exception(new MissingFeatureException(feature))
|
||||
}
|
||||
}
|
||||
|
||||
def getConstant[T](feature: Feature[T]): T = {
|
||||
constantMap.get(feature) match {
|
||||
case Some(x) =>
|
||||
x.asInstanceOf[T]
|
||||
case _ =>
|
||||
throw new MissingFeatureException(feature)
|
||||
}
|
||||
}
|
||||
|
||||
def -[T](key: Feature[T]): FeatureMap = new FeatureMap(map - key, constantMap - key)
|
||||
|
||||
override def toString: String = "FeatureMap(%s, %s)".format(map, constantMap)
|
||||
}
|
||||
|
||||
object FeatureMap {
|
||||
|
||||
def empty: FeatureMap = new FeatureMap(Map.empty, Map.empty)
|
||||
|
||||
def resolve(
|
||||
featureMap: FeatureMap,
|
||||
statsReceiver: StatsReceiver = NullStatsReceiver
|
||||
): Stitch[ResolvedFeatureMap] = {
|
||||
val featureMapHydrationStatsReceiver = statsReceiver.scope("feature_map_hydration")
|
||||
|
||||
Stitch
|
||||
.traverse(featureMap.map.toSeq) {
|
||||
case (feature, value: Stitch[_]) =>
|
||||
val featureStatsReceiver = featureMapHydrationStatsReceiver.scope(feature.name)
|
||||
lazy val featureFailureStat = featureStatsReceiver.scope("failures")
|
||||
val featureStitch: Stitch[(Feature[_], Any)] = value
|
||||
.map { resolvedValue =>
|
||||
featureStatsReceiver.counter("success").incr()
|
||||
(feature, resolvedValue)
|
||||
}
|
||||
|
||||
featureStitch
|
||||
.handle {
|
||||
case ffe: FeatureFailedException =>
|
||||
featureFailureStat.counter().incr()
|
||||
featureFailureStat.counter(ffe.exception.getClass.getName).incr()
|
||||
(feature, FeatureFailedPlaceholderObject(ffe.exception))
|
||||
}
|
||||
.ensure {
|
||||
featureStatsReceiver.counter("requests").incr()
|
||||
}
|
||||
}
|
||||
.map { resolvedFeatures: Seq[(Feature[_], Any)] =>
|
||||
new ResolvedFeatureMap(resolvedFeatures.toMap ++ featureMap.constantMap)
|
||||
}
|
||||
}
|
||||
|
||||
def rescueFeatureTuple(kv: (Feature[_], Stitch[_])): (Feature[_], Stitch[_]) = {
|
||||
val (k, v) = kv
|
||||
|
||||
val rescueValue = v.rescue {
|
||||
case e =>
|
||||
e match {
|
||||
case cdre: ClientDiscardedRequestException => Stitch.exception(cdre)
|
||||
case _ => Stitch.exception(FeatureFailedException(k, e))
|
||||
}
|
||||
}
|
||||
|
||||
(k, rescueValue)
|
||||
}
|
||||
}
|
||||
|
||||
class ResolvedFeatureMap(private[visibility] val resolvedMap: Map[Feature[_], Any])
|
||||
extends FeatureMap(Map.empty, resolvedMap) {
|
||||
|
||||
override def equals(other: Any): Boolean = other match {
|
||||
case otherResolvedFeatureMap: ResolvedFeatureMap =>
|
||||
this.resolvedMap.equals(otherResolvedFeatureMap.resolvedMap)
|
||||
case _ => false
|
||||
}
|
||||
|
||||
override def toString: String = "ResolvedFeatureMap(%s)".format(resolvedMap)
|
||||
}
|
||||
|
||||
object ResolvedFeatureMap {
|
||||
def apply(resolvedMap: Map[Feature[_], Any]): ResolvedFeatureMap = {
|
||||
new ResolvedFeatureMap(resolvedMap)
|
||||
}
|
||||
}
|
|
@ -1,269 +0,0 @@
|
|||
package com.twitter.visibility.features
|
||||
|
||||
import com.twitter.contenthealth.toxicreplyfilter.thriftscala.FilterState
|
||||
import com.twitter.gizmoduck.thriftscala.Label
|
||||
import com.twitter.search.common.constants.thriftscala.ThriftQuerySource
|
||||
import com.twitter.tseng.withholding.thriftscala.TakedownReason
|
||||
import com.twitter.util.Duration
|
||||
import com.twitter.util.Time
|
||||
import com.twitter.visibility.models.TweetDeleteReason.TweetDeleteReason
|
||||
import com.twitter.visibility.models._
|
||||
|
||||
case object AuthorId extends Feature[Set[Long]]
|
||||
|
||||
case object ViewerId extends Feature[Long]
|
||||
|
||||
case object AuthorIsProtected extends Feature[Boolean]
|
||||
|
||||
case object AuthorIsSuspended extends Feature[Boolean]
|
||||
|
||||
case object AuthorIsUnavailable extends Feature[Boolean]
|
||||
|
||||
case object AuthorIsDeactivated extends Feature[Boolean]
|
||||
|
||||
case object AuthorIsErased extends Feature[Boolean]
|
||||
|
||||
case object AuthorIsOffboarded extends Feature[Boolean]
|
||||
|
||||
case object AuthorIsVerified extends Feature[Boolean]
|
||||
|
||||
case object AuthorIsBlueVerified extends Feature[Boolean]
|
||||
|
||||
case object ViewerIsSuspended extends Feature[Boolean]
|
||||
|
||||
case object ViewerIsDeactivated extends Feature[Boolean]
|
||||
|
||||
case object AuthorFollowsViewer extends Feature[Boolean]
|
||||
|
||||
case object AuthorUserLabels extends Feature[Seq[Label]]
|
||||
|
||||
case object ViewerFollowsAuthorOfViolatingTweet extends Feature[Boolean]
|
||||
|
||||
case object ViewerDoesNotFollowAuthorOfViolatingTweet extends Feature[Boolean]
|
||||
|
||||
case object ViewerFollowsAuthor extends Feature[Boolean]
|
||||
|
||||
case object ViewerBlocksAuthor extends Feature[Boolean]
|
||||
|
||||
case object AuthorBlocksViewer extends Feature[Boolean]
|
||||
|
||||
case object AuthorMutesViewer extends Feature[Boolean]
|
||||
|
||||
case object ViewerMutesAuthor extends Feature[Boolean]
|
||||
|
||||
case object AuthorReportsViewerAsSpam extends Feature[Boolean]
|
||||
|
||||
case object ViewerReportsAuthorAsSpam extends Feature[Boolean]
|
||||
|
||||
case object ViewerReportedTweet extends Feature[Boolean]
|
||||
|
||||
case object ViewerMutesRetweetsFromAuthor extends Feature[Boolean]
|
||||
|
||||
case object ViewerHasUniversalQualityFilterEnabled extends Feature[Boolean]
|
||||
|
||||
case object ViewerIsProtected extends Feature[Boolean]
|
||||
|
||||
case object ViewerIsSoftUser extends Feature[Boolean]
|
||||
|
||||
case object TweetSafetyLabels extends Feature[Seq[TweetSafetyLabel]]
|
||||
|
||||
case object SpaceSafetyLabels extends Feature[Seq[SpaceSafetyLabel]]
|
||||
|
||||
case object MediaSafetyLabels extends Feature[Seq[MediaSafetyLabel]]
|
||||
|
||||
case object TweetTakedownReasons extends Feature[Seq[TakedownReason]]
|
||||
|
||||
case object AuthorTakedownReasons extends Feature[Seq[TakedownReason]]
|
||||
|
||||
case object AuthorIsNsfwUser extends Feature[Boolean]
|
||||
|
||||
case object AuthorIsNsfwAdmin extends Feature[Boolean]
|
||||
|
||||
case object TweetHasNsfwUser extends Feature[Boolean]
|
||||
|
||||
case object TweetHasNsfwAdmin extends Feature[Boolean]
|
||||
|
||||
case object TweetHasMedia extends Feature[Boolean]
|
||||
|
||||
case object CardHasMedia extends Feature[Boolean]
|
||||
|
||||
case object TweetHasCard extends Feature[Boolean]
|
||||
|
||||
case object ViewerMutesKeywordInTweetForHomeTimeline extends Feature[MutedKeyword]
|
||||
|
||||
case object ViewerMutesKeywordInTweetForTweetReplies extends Feature[MutedKeyword]
|
||||
|
||||
case object ViewerMutesKeywordInTweetForNotifications extends Feature[MutedKeyword]
|
||||
|
||||
case object ViewerMutesKeywordInSpaceTitleForNotifications extends Feature[MutedKeyword]
|
||||
|
||||
case object ViewerMutesKeywordInTweetForAllSurfaces extends Feature[MutedKeyword]
|
||||
|
||||
case object ViewerUserLabels extends Feature[Seq[Label]]
|
||||
|
||||
case object RequestCountryCode extends Feature[String]
|
||||
|
||||
case object RequestIsVerifiedCrawler extends Feature[Boolean]
|
||||
|
||||
case object ViewerCountryCode extends Feature[String]
|
||||
|
||||
case object TweetIsSelfReply extends Feature[Boolean]
|
||||
|
||||
case object TweetIsNullcast extends Feature[Boolean]
|
||||
|
||||
case object TweetTimestamp extends Feature[Time]
|
||||
|
||||
case object TweetIsInnerQuotedTweet extends Feature[Boolean]
|
||||
|
||||
case object TweetIsRetweet extends Feature[Boolean]
|
||||
|
||||
case object TweetIsSourceTweet extends Feature[Boolean]
|
||||
|
||||
case object TweetDeleteReason extends Feature[TweetDeleteReason]
|
||||
|
||||
case object TweetReplyToParentTweetDuration extends Feature[Duration]
|
||||
|
||||
case object TweetReplyToRootTweetDuration extends Feature[Duration]
|
||||
|
||||
case object TweetHasCommunityConversationControl extends Feature[Boolean]
|
||||
case object TweetHasByInvitationConversationControl extends Feature[Boolean]
|
||||
case object TweetHasFollowersConversationControl extends Feature[Boolean]
|
||||
case object TweetConversationViewerIsInvited extends Feature[Boolean]
|
||||
case object TweetConversationViewerIsInvitedViaReplyMention extends Feature[Boolean]
|
||||
case object TweetConversationViewerIsRootAuthor extends Feature[Boolean]
|
||||
case object ConversationRootAuthorFollowsViewer extends Feature[Boolean]
|
||||
case object ViewerFollowsConversationRootAuthor extends Feature[Boolean]
|
||||
|
||||
case object TweetIsExclusiveTweet extends Feature[Boolean]
|
||||
case object ViewerIsExclusiveTweetRootAuthor extends Feature[Boolean]
|
||||
case object ViewerSuperFollowsExclusiveTweetRootAuthor extends Feature[Boolean]
|
||||
|
||||
case object TweetIsCommunityTweet extends Feature[Boolean]
|
||||
|
||||
case object CommunityTweetCommunityNotFound extends Feature[Boolean]
|
||||
|
||||
case object CommunityTweetCommunityDeleted extends Feature[Boolean]
|
||||
|
||||
case object CommunityTweetCommunitySuspended extends Feature[Boolean]
|
||||
|
||||
case object CommunityTweetCommunityVisible extends Feature[Boolean]
|
||||
|
||||
case object CommunityTweetIsHidden extends Feature[Boolean]
|
||||
|
||||
case object ViewerIsInternalCommunitiesAdmin extends Feature[Boolean]
|
||||
|
||||
case object ViewerIsCommunityAdmin extends Feature[Boolean]
|
||||
|
||||
case object ViewerIsCommunityModerator extends Feature[Boolean]
|
||||
|
||||
case object ViewerIsCommunityMember extends Feature[Boolean]
|
||||
|
||||
case object CommunityTweetAuthorIsRemoved extends Feature[Boolean]
|
||||
|
||||
case object NotificationIsOnCommunityTweet extends Feature[Boolean]
|
||||
|
||||
case object NotificationIsOnUnmentionedViewer extends Feature[Boolean]
|
||||
|
||||
case object SearchResultsPageNumber extends Feature[Int]
|
||||
|
||||
case object SearchCandidateCount extends Feature[Int]
|
||||
|
||||
case object SearchQuerySource extends Feature[ThriftQuerySource]
|
||||
|
||||
case object SearchQueryHasUser extends Feature[Boolean]
|
||||
|
||||
case object TweetSemanticCoreAnnotations extends Feature[Seq[SemanticCoreAnnotation]]
|
||||
|
||||
case object OuterAuthorId extends Feature[Long]
|
||||
|
||||
case object AuthorBlocksOuterAuthor extends Feature[Boolean]
|
||||
|
||||
case object OuterAuthorFollowsAuthor extends Feature[Boolean]
|
||||
|
||||
case object OuterAuthorIsInnerAuthor extends Feature[Boolean]
|
||||
|
||||
case object TweetIsModerated extends Feature[Boolean]
|
||||
case object FocalTweetId extends Feature[Long]
|
||||
|
||||
case object TweetId extends Feature[Long]
|
||||
|
||||
case object TweetConversationId extends Feature[Long]
|
||||
case object TweetParentId extends Feature[Long]
|
||||
case object ConversationRootAuthorIsVerified extends Feature[Boolean]
|
||||
|
||||
case object ViewerOptInBlocking extends Feature[Boolean]
|
||||
|
||||
case object ViewerOptInFiltering extends Feature[Boolean]
|
||||
|
||||
case object ViewerRoles extends Feature[Seq[String]] {
|
||||
val EmployeeRole = "employee"
|
||||
}
|
||||
|
||||
case object TweetMisinformationPolicies extends Feature[Seq[MisinformationPolicy]]
|
||||
|
||||
case object TweetEnglishMisinformationPolicies extends Feature[Seq[MisinformationPolicy]]
|
||||
|
||||
case object HasInnerCircleOfFriendsRelationship extends Feature[Boolean]
|
||||
|
||||
case object ViewerAge extends Feature[UserAge]
|
||||
|
||||
case object HasDmcaMediaFeature extends Feature[Boolean]
|
||||
|
||||
case object MediaGeoRestrictionsAllowList extends Feature[Seq[String]]
|
||||
case object MediaGeoRestrictionsDenyList extends Feature[Seq[String]]
|
||||
|
||||
case object TweetIsTrustedFriendTweet extends Feature[Boolean]
|
||||
case object ViewerIsTrustedFriendTweetAuthor extends Feature[Boolean]
|
||||
case object ViewerIsTrustedFriendOfTweetAuthor extends Feature[Boolean]
|
||||
|
||||
case object DmConversationIsOneToOneConversation extends Feature[Boolean]
|
||||
case object DmConversationHasEmptyTimeline extends Feature[Boolean]
|
||||
case object DmConversationHasValidLastReadableEventId extends Feature[Boolean]
|
||||
case object DmConversationInfoExists extends Feature[Boolean]
|
||||
case object DmConversationTimelineExists extends Feature[Boolean]
|
||||
case object ViewerIsDmConversationParticipant extends Feature[Boolean]
|
||||
|
||||
case object DmEventIsMessageCreateEvent extends Feature[Boolean]
|
||||
case object DmEventIsWelcomeMessageCreateEvent extends Feature[Boolean]
|
||||
case object DmEventIsLastMessageReadUpdateEvent extends Feature[Boolean]
|
||||
case object DmEventIsDeleted extends Feature[Boolean]
|
||||
case object DmEventIsHidden extends Feature[Boolean]
|
||||
case object ViewerIsDmEventInitiatingUser extends Feature[Boolean]
|
||||
case object DmEventInOneToOneConversationWithUnavailableUser extends Feature[Boolean]
|
||||
case object DmEventIsJoinConversationEvent extends Feature[Boolean]
|
||||
case object DmEventIsConversationCreateEvent extends Feature[Boolean]
|
||||
case object DmEventInOneToOneConversation extends Feature[Boolean]
|
||||
case object DmEventIsTrustConversationEvent extends Feature[Boolean]
|
||||
case object DmEventIsCsFeedbackSubmitted extends Feature[Boolean]
|
||||
case object DmEventIsCsFeedbackDismissed extends Feature[Boolean]
|
||||
case object DmEventIsPerspectivalJoinConversationEvent extends Feature[Boolean]
|
||||
|
||||
case object DmEventOccurredBeforeLastClearedEvent extends Feature[Boolean]
|
||||
case object DmEventOccurredBeforeJoinConversationEvent extends Feature[Boolean]
|
||||
|
||||
case object CardUriHost extends Feature[String]
|
||||
case object CardIsPoll extends Feature[Boolean]
|
||||
|
||||
case object TweetIsStaleTweet extends Feature[Boolean]
|
||||
|
||||
case object TweetIsEditTweet extends Feature[Boolean]
|
||||
|
||||
case object TweetIsLatestTweet extends Feature[Boolean]
|
||||
|
||||
case object TweetIsInitialTweet extends Feature[Boolean]
|
||||
|
||||
case object TweetIsCollabInvitationTweet extends Feature[Boolean]
|
||||
|
||||
case object ViewerSensitiveMediaSettings extends Feature[UserSensitiveMediaSettings]
|
||||
|
||||
|
||||
case object ToxicReplyFilterState extends Feature[FilterState]
|
||||
|
||||
|
||||
case object ToxicReplyFilterConversationAuthorIsViewer extends Feature[Boolean]
|
||||
|
||||
case object RawQuery extends Feature[String]
|
||||
|
||||
case object AuthorScreenName extends Feature[String]
|
||||
|
||||
case object TweetIsInternalPromotedContent extends Feature[Boolean]
|
|
@ -1,30 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/com/ibm/icu:icu4j",
|
||||
"configapi/configapi-core",
|
||||
"decider/src/main/scala",
|
||||
"src/thrift/com/twitter/gizmoduck:thrift-scala",
|
||||
"src/thrift/com/twitter/gizmoduck:user-thrift-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-result-scala",
|
||||
"stitch/stitch-core",
|
||||
"strato/src/main/scala/com/twitter/strato/client",
|
||||
"twitter-config/yaml",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common/actions",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common/user_result",
|
||||
"visibility/common/src/main/thrift/com/twitter/visibility:action-scala",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/configapi",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/configs",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/features",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/models",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/rules",
|
||||
"visibility/results/src/main/scala/com/twitter/visibility/results/richtext",
|
||||
"visibility/results/src/main/scala/com/twitter/visibility/results/translation",
|
||||
],
|
||||
)
|
|
@ -1,58 +0,0 @@
|
|||
package com.twitter.visibility.generators
|
||||
|
||||
import com.ibm.icu.util.ULocale
|
||||
import com.twitter.config.yaml.YamlMap
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
|
||||
object CountryNameGenerator {
|
||||
|
||||
private val AuroraFilesystemPath = "/usr/local/twitter-config/twitter/config/"
|
||||
|
||||
private val ContentBlockingSupportedCountryList = "takedown_countries.yml"
|
||||
|
||||
def providesFromConfigBus(statsReceiver: StatsReceiver): CountryNameGenerator = {
|
||||
fromFile(AuroraFilesystemPath + ContentBlockingSupportedCountryList, statsReceiver)
|
||||
}
|
||||
|
||||
def providesWithCustomMap(countryCodeMap: Map[String, String], statsReceiver: StatsReceiver) = {
|
||||
new CountryNameGenerator(countryCodeMap, statsReceiver)
|
||||
}
|
||||
|
||||
private def fromFile(fileName: String, statsReceiver: StatsReceiver) = {
|
||||
val yamlConfig = YamlMap.load(fileName)
|
||||
val countryCodeMap: Map[String, String] = yamlConfig.keySet.map { countryCode: String =>
|
||||
val normalizedCode = countryCode.toUpperCase
|
||||
val countryName: Option[String] =
|
||||
yamlConfig.get(Seq(countryCode, "name")).asInstanceOf[Option[String]]
|
||||
(normalizedCode, countryName.getOrElse(normalizedCode))
|
||||
}.toMap
|
||||
new CountryNameGenerator(countryCodeMap, statsReceiver)
|
||||
}
|
||||
}
|
||||
|
||||
class CountryNameGenerator(countryCodeMap: Map[String, String], statsReceiver: StatsReceiver) {
|
||||
|
||||
private val scopedStatsReceiver = statsReceiver.scope("country_name_generator")
|
||||
private val foundCountryReceiver = scopedStatsReceiver.counter("found")
|
||||
private val missingCountryReceiver = scopedStatsReceiver.counter("missing")
|
||||
|
||||
def getCountryName(code: String): String = {
|
||||
val normalizedCode = code.toUpperCase
|
||||
countryCodeMap.get(normalizedCode) match {
|
||||
case Some(retrievedName) => {
|
||||
foundCountryReceiver.incr()
|
||||
retrievedName
|
||||
}
|
||||
case _ => {
|
||||
missingCountryReceiver.incr()
|
||||
val fallbackName =
|
||||
new ULocale("", normalizedCode).getDisplayCountry(ULocale.forLanguageTag("en"))
|
||||
|
||||
if (fallbackName == "")
|
||||
normalizedCode
|
||||
else
|
||||
fallbackName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package com.twitter.visibility.generators
|
||||
|
||||
import com.twitter.visibility.common.actions.LocalizedMessage
|
||||
import com.twitter.visibility.common.actions.MessageLink
|
||||
import com.twitter.visibility.results.translation.Translator
|
||||
import com.twitter.visibility.results.richtext.EpitaphToRichText
|
||||
import com.twitter.visibility.results.translation.Resource
|
||||
import com.twitter.visibility.results.translation.LearnMoreLink
|
||||
import com.twitter.visibility.rules.Epitaph
|
||||
import com.twitter.visibility.results.richtext.EpitaphToRichText.Copy
|
||||
|
||||
object EpitaphToLocalizedMessage {
|
||||
def apply(
|
||||
epitaph: Epitaph,
|
||||
languageTag: String,
|
||||
): LocalizedMessage = {
|
||||
val copy =
|
||||
EpitaphToRichText.EpitaphToPolicyMap.getOrElse(epitaph, EpitaphToRichText.FallbackPolicy)
|
||||
val text = Translator.translate(
|
||||
copy.resource,
|
||||
languageTag
|
||||
)
|
||||
localizeWithCopyAndText(copy, languageTag, text)
|
||||
}
|
||||
|
||||
def apply(
|
||||
epitaph: Epitaph,
|
||||
languageTag: String,
|
||||
applicableCountries: Seq[String],
|
||||
): LocalizedMessage = {
|
||||
val copy =
|
||||
EpitaphToRichText.EpitaphToPolicyMap.getOrElse(epitaph, EpitaphToRichText.FallbackPolicy)
|
||||
val text = Translator.translateWithSimplePlaceholderReplacement(
|
||||
copy.resource,
|
||||
languageTag,
|
||||
Map((Resource.ApplicableCountriesPlaceholder -> applicableCountries.mkString(", ")))
|
||||
)
|
||||
localizeWithCopyAndText(copy, languageTag, text)
|
||||
}
|
||||
|
||||
private def localizeWithCopyAndText(
|
||||
copy: Copy,
|
||||
languageTag: String,
|
||||
text: String
|
||||
): LocalizedMessage = {
|
||||
val learnMore = Translator.translate(LearnMoreLink, languageTag)
|
||||
|
||||
val links = copy.additionalLinks match {
|
||||
case links if links.nonEmpty =>
|
||||
MessageLink(Resource.LearnMorePlaceholder, learnMore, copy.link) +:
|
||||
links.map {
|
||||
case EpitaphToRichText.Link(placeholder, copyResource, link) =>
|
||||
val copyText = Translator.translate(copyResource, languageTag)
|
||||
MessageLink(placeholder, copyText, link)
|
||||
}
|
||||
case _ =>
|
||||
Seq(
|
||||
MessageLink(
|
||||
key = Resource.LearnMorePlaceholder,
|
||||
displayText = learnMore,
|
||||
uri = copy.link))
|
||||
}
|
||||
|
||||
LocalizedMessage(message = text, language = languageTag, links = links)
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package com.twitter.visibility.generators
|
||||
|
||||
import com.twitter.visibility.common.actions.InterstitialReason
|
||||
import com.twitter.visibility.common.actions.LocalizedMessage
|
||||
import com.twitter.visibility.common.actions.MessageLink
|
||||
import com.twitter.visibility.results.richtext.InterstitialReasonToRichText
|
||||
import com.twitter.visibility.results.richtext.InterstitialReasonToRichText.InterstitialCopy
|
||||
import com.twitter.visibility.results.richtext.InterstitialReasonToRichText.InterstitialLink
|
||||
import com.twitter.visibility.results.translation.LearnMoreLink
|
||||
import com.twitter.visibility.results.translation.Resource
|
||||
import com.twitter.visibility.results.translation.Translator
|
||||
|
||||
object InterstitialReasonToLocalizedMessage {
|
||||
def apply(
|
||||
reason: InterstitialReason,
|
||||
languageTag: String,
|
||||
): Option[LocalizedMessage] = {
|
||||
InterstitialReasonToRichText.reasonToCopy(reason).map { copy =>
|
||||
val text = Translator.translate(
|
||||
copy.resource,
|
||||
languageTag
|
||||
)
|
||||
localizeWithCopyAndText(copy, languageTag, text)
|
||||
}
|
||||
}
|
||||
|
||||
private def localizeWithCopyAndText(
|
||||
copy: InterstitialCopy,
|
||||
languageTag: String,
|
||||
text: String
|
||||
): LocalizedMessage = {
|
||||
val learnMore = Translator.translate(LearnMoreLink, languageTag)
|
||||
|
||||
val learnMoreLinkOpt =
|
||||
copy.link.map { link =>
|
||||
MessageLink(key = Resource.LearnMorePlaceholder, displayText = learnMore, uri = link)
|
||||
}
|
||||
val additionalLinks = copy.additionalLinks.map {
|
||||
case InterstitialLink(placeholder, copyResource, link) =>
|
||||
val copyText = Translator.translate(copyResource, languageTag)
|
||||
MessageLink(key = placeholder, displayText = copyText, uri = link)
|
||||
}
|
||||
|
||||
val links = learnMoreLinkOpt.toSeq ++ additionalLinks
|
||||
LocalizedMessage(message = text, language = languageTag, links = links)
|
||||
}
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
package com.twitter.visibility.generators
|
||||
|
||||
import com.twitter.decider.Decider
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.visibility.builder.VisibilityResult
|
||||
import com.twitter.visibility.common.actions.LocalizedMessage
|
||||
import com.twitter.visibility.common.actions.MessageLink
|
||||
import com.twitter.visibility.configapi.configs.VisibilityDeciderGates
|
||||
import com.twitter.visibility.results.richtext.PublicInterestReasonToRichText
|
||||
import com.twitter.visibility.results.translation.LearnMoreLink
|
||||
import com.twitter.visibility.results.translation.Resource
|
||||
import com.twitter.visibility.results.translation.SafetyResultReasonToResource
|
||||
import com.twitter.visibility.results.translation.Translator
|
||||
import com.twitter.visibility.rules.EmergencyDynamicInterstitial
|
||||
import com.twitter.visibility.rules.Interstitial
|
||||
import com.twitter.visibility.rules.InterstitialLimitedEngagements
|
||||
import com.twitter.visibility.rules.PublicInterest
|
||||
import com.twitter.visibility.rules.Reason
|
||||
import com.twitter.visibility.rules.TweetInterstitial
|
||||
|
||||
object LocalizedInterstitialGenerator {
|
||||
def apply(
|
||||
visibilityDecider: Decider,
|
||||
baseStatsReceiver: StatsReceiver,
|
||||
): LocalizedInterstitialGenerator = {
|
||||
new LocalizedInterstitialGenerator(visibilityDecider, baseStatsReceiver)
|
||||
}
|
||||
}
|
||||
|
||||
class LocalizedInterstitialGenerator private (
|
||||
val visibilityDecider: Decider,
|
||||
val baseStatsReceiver: StatsReceiver) {
|
||||
|
||||
private val visibilityDeciderGates = VisibilityDeciderGates(visibilityDecider)
|
||||
private val localizationStatsReceiver = baseStatsReceiver.scope("interstitial_localization")
|
||||
private val publicInterestInterstitialStats =
|
||||
localizationStatsReceiver.scope("public_interest_copy")
|
||||
private val emergencyDynamicInterstitialStats =
|
||||
localizationStatsReceiver.scope("emergency_dynamic_copy")
|
||||
private val regularInterstitialStats = localizationStatsReceiver.scope("interstitial_copy")
|
||||
|
||||
def apply(visibilityResult: VisibilityResult, languageTag: String): VisibilityResult = {
|
||||
if (!visibilityDeciderGates.enableLocalizedInterstitialGenerator()) {
|
||||
return visibilityResult
|
||||
}
|
||||
|
||||
visibilityResult.verdict match {
|
||||
case ipi: InterstitialLimitedEngagements if PublicInterest.Reasons.contains(ipi.reason) =>
|
||||
visibilityResult.copy(
|
||||
verdict = ipi.copy(
|
||||
localizedMessage = Some(localizePublicInterestCopyInResult(ipi, languageTag))
|
||||
))
|
||||
case edi: EmergencyDynamicInterstitial =>
|
||||
visibilityResult.copy(
|
||||
verdict = EmergencyDynamicInterstitial(
|
||||
edi.copy,
|
||||
edi.linkOpt,
|
||||
Some(localizeEmergencyDynamicCopyInResult(edi, languageTag))
|
||||
))
|
||||
case interstitial: Interstitial =>
|
||||
visibilityResult.copy(
|
||||
verdict = interstitial.copy(
|
||||
localizedMessage = localizeInterstitialCopyInResult(interstitial, languageTag)
|
||||
))
|
||||
case tweetInterstitial: TweetInterstitial if tweetInterstitial.interstitial.isDefined =>
|
||||
tweetInterstitial.interstitial.get match {
|
||||
case ipi: InterstitialLimitedEngagements if PublicInterest.Reasons.contains(ipi.reason) =>
|
||||
visibilityResult.copy(
|
||||
verdict = tweetInterstitial.copy(
|
||||
interstitial = Some(
|
||||
ipi.copy(
|
||||
localizedMessage = Some(localizePublicInterestCopyInResult(ipi, languageTag))
|
||||
))
|
||||
))
|
||||
case edi: EmergencyDynamicInterstitial =>
|
||||
visibilityResult.copy(
|
||||
verdict = tweetInterstitial.copy(
|
||||
interstitial = Some(
|
||||
EmergencyDynamicInterstitial(
|
||||
edi.copy,
|
||||
edi.linkOpt,
|
||||
Some(localizeEmergencyDynamicCopyInResult(edi, languageTag))
|
||||
))
|
||||
))
|
||||
case interstitial: Interstitial =>
|
||||
visibilityResult.copy(
|
||||
verdict = tweetInterstitial.copy(
|
||||
interstitial = Some(
|
||||
interstitial.copy(
|
||||
localizedMessage = localizeInterstitialCopyInResult(interstitial, languageTag)
|
||||
))
|
||||
))
|
||||
case _ => visibilityResult
|
||||
}
|
||||
case _ => visibilityResult
|
||||
}
|
||||
}
|
||||
|
||||
private def localizeEmergencyDynamicCopyInResult(
|
||||
edi: EmergencyDynamicInterstitial,
|
||||
languageTag: String
|
||||
): LocalizedMessage = {
|
||||
val text = edi.linkOpt
|
||||
.map(_ => s"${edi.copy} {${Resource.LearnMorePlaceholder}}")
|
||||
.getOrElse(edi.copy)
|
||||
|
||||
val messageLinks = edi.linkOpt
|
||||
.map { link =>
|
||||
val learnMoreText = Translator.translate(LearnMoreLink, languageTag)
|
||||
Seq(MessageLink(Resource.LearnMorePlaceholder, learnMoreText, link))
|
||||
}.getOrElse(Seq.empty)
|
||||
|
||||
emergencyDynamicInterstitialStats.counter("localized").incr()
|
||||
LocalizedMessage(text, languageTag, messageLinks)
|
||||
}
|
||||
|
||||
private def localizePublicInterestCopyInResult(
|
||||
ipi: InterstitialLimitedEngagements,
|
||||
languageTag: String
|
||||
): LocalizedMessage = {
|
||||
val safetyResultReason = PublicInterest.ReasonToSafetyResultReason(ipi.reason)
|
||||
val text = Translator.translate(
|
||||
SafetyResultReasonToResource.resource(safetyResultReason),
|
||||
languageTag,
|
||||
)
|
||||
|
||||
val learnMoreLink = PublicInterestReasonToRichText.toLearnMoreLink(safetyResultReason)
|
||||
val learnMoreText = Translator.translate(LearnMoreLink, languageTag)
|
||||
val messageLinks = Seq(MessageLink(Resource.LearnMorePlaceholder, learnMoreText, learnMoreLink))
|
||||
|
||||
publicInterestInterstitialStats.counter("localized").incr()
|
||||
LocalizedMessage(text, languageTag, messageLinks)
|
||||
}
|
||||
|
||||
private def localizeInterstitialCopyInResult(
|
||||
interstitial: Interstitial,
|
||||
languageTag: String
|
||||
): Option[LocalizedMessage] = {
|
||||
val localizedMessageOpt = Reason
|
||||
.toInterstitialReason(interstitial.reason)
|
||||
.flatMap(InterstitialReasonToLocalizedMessage(_, languageTag))
|
||||
|
||||
if (localizedMessageOpt.isDefined) {
|
||||
regularInterstitialStats.counter("localized").incr()
|
||||
localizedMessageOpt
|
||||
} else {
|
||||
regularInterstitialStats.counter("empty").incr()
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
package com.twitter.visibility.generators
|
||||
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.servo.util.MemoizingStatsReceiver
|
||||
import com.twitter.visibility.builder.VisibilityResult
|
||||
import com.twitter.visibility.common.actions.TombstoneReason
|
||||
import com.twitter.visibility.configapi.VisibilityParams
|
||||
import com.twitter.visibility.rules.Epitaph
|
||||
import com.twitter.visibility.rules.LocalizedTombstone
|
||||
import com.twitter.visibility.rules.Tombstone
|
||||
|
||||
object TombstoneGenerator {
|
||||
def apply(
|
||||
visibilityParams: VisibilityParams,
|
||||
countryNameGenerator: CountryNameGenerator,
|
||||
statsReceiver: StatsReceiver
|
||||
): TombstoneGenerator = {
|
||||
new TombstoneGenerator(visibilityParams, countryNameGenerator, statsReceiver)
|
||||
}
|
||||
}
|
||||
|
||||
class TombstoneGenerator(
|
||||
paramsFactory: VisibilityParams,
|
||||
countryNameGenerator: CountryNameGenerator,
|
||||
baseStatsReceiver: StatsReceiver) {
|
||||
|
||||
private[this] val statsReceiver = new MemoizingStatsReceiver(
|
||||
baseStatsReceiver.scope("tombstone_generator"))
|
||||
private[this] val deletedReceiver = statsReceiver.scope("deleted_state")
|
||||
private[this] val authorStateReceiver = statsReceiver.scope("tweet_author_state")
|
||||
private[this] val visResultReceiver = statsReceiver.scope("visibility_result")
|
||||
|
||||
def apply(
|
||||
result: VisibilityResult,
|
||||
language: String
|
||||
): VisibilityResult = {
|
||||
|
||||
result.verdict match {
|
||||
case tombstone: Tombstone =>
|
||||
val epitaph = tombstone.epitaph
|
||||
visResultReceiver.scope("tombstone").counter(epitaph.name.toLowerCase())
|
||||
|
||||
val overriddenLanguage = epitaph match {
|
||||
case Epitaph.LegalDemandsWithheldMedia | Epitaph.LocalLawsWithheldMedia => "en"
|
||||
case _ => language
|
||||
}
|
||||
|
||||
tombstone.applicableCountryCodes match {
|
||||
case Some(countryCodes) => {
|
||||
val countryNames = countryCodes.map(countryNameGenerator.getCountryName(_))
|
||||
|
||||
result.copy(verdict = LocalizedTombstone(
|
||||
reason = epitaphToTombstoneReason(epitaph),
|
||||
message = EpitaphToLocalizedMessage(epitaph, overriddenLanguage, countryNames)))
|
||||
}
|
||||
case _ => {
|
||||
result.copy(verdict = LocalizedTombstone(
|
||||
reason = epitaphToTombstoneReason(epitaph),
|
||||
message = EpitaphToLocalizedMessage(epitaph, overriddenLanguage)))
|
||||
}
|
||||
}
|
||||
case _ =>
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
private def epitaphToTombstoneReason(epitaph: Epitaph): TombstoneReason = {
|
||||
epitaph match {
|
||||
case Epitaph.Deleted => TombstoneReason.Deleted
|
||||
case Epitaph.Bounced => TombstoneReason.Bounced
|
||||
case Epitaph.BounceDeleted => TombstoneReason.BounceDeleted
|
||||
case Epitaph.Protected => TombstoneReason.ProtectedAuthor
|
||||
case Epitaph.Suspended => TombstoneReason.SuspendedAuthor
|
||||
case Epitaph.BlockedBy => TombstoneReason.AuthorBlocksViewer
|
||||
case Epitaph.SuperFollowsContent => TombstoneReason.ExclusiveTweet
|
||||
case Epitaph.Underage => TombstoneReason.NsfwViewerIsUnderage
|
||||
case Epitaph.NoStatedAge => TombstoneReason.NsfwViewerHasNoStatedAge
|
||||
case Epitaph.LoggedOutAge => TombstoneReason.NsfwLoggedOut
|
||||
case Epitaph.Deactivated => TombstoneReason.DeactivatedAuthor
|
||||
case Epitaph.CommunityTweetHidden => TombstoneReason.CommunityTweetHidden
|
||||
case Epitaph.CommunityTweetCommunityIsSuspended =>
|
||||
TombstoneReason.CommunityTweetCommunityIsSuspended
|
||||
case Epitaph.DevelopmentOnly => TombstoneReason.DevelopmentOnly
|
||||
case Epitaph.AdultMedia => TombstoneReason.AdultMedia
|
||||
case Epitaph.ViolentMedia => TombstoneReason.ViolentMedia
|
||||
case Epitaph.OtherSensitiveMedia => TombstoneReason.OtherSensitiveMedia
|
||||
case Epitaph.DmcaWithheldMedia => TombstoneReason.DmcaWithheldMedia
|
||||
case Epitaph.LegalDemandsWithheldMedia => TombstoneReason.LegalDemandsWithheldMedia
|
||||
case Epitaph.LocalLawsWithheldMedia => TombstoneReason.LocalLawsWithheldMedia
|
||||
case Epitaph.ToxicReplyFiltered => TombstoneReason.ReplyFiltered
|
||||
case _ => TombstoneReason.Unspecified
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/com/twitter/src/java/com/twitter/logpipeline/client:logpipeline-event-publisher-thin",
|
||||
"decider/src/main/scala",
|
||||
"mediaservices/media-util/src/main/scala",
|
||||
"servo/decider/src/main/scala",
|
||||
"src/thrift/com/twitter/escherbird:media-annotation-structs-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-level-scala",
|
||||
"src/thrift/com/twitter/spam/rtf:tweet-rtf-event-scala",
|
||||
"src/thrift/com/twitter/tweetypie:tweet-scala",
|
||||
"stitch/stitch-core",
|
||||
"strato/src/main/scala/com/twitter/strato/client",
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common",
|
||||
"visibility/lib/src/main/resources/config",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder/media",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder/tweets",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/configapi/configs",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/features",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/blender",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/interfaces/common/tweets",
|
||||
"visibility/lib/src/main/thrift/com/twitter/visibility/logging:vf-logging-scala",
|
||||
],
|
||||
exports = [
|
||||
"visibility/common/src/main/scala/com/twitter/visibility/common",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility",
|
||||
],
|
||||
)
|
|
@ -1,416 +0,0 @@
|
|||
package com.twitter.visibility.interfaces.blender
|
||||
|
||||
import com.twitter.decider.Decider
|
||||
import com.twitter.finagle.stats.StatsReceiver
|
||||
import com.twitter.mediaservices.media_util.GenericMediaKey
|
||||
import com.twitter.servo.util.Gate
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.strato.client.{Client => StratoClient}
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.util.Stopwatch
|
||||
import com.twitter.visibility.VisibilityLibrary
|
||||
import com.twitter.visibility.builder.VerdictLogger
|
||||
import com.twitter.visibility.builder.VisibilityResult
|
||||
import com.twitter.visibility.builder.media.MediaFeatures
|
||||
import com.twitter.visibility.builder.media.StratoMediaLabelMaps
|
||||
import com.twitter.visibility.builder.tweets._
|
||||
import com.twitter.visibility.builder.users.AuthorFeatures
|
||||
import com.twitter.visibility.builder.users.RelationshipFeatures
|
||||
import com.twitter.visibility.builder.users.ViewerFeatures
|
||||
import com.twitter.visibility.common.MediaSafetyLabelMapSource
|
||||
import com.twitter.visibility.common.MisinformationPolicySource
|
||||
import com.twitter.visibility.common.SafetyLabelMapSource
|
||||
import com.twitter.visibility.common.TrustedFriendsSource
|
||||
import com.twitter.visibility.common.UserRelationshipSource
|
||||
import com.twitter.visibility.common.UserSource
|
||||
import com.twitter.visibility.rules.ComposableActions.ComposableActionsWithInterstitial
|
||||
import com.twitter.visibility.configapi.configs.VisibilityDeciderGates
|
||||
import com.twitter.visibility.features.FeatureMap
|
||||
import com.twitter.visibility.features.TweetIsInnerQuotedTweet
|
||||
import com.twitter.visibility.features.TweetIsRetweet
|
||||
import com.twitter.visibility.features.TweetIsSourceTweet
|
||||
import com.twitter.visibility.logging.thriftscala.VFLibType
|
||||
import com.twitter.visibility.models.ContentId
|
||||
import com.twitter.visibility.models.ContentId.BlenderTweetId
|
||||
import com.twitter.visibility.models.ContentId.TweetId
|
||||
import com.twitter.visibility.models.SafetyLevel
|
||||
import com.twitter.visibility.models.SafetyLevel.toThrift
|
||||
import com.twitter.visibility.rules.Action
|
||||
import com.twitter.visibility.rules.Allow
|
||||
import com.twitter.visibility.rules.Drop
|
||||
import com.twitter.visibility.rules.Interstitial
|
||||
import com.twitter.visibility.rules.TweetInterstitial
|
||||
|
||||
object TweetType extends Enumeration {
|
||||
type TweetType = Value
|
||||
val ORIGINAL, SOURCE, QUOTED = Value
|
||||
}
|
||||
import com.twitter.visibility.interfaces.blender.TweetType._
|
||||
|
||||
object BlenderVisibilityLibrary {
|
||||
def buildWithStratoClient(
|
||||
visibilityLibrary: VisibilityLibrary,
|
||||
decider: Decider,
|
||||
stratoClient: StratoClient,
|
||||
userSource: UserSource,
|
||||
userRelationshipSource: UserRelationshipSource
|
||||
): BlenderVisibilityLibrary = new BlenderVisibilityLibrary(
|
||||
visibilityLibrary,
|
||||
decider,
|
||||
stratoClient,
|
||||
userSource,
|
||||
userRelationshipSource,
|
||||
None
|
||||
)
|
||||
|
||||
def buildWithSafetyLabelMapSource(
|
||||
visibilityLibrary: VisibilityLibrary,
|
||||
decider: Decider,
|
||||
stratoClient: StratoClient,
|
||||
userSource: UserSource,
|
||||
userRelationshipSource: UserRelationshipSource,
|
||||
safetyLabelMapSource: SafetyLabelMapSource
|
||||
): BlenderVisibilityLibrary = new BlenderVisibilityLibrary(
|
||||
visibilityLibrary,
|
||||
decider,
|
||||
stratoClient,
|
||||
userSource,
|
||||
userRelationshipSource,
|
||||
Some(safetyLabelMapSource)
|
||||
)
|
||||
|
||||
def createVerdictLogger(
|
||||
enableVerdictLogger: Gate[Unit],
|
||||
decider: Decider,
|
||||
statsReceiver: StatsReceiver
|
||||
): VerdictLogger = {
|
||||
if (enableVerdictLogger()) {
|
||||
VerdictLogger(statsReceiver, decider)
|
||||
} else {
|
||||
VerdictLogger.Empty
|
||||
}
|
||||
}
|
||||
|
||||
def scribeVisibilityVerdict(
|
||||
result: CombinedVisibilityResult,
|
||||
enableVerdictScribing: Gate[Unit],
|
||||
verdictLogger: VerdictLogger,
|
||||
viewerId: Option[Long],
|
||||
safetyLevel: SafetyLevel
|
||||
): Unit = if (enableVerdictScribing()) {
|
||||
verdictLogger.scribeVerdict(
|
||||
visibilityResult = result.tweetVisibilityResult,
|
||||
viewerId = viewerId,
|
||||
safetyLevel = toThrift(safetyLevel),
|
||||
vfLibType = VFLibType.BlenderVisibilityLibrary)
|
||||
|
||||
result.quotedTweetVisibilityResult.map(quotedTweetVisibilityResult =>
|
||||
verdictLogger.scribeVerdict(
|
||||
visibilityResult = quotedTweetVisibilityResult,
|
||||
viewerId = viewerId,
|
||||
safetyLevel = toThrift(safetyLevel),
|
||||
vfLibType = VFLibType.BlenderVisibilityLibrary))
|
||||
}
|
||||
}
|
||||
|
||||
class BlenderVisibilityLibrary(
|
||||
visibilityLibrary: VisibilityLibrary,
|
||||
decider: Decider,
|
||||
stratoClient: StratoClient,
|
||||
userSource: UserSource,
|
||||
userRelationshipSource: UserRelationshipSource,
|
||||
safetyLabelMapSourceOption: Option[SafetyLabelMapSource]) {
|
||||
|
||||
val libraryStatsReceiver = visibilityLibrary.statsReceiver
|
||||
val stratoClientStatsReceiver = visibilityLibrary.statsReceiver.scope("strato")
|
||||
val vfEngineCounter = libraryStatsReceiver.counter("vf_engine_requests")
|
||||
val bvlRequestCounter = libraryStatsReceiver.counter("bvl_requests")
|
||||
val vfLatencyOverallStat = libraryStatsReceiver.stat("vf_latency_overall")
|
||||
val vfLatencyStitchBuildStat = libraryStatsReceiver.stat("vf_latency_stitch_build")
|
||||
val vfLatencyStitchRunStat = libraryStatsReceiver.stat("vf_latency_stitch_run")
|
||||
val visibilityDeciderGates = VisibilityDeciderGates(decider)
|
||||
val verdictLogger = BlenderVisibilityLibrary.createVerdictLogger(
|
||||
visibilityDeciderGates.enableVerdictLoggerBVL,
|
||||
decider,
|
||||
libraryStatsReceiver)
|
||||
|
||||
val tweetLabels = safetyLabelMapSourceOption match {
|
||||
case Some(safetyLabelMapSource) =>
|
||||
new StratoTweetLabelMaps(safetyLabelMapSource)
|
||||
case None =>
|
||||
new StratoTweetLabelMaps(
|
||||
SafetyLabelMapSource.fromStrato(stratoClient, stratoClientStatsReceiver))
|
||||
}
|
||||
|
||||
val mediaLabelMaps = new StratoMediaLabelMaps(
|
||||
MediaSafetyLabelMapSource.fromStrato(stratoClient, stratoClientStatsReceiver))
|
||||
|
||||
val tweetFeatures = new TweetFeatures(tweetLabels, libraryStatsReceiver)
|
||||
val blenderContextFeatures = new BlenderContextFeatures(libraryStatsReceiver)
|
||||
val authorFeatures = new AuthorFeatures(userSource, libraryStatsReceiver)
|
||||
val viewerFeatures = new ViewerFeatures(userSource, libraryStatsReceiver)
|
||||
val relationshipFeatures =
|
||||
new RelationshipFeatures(userRelationshipSource, libraryStatsReceiver)
|
||||
val fonsrRelationshipFeatures =
|
||||
new FosnrRelationshipFeatures(
|
||||
tweetLabels = tweetLabels,
|
||||
userRelationshipSource = userRelationshipSource,
|
||||
statsReceiver = libraryStatsReceiver)
|
||||
val misinfoPolicySource =
|
||||
MisinformationPolicySource.fromStrato(stratoClient, stratoClientStatsReceiver)
|
||||
val misinfoPolicyFeatures =
|
||||
new MisinformationPolicyFeatures(misinfoPolicySource, stratoClientStatsReceiver)
|
||||
val exclusiveTweetFeatures =
|
||||
new ExclusiveTweetFeatures(userRelationshipSource, libraryStatsReceiver)
|
||||
val mediaFeatures = new MediaFeatures(mediaLabelMaps, libraryStatsReceiver)
|
||||
val trustedFriendsTweetFeatures = new TrustedFriendsFeatures(
|
||||
trustedFriendsSource = TrustedFriendsSource.fromStrato(stratoClient, stratoClientStatsReceiver))
|
||||
val editTweetFeatures = new EditTweetFeatures(libraryStatsReceiver)
|
||||
|
||||
def getCombinedVisibilityResult(
|
||||
bvRequest: BlenderVisibilityRequest
|
||||
): Stitch[CombinedVisibilityResult] = {
|
||||
val elapsed = Stopwatch.start()
|
||||
bvlRequestCounter.incr()
|
||||
|
||||
val (
|
||||
requestTweetVisibilityResult,
|
||||
quotedTweetVisibilityResultOption,
|
||||
sourceTweetVisibilityResultOption
|
||||
) = getAllVisibilityResults(bvRequest: BlenderVisibilityRequest)
|
||||
|
||||
val response: Stitch[CombinedVisibilityResult] = {
|
||||
(
|
||||
requestTweetVisibilityResult,
|
||||
quotedTweetVisibilityResultOption,
|
||||
sourceTweetVisibilityResultOption) match {
|
||||
case (requestTweetVisResult, Some(quotedTweetVisResult), Some(sourceTweetVisResult)) => {
|
||||
Stitch
|
||||
.join(
|
||||
requestTweetVisResult,
|
||||
quotedTweetVisResult,
|
||||
sourceTweetVisResult
|
||||
).map {
|
||||
case (requestTweetVisResult, quotedTweetVisResult, sourceTweetVisResult) => {
|
||||
requestTweetVisResult.verdict match {
|
||||
case Allow =>
|
||||
CombinedVisibilityResult(sourceTweetVisResult, Some(quotedTweetVisResult))
|
||||
case _ =>
|
||||
CombinedVisibilityResult(requestTweetVisResult, Some(quotedTweetVisResult))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case (requestTweetVisResult, None, Some(sourceTweetVisResult)) => {
|
||||
Stitch
|
||||
.join(
|
||||
requestTweetVisResult,
|
||||
sourceTweetVisResult
|
||||
).map {
|
||||
case (requestTweetVisResult, sourceTweetVisResult) => {
|
||||
requestTweetVisResult.verdict match {
|
||||
case Allow =>
|
||||
CombinedVisibilityResult(sourceTweetVisResult, None)
|
||||
case _ =>
|
||||
CombinedVisibilityResult(requestTweetVisResult, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case (requestTweetVisResult, Some(quotedTweetVisResult), None) => {
|
||||
Stitch
|
||||
.join(
|
||||
requestTweetVisResult,
|
||||
quotedTweetVisResult
|
||||
).map {
|
||||
case (requestTweetVisResult, quotedTweetVisResult) => {
|
||||
CombinedVisibilityResult(requestTweetVisResult, Some(quotedTweetVisResult))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case (requestTweetVisResult, None, None) => {
|
||||
requestTweetVisResult.map {
|
||||
CombinedVisibilityResult(_, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val runStitchStartMs = elapsed().inMilliseconds
|
||||
val buildStitchStatMs = elapsed().inMilliseconds
|
||||
vfLatencyStitchBuildStat.add(buildStitchStatMs)
|
||||
|
||||
response
|
||||
.onSuccess(_ => {
|
||||
val overallMs = elapsed().inMilliseconds
|
||||
vfLatencyOverallStat.add(overallMs)
|
||||
val stitchRunMs = elapsed().inMilliseconds - runStitchStartMs
|
||||
vfLatencyStitchRunStat.add(stitchRunMs)
|
||||
})
|
||||
.onSuccess(
|
||||
BlenderVisibilityLibrary.scribeVisibilityVerdict(
|
||||
_,
|
||||
visibilityDeciderGates.enableVerdictScribingBVL,
|
||||
verdictLogger,
|
||||
bvRequest.viewerContext.userId,
|
||||
bvRequest.safetyLevel))
|
||||
}
|
||||
|
||||
def getContentId(viewerId: Option[Long], authorId: Long, tweet: Tweet): ContentId = {
|
||||
if (viewerId.contains(authorId))
|
||||
TweetId(tweet.id)
|
||||
else BlenderTweetId(tweet.id)
|
||||
}
|
||||
|
||||
def getAllVisibilityResults(bvRequest: BlenderVisibilityRequest): (
|
||||
Stitch[VisibilityResult],
|
||||
Option[Stitch[VisibilityResult]],
|
||||
Option[Stitch[VisibilityResult]]
|
||||
) = {
|
||||
val tweetContentId = getContentId(
|
||||
viewerId = bvRequest.viewerContext.userId,
|
||||
authorId = bvRequest.tweet.coreData.get.userId,
|
||||
tweet = bvRequest.tweet)
|
||||
|
||||
val tweetFeatureMap =
|
||||
buildFeatureMap(bvRequest, bvRequest.tweet, ORIGINAL)
|
||||
vfEngineCounter.incr()
|
||||
val requestTweetVisibilityResult = visibilityLibrary
|
||||
.runRuleEngine(
|
||||
tweetContentId,
|
||||
tweetFeatureMap,
|
||||
bvRequest.viewerContext,
|
||||
bvRequest.safetyLevel
|
||||
).map(handleComposableVisibilityResult)
|
||||
|
||||
val quotedTweetVisibilityResultOption = bvRequest.quotedTweet.map(quotedTweet => {
|
||||
val quotedTweetContentId = getContentId(
|
||||
viewerId = bvRequest.viewerContext.userId,
|
||||
authorId = quotedTweet.coreData.get.userId,
|
||||
tweet = quotedTweet)
|
||||
|
||||
val quotedInnerTweetFeatureMap =
|
||||
buildFeatureMap(bvRequest, quotedTweet, QUOTED)
|
||||
vfEngineCounter.incr()
|
||||
visibilityLibrary
|
||||
.runRuleEngine(
|
||||
quotedTweetContentId,
|
||||
quotedInnerTweetFeatureMap,
|
||||
bvRequest.viewerContext,
|
||||
bvRequest.safetyLevel
|
||||
)
|
||||
.map(handleComposableVisibilityResult)
|
||||
.map(handleInnerQuotedTweetVisibilityResult)
|
||||
})
|
||||
|
||||
val sourceTweetVisibilityResultOption = bvRequest.retweetSourceTweet.map(sourceTweet => {
|
||||
val sourceTweetContentId = getContentId(
|
||||
viewerId = bvRequest.viewerContext.userId,
|
||||
authorId = sourceTweet.coreData.get.userId,
|
||||
tweet = sourceTweet)
|
||||
|
||||
val sourceTweetFeatureMap =
|
||||
buildFeatureMap(bvRequest, sourceTweet, SOURCE)
|
||||
vfEngineCounter.incr()
|
||||
visibilityLibrary
|
||||
.runRuleEngine(
|
||||
sourceTweetContentId,
|
||||
sourceTweetFeatureMap,
|
||||
bvRequest.viewerContext,
|
||||
bvRequest.safetyLevel
|
||||
)
|
||||
.map(handleComposableVisibilityResult)
|
||||
})
|
||||
|
||||
(
|
||||
requestTweetVisibilityResult,
|
||||
quotedTweetVisibilityResultOption,
|
||||
sourceTweetVisibilityResultOption)
|
||||
}
|
||||
|
||||
def buildFeatureMap(
|
||||
bvRequest: BlenderVisibilityRequest,
|
||||
tweet: Tweet,
|
||||
tweetType: TweetType
|
||||
): FeatureMap = {
|
||||
val authorId = tweet.coreData.get.userId
|
||||
val viewerId = bvRequest.viewerContext.userId
|
||||
val isRetweet = if (tweetType.equals(ORIGINAL)) bvRequest.isRetweet else false
|
||||
val isSourceTweet = tweetType.equals(SOURCE)
|
||||
val isQuotedTweet = tweetType.equals(QUOTED)
|
||||
val tweetMediaKeys: Seq[GenericMediaKey] = tweet.media
|
||||
.getOrElse(Seq.empty)
|
||||
.flatMap(_.mediaKey.map(GenericMediaKey.apply))
|
||||
|
||||
visibilityLibrary.featureMapBuilder(
|
||||
Seq(
|
||||
viewerFeatures
|
||||
.forViewerBlenderContext(bvRequest.blenderVFRequestContext, bvRequest.viewerContext),
|
||||
relationshipFeatures.forAuthorId(authorId, viewerId),
|
||||
fonsrRelationshipFeatures
|
||||
.forTweetAndAuthorId(tweet = tweet, authorId = authorId, viewerId = viewerId),
|
||||
tweetFeatures.forTweet(tweet),
|
||||
mediaFeatures.forMediaKeys(tweetMediaKeys),
|
||||
authorFeatures.forAuthorId(authorId),
|
||||
blenderContextFeatures.forBlenderContext(bvRequest.blenderVFRequestContext),
|
||||
_.withConstantFeature(TweetIsRetweet, isRetweet),
|
||||
misinfoPolicyFeatures.forTweet(tweet, bvRequest.viewerContext),
|
||||
exclusiveTweetFeatures.forTweet(tweet, bvRequest.viewerContext),
|
||||
trustedFriendsTweetFeatures.forTweet(tweet, viewerId),
|
||||
editTweetFeatures.forTweet(tweet),
|
||||
_.withConstantFeature(TweetIsInnerQuotedTweet, isQuotedTweet),
|
||||
_.withConstantFeature(TweetIsSourceTweet, isSourceTweet),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def handleComposableVisibilityResult(result: VisibilityResult): VisibilityResult = {
|
||||
if (result.secondaryVerdicts.nonEmpty) {
|
||||
result.copy(verdict = composeActions(result.verdict, result.secondaryVerdicts))
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
private def composeActions(primary: Action, secondary: Seq[Action]): Action = {
|
||||
if (primary.isComposable && secondary.nonEmpty) {
|
||||
val actions = Seq[Action] { primary } ++ secondary
|
||||
val interstitialOpt = Action.getFirstInterstitial(actions: _*)
|
||||
val softInterventionOpt = Action.getFirstSoftIntervention(actions: _*)
|
||||
val limitedEngagementsOpt = Action.getFirstLimitedEngagements(actions: _*)
|
||||
val avoidOpt = Action.getFirstAvoid(actions: _*)
|
||||
|
||||
val numActions =
|
||||
Seq[Option[_]](interstitialOpt, softInterventionOpt, limitedEngagementsOpt, avoidOpt)
|
||||
.count(_.isDefined)
|
||||
if (numActions > 1) {
|
||||
TweetInterstitial(
|
||||
interstitialOpt,
|
||||
softInterventionOpt,
|
||||
limitedEngagementsOpt,
|
||||
None,
|
||||
avoidOpt
|
||||
)
|
||||
} else {
|
||||
primary
|
||||
}
|
||||
} else {
|
||||
primary
|
||||
}
|
||||
}
|
||||
|
||||
def handleInnerQuotedTweetVisibilityResult(
|
||||
result: VisibilityResult
|
||||
): VisibilityResult = {
|
||||
val newVerdict: Action =
|
||||
result.verdict match {
|
||||
case interstitial: Interstitial => Drop(interstitial.reason)
|
||||
case ComposableActionsWithInterstitial(tweetInterstitial) => Drop(tweetInterstitial.reason)
|
||||
case verdict => verdict
|
||||
}
|
||||
|
||||
result.copy(verdict = newVerdict)
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package com.twitter.visibility.interfaces.blender
|
||||
|
||||
import com.twitter.tweetypie.thriftscala.Tweet
|
||||
import com.twitter.visibility.models.SafetyLevel
|
||||
import com.twitter.visibility.models.ViewerContext
|
||||
import com.twitter.visibility.interfaces.common.blender.BlenderVFRequestContext
|
||||
|
||||
case class BlenderVisibilityRequest(
|
||||
tweet: Tweet,
|
||||
quotedTweet: Option[Tweet],
|
||||
retweetSourceTweet: Option[Tweet] = None,
|
||||
isRetweet: Boolean,
|
||||
safetyLevel: SafetyLevel,
|
||||
viewerContext: ViewerContext,
|
||||
blenderVFRequestContext: BlenderVFRequestContext) {
|
||||
|
||||
def getTweetID: Long = tweet.id
|
||||
|
||||
def hasQuotedTweet: Boolean = {
|
||||
quotedTweet.nonEmpty
|
||||
}
|
||||
def hasSourceTweet: Boolean = {
|
||||
retweetSourceTweet.nonEmpty
|
||||
}
|
||||
|
||||
def getQuotedTweetId: Long = {
|
||||
quotedTweet match {
|
||||
case Some(qTweet) =>
|
||||
qTweet.id
|
||||
case None =>
|
||||
-1
|
||||
}
|
||||
}
|
||||
def getSourceTweetId: Long = {
|
||||
retweetSourceTweet match {
|
||||
case Some(sourceTweet) =>
|
||||
sourceTweet.id
|
||||
case None =>
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package com.twitter.visibility.interfaces.blender
|
||||
|
||||
import com.twitter.visibility.builder.VisibilityResult
|
||||
|
||||
case class CombinedVisibilityResult(
|
||||
tweetVisibilityResult: VisibilityResult,
|
||||
quotedTweetVisibilityResult: Option[VisibilityResult])
|
|
@ -1,17 +0,0 @@
|
|||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = False,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"appsec/sanitization-lib/src/main/scala",
|
||||
"src/thrift/com/twitter/expandodo:cards-scala",
|
||||
"stitch/stitch-core",
|
||||
"visibility/lib/src/main/resources/config",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder/tweets",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/builder/users",
|
||||
"visibility/lib/src/main/scala/com/twitter/visibility/features",
|
||||
],
|
||||
)
|
|
@ -1,187 +0,0 @@
|
|||
package com.twitter.visibility.interfaces.cards
|
||||
|
||||
import com.twitter.appsec.sanitization.URLSafety
|
||||
import com.twitter.decider.Decider
|
||||
import com.twitter.servo.util.Gate
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.tweetypie.{thriftscala => tweetypiethrift}
|
||||
import com.twitter.util.Stopwatch
|
||||
import com.twitter.visibility.VisibilityLibrary
|
||||
import com.twitter.visibility.builder.FeatureMapBuilder
|
||||
import com.twitter.visibility.builder.VisibilityResult
|
||||
import com.twitter.visibility.builder.tweets.CommunityTweetFeatures
|
||||
import com.twitter.visibility.builder.tweets.CommunityTweetFeaturesV2
|
||||
import com.twitter.visibility.builder.tweets.NilTweetLabelMaps
|
||||
import com.twitter.visibility.builder.tweets.TweetFeatures
|
||||
import com.twitter.visibility.builder.users.AuthorFeatures
|
||||
import com.twitter.visibility.builder.users.RelationshipFeatures
|
||||
import com.twitter.visibility.builder.users.ViewerFeatures
|
||||
import com.twitter.visibility.common.CommunitiesSource
|
||||
import com.twitter.visibility.common.UserId
|
||||
import com.twitter.visibility.common.UserRelationshipSource
|
||||
import com.twitter.visibility.common.UserSource
|
||||
import com.twitter.visibility.configapi.configs.VisibilityDeciderGates
|
||||
import com.twitter.visibility.features.CardIsPoll
|
||||
import com.twitter.visibility.features.CardUriHost
|
||||
import com.twitter.visibility.features.FeatureMap
|
||||
import com.twitter.visibility.models.ContentId.CardId
|
||||
import com.twitter.visibility.models.ViewerContext
|
||||
|
||||
object CardVisibilityLibrary {
|
||||
type Type = CardVisibilityRequest => Stitch[VisibilityResult]
|
||||
|
||||
private[this] def getAuthorFeatures(
|
||||
authorIdOpt: Option[Long],
|
||||
authorFeatures: AuthorFeatures
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
authorIdOpt match {
|
||||
case Some(authorId) => authorFeatures.forAuthorId(authorId)
|
||||
case _ => authorFeatures.forNoAuthor()
|
||||
}
|
||||
}
|
||||
|
||||
private[this] def getCardUriFeatures(
|
||||
cardUri: String,
|
||||
enableCardVisibilityLibraryCardUriParsing: Boolean,
|
||||
trackCardUriHost: Option[String] => Unit
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
if (enableCardVisibilityLibraryCardUriParsing) {
|
||||
val safeCardUriHost = URLSafety.getHostSafe(cardUri)
|
||||
trackCardUriHost(safeCardUriHost)
|
||||
|
||||
_.withConstantFeature(CardUriHost, safeCardUriHost)
|
||||
} else {
|
||||
identity
|
||||
}
|
||||
}
|
||||
|
||||
private[this] def getRelationshipFeatures(
|
||||
authorIdOpt: Option[Long],
|
||||
viewerIdOpt: Option[Long],
|
||||
relationshipFeatures: RelationshipFeatures
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
authorIdOpt match {
|
||||
case Some(authorId) => relationshipFeatures.forAuthorId(authorId, viewerIdOpt)
|
||||
case _ => relationshipFeatures.forNoAuthor()
|
||||
}
|
||||
}
|
||||
|
||||
private[this] def getTweetFeatures(
|
||||
tweetOpt: Option[tweetypiethrift.Tweet],
|
||||
tweetFeatures: TweetFeatures
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
tweetOpt match {
|
||||
case Some(tweet) => tweetFeatures.forTweet(tweet)
|
||||
case _ => identity
|
||||
}
|
||||
}
|
||||
|
||||
private[this] def getCommunityFeatures(
|
||||
tweetOpt: Option[tweetypiethrift.Tweet],
|
||||
viewerContext: ViewerContext,
|
||||
communityTweetFeatures: CommunityTweetFeatures
|
||||
): FeatureMapBuilder => FeatureMapBuilder = {
|
||||
tweetOpt match {
|
||||
case Some(tweet) => communityTweetFeatures.forTweet(tweet, viewerContext)
|
||||
case _ => identity
|
||||
}
|
||||
}
|
||||
|
||||
def apply(
|
||||
visibilityLibrary: VisibilityLibrary,
|
||||
userSource: UserSource = UserSource.empty,
|
||||
userRelationshipSource: UserRelationshipSource = UserRelationshipSource.empty,
|
||||
communitiesSource: CommunitiesSource = CommunitiesSource.empty,
|
||||
enableVfParityTest: Gate[Unit] = Gate.False,
|
||||
enableVfFeatureHydration: Gate[Unit] = Gate.False,
|
||||
decider: Decider
|
||||
): Type = {
|
||||
val libraryStatsReceiver = visibilityLibrary.statsReceiver
|
||||
val vfLatencyOverallStat = libraryStatsReceiver.stat("vf_latency_overall")
|
||||
val vfLatencyStitchBuildStat = libraryStatsReceiver.stat("vf_latency_stitch_build")
|
||||
val vfLatencyStitchRunStat = libraryStatsReceiver.stat("vf_latency_stitch_run")
|
||||
val cardUriStats = libraryStatsReceiver.scope("card_uri")
|
||||
val visibilityDeciderGates = VisibilityDeciderGates(decider)
|
||||
|
||||
val authorFeatures = new AuthorFeatures(userSource, libraryStatsReceiver)
|
||||
val viewerFeatures = new ViewerFeatures(userSource, libraryStatsReceiver)
|
||||
val tweetFeatures = new TweetFeatures(NilTweetLabelMaps, libraryStatsReceiver)
|
||||
val communityTweetFeatures = new CommunityTweetFeaturesV2(
|
||||
communitiesSource = communitiesSource,
|
||||
)
|
||||
val relationshipFeatures =
|
||||
new RelationshipFeatures(userRelationshipSource, libraryStatsReceiver)
|
||||
val parityTest = new CardVisibilityLibraryParityTest(libraryStatsReceiver)
|
||||
|
||||
{ r: CardVisibilityRequest =>
|
||||
val elapsed = Stopwatch.start()
|
||||
var runStitchStartMs = 0L
|
||||
|
||||
val viewerId: Option[UserId] = r.viewerContext.userId
|
||||
|
||||
val featureMap =
|
||||
visibilityLibrary
|
||||
.featureMapBuilder(
|
||||
Seq(
|
||||
viewerFeatures.forViewerId(viewerId),
|
||||
getAuthorFeatures(r.authorId, authorFeatures),
|
||||
getCardUriFeatures(
|
||||
cardUri = r.cardUri,
|
||||
enableCardVisibilityLibraryCardUriParsing =
|
||||
visibilityDeciderGates.enableCardVisibilityLibraryCardUriParsing(),
|
||||
trackCardUriHost = { safeCardUriHost: Option[String] =>
|
||||
if (safeCardUriHost.isEmpty) {
|
||||
cardUriStats.counter("empty").incr()
|
||||
}
|
||||
}
|
||||
),
|
||||
getCommunityFeatures(r.tweetOpt, r.viewerContext, communityTweetFeatures),
|
||||
getRelationshipFeatures(r.authorId, r.viewerContext.userId, relationshipFeatures),
|
||||
getTweetFeatures(r.tweetOpt, tweetFeatures),
|
||||
_.withConstantFeature(CardIsPoll, r.isPollCardType)
|
||||
)
|
||||
)
|
||||
|
||||
val response = visibilityLibrary
|
||||
.runRuleEngine(
|
||||
CardId(r.cardUri),
|
||||
featureMap,
|
||||
r.viewerContext,
|
||||
r.safetyLevel
|
||||
)
|
||||
.onSuccess(_ => {
|
||||
val overallStatMs = elapsed().inMilliseconds
|
||||
vfLatencyOverallStat.add(overallStatMs)
|
||||
val runStitchEndMs = elapsed().inMilliseconds
|
||||
vfLatencyStitchRunStat.add(runStitchEndMs - runStitchStartMs)
|
||||
})
|
||||
|
||||
runStitchStartMs = elapsed().inMilliseconds
|
||||
val buildStitchStatMs = elapsed().inMilliseconds
|
||||
vfLatencyStitchBuildStat.add(buildStitchStatMs)
|
||||
|
||||
lazy val hydratedFeatureResponse: Stitch[VisibilityResult] =
|
||||
FeatureMap.resolve(featureMap, libraryStatsReceiver).flatMap { resolvedFeatureMap =>
|
||||
visibilityLibrary.runRuleEngine(
|
||||
CardId(r.cardUri),
|
||||
resolvedFeatureMap,
|
||||
r.viewerContext,
|
||||
r.safetyLevel
|
||||
)
|
||||
}
|
||||
|
||||
val isVfParityTestEnabled = enableVfParityTest()
|
||||
val isVfFeatureHydrationEnabled = enableVfFeatureHydration()
|
||||
|
||||
if (!isVfParityTestEnabled && !isVfFeatureHydrationEnabled) {
|
||||
response
|
||||
} else if (isVfParityTestEnabled && !isVfFeatureHydrationEnabled) {
|
||||
response.applyEffect { resp =>
|
||||
Stitch.async(parityTest.runParityTest(hydratedFeatureResponse, resp))
|
||||
}
|
||||
} else {
|
||||
hydratedFeatureResponse
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue