WordPress.org

Making WordPress.org

Changeset 6275


Ignore:
Timestamp:
12/14/17 23:45:15 (5 weeks ago)
Author:
iandunn
Message:

Events: Stick an upcoming WordCamp to the response to improve visibility.

Fixes #2994
Props metalandcoffee for the initial patch

Location:
sites/trunk/api.wordpress.org/public_html/events/1.0
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/api.wordpress.org/public_html/events/1.0/index.php

    r6260 r6275  
    1515     * Short-circuit some requests if a traffic spike is larger than we can handle. 
    1616     * 
     17     * THROTTLE_STICKY_WORDCAMPS prevents the additional `SELECT` query in `get_sticky_wordcamp()`. This is the 
     18     * least intrusive throttle for users, and should be tried first. 
     19     * 
     20     * If that doesn't help enough, then start throttling ip2location, since those happen automatically 
     21     * and are therefore less likely to be noticed by users. Throttling Geonames should be a last 
     22     * resort, since users will notice those the most, and will sometimes retry their requests, 
     23     * which makes the problem worse. 
     24     * 
     25     * THROTTLE_{ GEONAMES | IP2LOCATION } 
    1726     * - A value of `0` means that 0% of requests will be throttled. 
    1827     * - A value of `100` means that all cache-miss requests will be short-circuited with an error. 
     
    2231     * In all of the above scenarios, requests that have cached results will always be served. 
    2332     */ 
     33    define( 'THROTTLE_STICKY_WORDCAMPS', false ); 
    2434    define( 'THROTTLE_GEONAMES',    0 ); 
    2535    define( 'THROTTLE_IP2LOCATION', 0 ); 
    2636 
    2737    defined( 'DAY_IN_SECONDS' ) or define( 'DAY_IN_SECONDS', 60 * 60 * 24 ); 
     38    defined( 'WEEK_IN_SECONDS' ) or define( 'WEEK_IN_SECONDS', 7 * DAY_IN_SECONDS ); 
    2839 
    2940    // The test suite just needs the functions defined and doesn't want any headers or output 
     
    132143 
    133144    if ( $location ) { 
    134         $event_args = array(); 
     145        $event_args = array( 
     146            'is_client_core' => is_client_core( $_SERVER['HTTP_USER_AGENT'] ), 
     147        ); 
    135148 
    136149        if ( isset( $_REQUEST['number'] ) ) { 
     
    642655} 
    643656 
     657/** 
     658 * Get upcoming events for the requested location. 
     659 * 
     660 * @param array $args 
     661 * 
     662 * @return array 
     663 */ 
    644664function get_events( $args = array() ) { 
    645665    global $wpdb, $cache_life, $cache_group; 
     
    651671    $args['number'] = $args['number'] ?? 10; 
    652672    $args['number'] = max( 0, min( $args['number'], 100 ) ); 
     673 
     674    // Distances in kilometers 
     675    $event_distances = array( 
     676        'meetup'   => 100, 
     677        'wordcamp' => 400, 
     678    ); 
    653679 
    654680    $cache_key = 'events:' . md5( serialize( $args ) ); 
     
    665691    // If we want nearby events, create a WHERE based on a bounded box of lat/long co-ordinates. 
    666692    if ( !empty( $args['nearby'] ) ) { 
    667         // Distances in kilometers 
    668         $event_distances = array( 
    669             'meetup' => 100, 
    670             'wordcamp' => 400, 
    671         ); 
    672693        $nearby_where = array(); 
    673694 
     
    696717    // Just show upcoming events 
    697718    $wheres[] = '`date_utc` >= %s'; 
     719 
    698720    // Dates are in local-time not UTC, so the API output will contain events that have already happened in some parts of the world. 
    699721    // TODO update this when the UTC dates are stored. 
     
    723745        $sql_values 
    724746    ) ); 
     747 
     748    if ( should_stick_wordcamp( $args, $raw_events ) ) { 
     749        $sticky_wordcamp = get_sticky_wordcamp( $args, $event_distances['wordcamp'] ); 
     750 
     751        if ( $sticky_wordcamp ) { 
     752            array_pop( $raw_events ); 
     753            array_push( $raw_events, $sticky_wordcamp ); 
     754        } 
     755    } 
    725756 
    726757    $events = array(); 
     
    743774 
    744775    wp_cache_set( $cache_key, $events, $cache_group, $cache_life ); 
     776 
    745777    return $events;  
     778} 
     779 
     780/** 
     781 * Determine if conditions for sticking a WordCamp event to the response are met. 
     782 * 
     783 * @param array $request_args 
     784 * @param array $raw_events 
     785 * 
     786 * @return bool 
     787 */ 
     788function should_stick_wordcamp( $request_args, $raw_events ) { 
     789    if ( THROTTLE_STICKY_WORDCAMPS ) { 
     790        return false; 
     791    } 
     792 
     793    // $raw_events already contains all the events that are coming up 
     794    if ( count( $raw_events ) < $request_args['number'] ) { 
     795        return false; 
     796    } 
     797 
     798    if ( ! $request_args['is_client_core'] ) { 
     799        return false; 
     800    } 
     801 
     802    $event_types = array_column( $raw_events, 'type' ); 
     803 
     804    if ( in_array( 'wordcamp', $event_types, true ) ) { 
     805        return false; 
     806    } 
     807 
     808    return true; 
     809} 
     810 
     811/** 
     812 * Get the WordCamp that should be stuck to the response. 
     813 * 
     814 * WordCamps are large, all-day (or multi-day) events that require more of attendees that meetups do. Attendees 
     815 * need to have more advanced notice of when they're occurring. In a city with an active meetup, the camp 
     816 * might not show up in the Events Widget until a week or two before it happens, which isn't enough time. 
     817 * 
     818 * @param array $request_args 
     819 * @param int   $distance 
     820 * 
     821 * @return object|false A database row on success; `false` on failure. 
     822 */ 
     823function get_sticky_wordcamp( $request_args, $distance ) { 
     824    global $wpdb; 
     825 
     826    $sticky_wordcamp_query = build_sticky_wordcamp_query( $request_args, $distance ); 
     827    $sticky_wordcamp       = $wpdb->get_results( $wpdb->prepare( 
     828        $sticky_wordcamp_query['query'], 
     829        $sticky_wordcamp_query['values'] 
     830    ) ); 
     831 
     832    if ( ! empty( $sticky_wordcamp[0]->type ) ) { 
     833        return $sticky_wordcamp[0]; 
     834    } 
     835 
     836    return false; 
     837} 
     838 
     839/** 
     840 * Build the database query for fetching the WordCamp to stick to the response. 
     841 * 
     842 * @param array $request_args 
     843 * @param int   $distance 
     844 * 
     845 * @return array 
     846 */ 
     847function build_sticky_wordcamp_query( $request_args, $distance ) { 
     848    $where = $values = array(); 
     849 
     850    /* 
     851     * How far ahead the query should search for an upcoming camp. It should be high enough that attendees have 
     852     * enough time to prepare for the event, but low enough that it doesn't crowd out meetups that are happening 
     853     * in the mean-time, or make the content of the Events Widget feel less dynamic. Always having fresh content 
     854     * there is one of the things that makes the widget engaging. 
     855     */ 
     856    $date_upper_bound = 6 * WEEK_IN_SECONDS; 
     857 
     858    if ( ! empty( $request_args['nearby'] ) ) { 
     859        $bounded_box = get_bounded_coordinates( $request_args['nearby']['latitude'], $request_args['nearby']['longitude'], $distance ); 
     860        $where[]  = '( `latitude` BETWEEN %f AND %f AND `longitude` BETWEEN %f AND %f )'; 
     861        $values[] = $bounded_box['latitude']['min']; 
     862        $values[] = $bounded_box['latitude']['max']; 
     863        $values[] = $bounded_box['longitude']['min']; 
     864        $values[] = $bounded_box['longitude']['max']; 
     865    } 
     866 
     867    // Allow queries for limiting to specific countries. 
     868    if ( ! empty( $request_args['country'] ) && preg_match( '![a-z]{2}!i', $request_args['country'] ) ) { 
     869        $where[]  = '`country` = %s'; 
     870        $values[] = $request_args['country']; 
     871    } 
     872 
     873    $where = implode( ' AND ', $where ); 
     874 
     875    $query = " 
     876        SELECT 
     877            `type`, `title`, `url`, 
     878            `meetup`, `meetup_url`, 
     879            `date_utc`, `date_utc_offset`, 
     880            `location`, `country`, `latitude`, `longitude` 
     881        FROM `wporg_events` 
     882        WHERE 
     883            `type` = 'wordcamp' AND 
     884            $where AND 
     885            `date_utc` >= %s AND 
     886            `date_utc` <= %s 
     887        ORDER BY `date_utc` ASC 
     888        LIMIT 1" 
     889    ; 
     890 
     891    $values[] = gmdate( 'Y-m-d', time() - DAY_IN_SECONDS ); 
     892    $values[] = gmdate( 'Y-m-d', time() + $date_upper_bound ); 
     893 
     894    return compact( 'query', 'values' ); 
    746895} 
    747896 
  • sites/trunk/api.wordpress.org/public_html/events/1.0/tests/test-index.php

    r6210 r6275  
    600600            ), 
    601601        ), 
    602  
    603602 
    604603        /* 
     
    11321131 * @return false 
    11331132 */ 
    1134 function wp_cache_get() { 
     1133function wp_cache_get( $key, $group = '', $force = false, &$found = null ) { 
    11351134    return false; 
    11361135} 
     
    11391138 * Stub to simulate cache misses, so that the tests always get fresh results 
    11401139 */ 
    1141 function wp_cache_set() { 
     1140function wp_cache_set( $key, $data, $group = '', $expire = 0 ) { 
    11421141    // Intentionally empty 
    11431142} 
Note: See TracChangeset for help on using the changeset viewer.