Making WordPress.org

Changeset 6275


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