WordPress.org

Making WordPress.org

Changeset 6275


Ignore:
Timestamp:
12/14/2017 11:45:15 PM (7 months 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.