From a4b8403a94103271f36e1272b76eac57ef74e1bc Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Fri, 28 Feb 2020 13:57:51 +0000 Subject: [PATCH 01/95] wip, adding base audience UI files --- composer.json | 3 +- inc/audiences/namespace.php | 64 +++++++++++++++++++++++++++++++++++++ inc/namespace.php | 3 +- plugin.php | 1 + src/audiences/admin.js | 9 ++++++ webpack.config.js | 3 +- 6 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 inc/audiences/namespace.php create mode 100644 src/audiences/admin.js diff --git a/composer.json b/composer.json index baa21d74..c76ef065 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,8 @@ "description": "AWS Pinpoint Analytics integration for WordPress", "type": "wordpress-plugin", "require": { - "aws/aws-sdk-php": "^3.103.0" + "aws/aws-sdk-php": "^3.103.0", + "johnbillion/extended-cpts": "^4.3.2" }, "license": "GPL-2.0-or-later", "authors": [ diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php new file mode 100644 index 00000000..adb44450 --- /dev/null +++ b/inc/audiences/namespace.php @@ -0,0 +1,64 @@ + [ 'title' ], + ] ); +} + +function meta_boxes() { + remove_meta_box( 'submitdiv', POST_TYPE, 'side' ); + remove_meta_box( 'slugdiv', POST_TYPE, 'normal' ); + + // Add our replaced submitdiv meta box. + add_meta_box( 'audience-options', __( 'Options', 'altis-analytics' ), function () { + echo '
'; + }, POST_TYPE, 'side', 'high' ); +} + +function audience_ui( WP_Post $post ) { + if ( $post->post_type !== POST_TYPE ) { + return; + } + + echo '
'; +} + +function admin_enqueue_scripts() { + wp_enqueue_script( + 'altis-analytics-audience-ui', + plugins_url( 'build/audience.js', dirname( __FILE__, 2 ) ), + [ + 'react', + 'react-dom', + 'wp-i18n', + 'wp-hooks', + 'wp-data', + 'wp-components', + ], + '__AUDIENCE_SCRIPT_HASH__', + true + ); +} diff --git a/inc/namespace.php b/inc/namespace.php index 018e616e..b5c31da5 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -3,12 +3,13 @@ * Altis Analytics. * * @package altis-analytics - * */ namespace Altis\Analytics; function setup() { + // Setup audiences. + Audiences\setup(); // Handle async scripts. add_filter( 'script_loader_tag', __NAMESPACE__ . '\\async_scripts', 20, 2 ); // Load analytics scripts super early. diff --git a/plugin.php b/plugin.php index dcac8e89..b2db0563 100644 --- a/plugin.php +++ b/plugin.php @@ -19,6 +19,7 @@ } require_once 'inc/namespace.php'; +require_once 'inc/audiences/namespace.php'; require_once 'inc/utils/namespace.php'; add_action( 'plugins_loaded', __NAMESPACE__ . '\\setup' ); diff --git a/src/audiences/admin.js b/src/audiences/admin.js new file mode 100644 index 00000000..2097ffae --- /dev/null +++ b/src/audiences/admin.js @@ -0,0 +1,9 @@ +// Audience UI Application. + +const audienceUI = document.getElementById( 'altis-analytics-audience-ui' ); + +if ( !audienceUI ) { + return; +} + +// Mount audience react app. diff --git a/webpack.config.js b/webpack.config.js index 8f05cfff..90e52c31 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,7 +6,8 @@ const BundleAnalyzerPlugin = require("webpack-bundle-analyzer") const sharedConfig = { mode: mode, entry: { - analytics: path.resolve(__dirname, "src/analytics.js"), + analytics: path.resolve( __dirname, "src/analytics.js" ), + audiences: path.resolve( __dirname, "src/audiences/admin.js" ), }, output: { path: path.resolve(__dirname, "build"), From 09af3c001d8f9607df15d285803d3ab7515563a0 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Fri, 28 Feb 2020 18:00:07 +0000 Subject: [PATCH 02/95] basic rule picker --- inc/audiences/namespace.php | 74 +++++++++++++++++++++++++++++++- inc/utils/namespace.php | 17 ++++++-- src/audiences/admin.js | 9 ---- src/audiences/edit/index.js | 57 ++++++++++++++++++++++++ src/audiences/edit/rule-group.js | 0 src/audiences/index.js | 12 ++++++ webpack.config.js | 2 +- 7 files changed, 156 insertions(+), 15 deletions(-) delete mode 100644 src/audiences/admin.js create mode 100644 src/audiences/edit/index.js create mode 100644 src/audiences/edit/rule-group.js create mode 100644 src/audiences/index.js diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index adb44450..acecb0e7 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -24,7 +24,11 @@ function setup() { */ function register_post_type() { register_extended_post_type( POST_TYPE, [ + 'public' => false, + 'show_ui' => true, 'supports' => [ 'title' ], + 'menu_icon' => 'dashicons-groups', + 'menu_position' => 151 ] ); } @@ -46,10 +50,25 @@ function audience_ui( WP_Post $post ) { echo '
'; } +function get_event_data_maps() : array { + global $altis_analytics_event_data_maps; + return array_values( $altis_analytics_event_data_maps ?: [] ); +} + +function register_event_data_map( string $field, string $label ) { + global $altis_analytics_event_data_maps; + $altis_analytics_event_data_maps = $altis_analytics_event_data_maps ?: []; + $altis_analytics_event_data_maps[ $field ] = [ + 'field' => $field, + 'label' => $label, + ]; + ksort( $altis_analytics_event_data_maps ); +} + function admin_enqueue_scripts() { wp_enqueue_script( 'altis-analytics-audience-ui', - plugins_url( 'build/audience.js', dirname( __FILE__, 2 ) ), + plugins_url( 'build/audiences.js', dirname( __FILE__, 2 ) ), [ 'react', 'react-dom', @@ -61,4 +80,57 @@ function admin_enqueue_scripts() { '__AUDIENCE_SCRIPT_HASH__', true ); + + // Register default audience data maps. + register_event_data_map( 'attributes.referer', __( 'Referrer', 'altis-analytics' ) ); + register_event_data_map( 'endpoint.Demographic.Model', __( 'Browser', 'altis-analytics' ) ); + register_event_data_map( 'endpoint.Demographic.ModelVersion', __( 'Browser version', 'altis-analytics' ) ); + register_event_data_map( 'endpoint.Demographic.Locale', __( 'Browser Locale', 'altis-analytics' ) ); + register_event_data_map( 'endpoint.Demographic.Platform', __( 'Operating system', 'altis-analytics' ) ); + register_event_data_map( 'endpoint.Demographic.PlatformVersion', __( 'Operating system version', 'altis-analytics' ) ); + register_event_data_map( 'endpoint.Location.Country', __( 'Country', 'altis-analytics' ) ); + + $maps = get_event_data_maps(); + + // TODO: Move this to API endpoint and fetch aggregation by key on demand. + $query = [ + // 'query' => [ + // 'filter' => [ + // // For the past week. + // [ 'range' => [ + // 'event_timestamp' => [ 'gte' => intval( microtime( false ) - ( 7 * 24 * 60 * 60 * 1000 ) ) ], + // ] ], + // ], + // ], + 'size' => 0, + 'aggs' => [], + 'sort' => [ 'event_timestamp' => 'desc' ], + ]; + foreach ( $maps as $map ) { + // Query for all the different values available for each. + $query['aggs'][ $map['field'] ] = [ + 'terms' => [ + 'field' => "{$map['field']}.keyword", + 'size' => 100, + ], + ]; + } + + $result = query( $query ); + + $data = [ + 'DataMaps' => $maps, + 'Data' => $result['aggregations'] ?? (object) [], + ]; + + wp_add_inline_script( + 'altis-analytics-audience-ui', + sprintf( + 'window.Altis = window.Altis || {};' . + 'window.Altis.Analytics = window.Altis.Analytics || {};' . + 'window.Altis.Analytics.Audiences = %s;', + wp_json_encode( $data ) + ), + 'before' + ); } diff --git a/inc/utils/namespace.php b/inc/utils/namespace.php index 936a96a0..3024435e 100644 --- a/inc/utils/namespace.php +++ b/inc/utils/namespace.php @@ -81,12 +81,19 @@ function get_elasticsearch_url() : string { * * @param array $query A full elasticsearch Query DSL array. * @param array $params URL query parameters to append to request URL. + * @param string $path The endpoint to query against, defaults to _search. * @return array|null */ -function query( array $query, array $params = [] ) : ?array { +function query( array $query, array $params = [], string $path = '_search' ) : ?array { + + // Sanitize path. + $path = trim( $path, '/' ); // Get URL. - $url = add_query_arg( $params, get_elasticsearch_url() . '/analytics*/_search' ); + $url = add_query_arg( $params, get_elasticsearch_url() . '/analytics*/' . $path ); + + // Ensure URL is legit. + $url = esc_url_raw( $url ); $response = wp_remote_post( $url, [ 'headers' => [ @@ -98,12 +105,14 @@ function query( array $query, array $params = [] ) : ?array { if ( wp_remote_retrieve_response_code( $response ) !== 200 || is_wp_error( $response ) ) { if ( is_wp_error( $response ) ) { trigger_error( sprintf( - 'Analytics: elasticsearch query failed: %s', + "Analytics: elasticsearch query failed:\n%s\n%s", + $url, $response->get_error_message() ), E_USER_WARNING ); } else { trigger_error( sprintf( - "Analytics: elasticsearch query failed:\n%s\n%s", + "Analytics: elasticsearch query failed:\n%s\n%s\n%s", + $url, json_encode( $query ), wp_remote_retrieve_body( $response ) ), E_USER_WARNING ); diff --git a/src/audiences/admin.js b/src/audiences/admin.js deleted file mode 100644 index 2097ffae..00000000 --- a/src/audiences/admin.js +++ /dev/null @@ -1,9 +0,0 @@ -// Audience UI Application. - -const audienceUI = document.getElementById( 'altis-analytics-audience-ui' ); - -if ( !audienceUI ) { - return; -} - -// Mount audience react app. diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js new file mode 100644 index 00000000..6cf33cc1 --- /dev/null +++ b/src/audiences/edit/index.js @@ -0,0 +1,57 @@ +import React, { useState } from 'react'; + +const Edit = () => { + const [ match, setMatch ] = useState( 'any' ); + const [ operator, setOperator ] = useState( '=' ); + const [ field, setField ] = useState( Altis.Analytics.Audiences.DataMaps[0].field ); + const [ value, setValue ] = useState( '' ); + + const valueField = Altis.Analytics.Audiences.Data[ field ] || { buckets: [] }; + + return ( +
+

+ Include visitors who match + + of the following: +

+ +
+ + + +
+
+ ); +} + +export default Edit; diff --git a/src/audiences/edit/rule-group.js b/src/audiences/edit/rule-group.js new file mode 100644 index 00000000..e69de29b diff --git a/src/audiences/index.js b/src/audiences/index.js new file mode 100644 index 00000000..f6df5ce2 --- /dev/null +++ b/src/audiences/index.js @@ -0,0 +1,12 @@ +// Audience UI Application. +import React from 'react'; +import ReactDOM from 'react-dom'; +import Edit from './edit'; + +// Is our audience +const AudienceUI = document.getElementById( 'altis-analytics-audience-ui' ); + +if ( AudienceUI ) { + // Mount audience react app. + ReactDOM.render( , AudienceUI ); +} diff --git a/webpack.config.js b/webpack.config.js index 90e52c31..cc87f8c5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,7 +7,7 @@ const sharedConfig = { mode: mode, entry: { analytics: path.resolve( __dirname, "src/analytics.js" ), - audiences: path.resolve( __dirname, "src/audiences/admin.js" ), + audiences: path.resolve( __dirname, "src/audiences/index.js" ), }, output: { path: path.resolve(__dirname, "build"), From c528a36dbab07a837f1488ebc9d1edcaa06eefa0 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Fri, 28 Feb 2020 18:48:52 +0000 Subject: [PATCH 03/95] remembered the milliseconds helper function --- inc/audiences/namespace.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index acecb0e7..3c2fe850 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -7,6 +7,7 @@ namespace Altis\Analytics\Audiences; +use function Altis\Analytics\Utils\milliseconds; use function Altis\Analytics\Utils\query; use WP_Post; @@ -98,7 +99,7 @@ function admin_enqueue_scripts() { // 'filter' => [ // // For the past week. // [ 'range' => [ - // 'event_timestamp' => [ 'gte' => intval( microtime( false ) - ( 7 * 24 * 60 * 60 * 1000 ) ) ], + // 'event_timestamp' => [ 'gte' => intval( milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ) ) ], // ] ], // ], // ], From 6f4568976d537fe10c07ebb82d1953b7b2ceeb71 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Wed, 4 Mar 2020 13:21:43 +0000 Subject: [PATCH 04/95] added functional basic group and rule editing UI --- src/audiences/edit/index.js | 184 +++++++++++++++++++++------ src/audiences/edit/rule-group.js | 0 src/audiences/edit/select-include.js | 17 +++ webpack.config.js | 8 +- 4 files changed, 163 insertions(+), 46 deletions(-) delete mode 100644 src/audiences/edit/rule-group.js create mode 100644 src/audiences/edit/select-include.js diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index 6cf33cc1..afba6376 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -1,55 +1,155 @@ import React, { useState } from 'react'; +import SelectInclude from './select-include'; + +const { __ } = wp.i18n; +const { Button } = wp.components; + +const defaultRule = { + field: Altis.Analytics.Audiences.DataMaps[ 0 ].field, + operator: '=', // =, !=, *, !* + value: '', // mixed + type: 'string', // data type, string or number +}; + +const defaultGroup = { + include: 'any', // ANY, ALL, NONE + rules: [ + defaultRule + ], +}; const Edit = () => { - const [ match, setMatch ] = useState( 'any' ); - const [ operator, setOperator ] = useState( '=' ); - const [ field, setField ] = useState( Altis.Analytics.Audiences.DataMaps[0].field ); - const [ value, setValue ] = useState( '' ); - const valueField = Altis.Analytics.Audiences.Data[ field ] || { buckets: [] }; + // Collect the audience state. + const [ audience, setAudience ] = useState( { + include: 'any', // ANY, ALL, NONE + groups: [ + defaultGroup, + ], + } ); + + const updateAudience = ( newAudience ) => { + setAudience( audienceToUpdate => { + return { ...audienceToUpdate, ...newAudience }; + } ); + }; + + const updateGroup = ( groupId, group ) => { + const groups = audience.groups.slice(); + const newGroup = Object.assign( {}, groups[ groupId ], group ); + groups.splice( groupId, 1, newGroup ); + updateAudience( { groups } ); + }; + + const updateRule = ( groupId, ruleId, rule ) => { + const rules = audience.groups[ groupId ].rules.slice(); + const newRule = Object.assign( {}, rules[ ruleId ], rule ); + rules.splice( ruleId, 1, newRule ); + updateGroup( groupId, { rules } ); + }; return (

- Include visitors who match - - of the following: + { __( 'Include visitors who match', 'altis-analytics' ) } + { ' ' } + updateAudience( { include: e.target.value } ) } + value={ audience.include } + name="audience_include" + /> + { ' ' } + { __( 'of the following', 'altis-analytics' ) }:

-
- - - -
+ { audience.groups.map( ( group, groupId ) => { + return ( +
+ { group.rules.length > 1 && ( +

+ updateGroup( groupId, { include: e.target.value } ) } + value={ group.include } + name={`audience_group[${groupId}][include]`} + /> +

+ ) } + { group.rules.map( ( rule, ruleId ) => { + return ( +
+ + + + + { group.rules.length > 1 && ( + + ) } +
+ ); + } ) } + + + { audience.groups.length > 1 && ( + + ) } +
+ ); + } ) } +
); } diff --git a/src/audiences/edit/rule-group.js b/src/audiences/edit/rule-group.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/audiences/edit/select-include.js b/src/audiences/edit/select-include.js new file mode 100644 index 00000000..38a136d7 --- /dev/null +++ b/src/audiences/edit/select-include.js @@ -0,0 +1,17 @@ +import React from 'react'; + +const SelectInclude = ( { value, onChange, name } ) => { + return ( + + ) +} + +export default SelectInclude; diff --git a/webpack.config.js b/webpack.config.js index cc87f8c5..80b5ae64 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -41,10 +41,6 @@ const sharedConfig = { noEmitOnErrors: true, }, plugins: [], - devtool: - mode === "production" - ? "cheap-module-source-map" - : "cheap-module-eval-source-map", externals: { "Altis": "Altis", "wp": "wp", @@ -53,6 +49,10 @@ const sharedConfig = { }, }; +if ( mode !== "production" ) { + sharedConfig.devtool = "cheap-module-eval-source-map"; +} + if (process.env.ANALYSE_BUNDLE) { // Add bundle analyser. sharedConfig.plugins.push(new BundleAnalyzerPlugin()); From 746b3dd193cab0d0a096c3aacec4f2ee6a427d63 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Wed, 4 Mar 2020 17:48:21 +0000 Subject: [PATCH 05/95] added styling and file organisation to rule editor UI --- package-lock.json | 109 ++++++++++++++++++ package.json | 5 +- src/audiences/edit/components/editor.js | 13 +++ src/audiences/edit/components/group.js | 29 +++++ src/audiences/edit/components/options.js | 42 +++++++ src/audiences/edit/components/rule.js | 19 +++ .../edit/components/select-include.js | 32 +++++ src/audiences/edit/data/defaults.js | 20 ++++ src/audiences/edit/index.js | 91 ++++++++------- src/audiences/edit/select-include.js | 17 --- webpack.config.js | 29 ++++- 11 files changed, 339 insertions(+), 67 deletions(-) create mode 100644 src/audiences/edit/components/editor.js create mode 100644 src/audiences/edit/components/group.js create mode 100644 src/audiences/edit/components/options.js create mode 100644 src/audiences/edit/components/rule.js create mode 100644 src/audiences/edit/components/select-include.js create mode 100644 src/audiences/edit/data/defaults.js delete mode 100644 src/audiences/edit/select-include.js diff --git a/package-lock.json b/package-lock.json index 1f506b3b..2472615c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1363,6 +1363,29 @@ "to-fast-properties": "^2.0.0" } }, + "@emotion/is-prop-valid": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.7.tgz", + "integrity": "sha512-OPkKzUeiid0vEKjZqnGcy2mzxjIlCffin+L2C02pdz/bVlt5zZZE2VzO0D3XOPnH0NEeF21QNKSXiZphjr4xiQ==", + "requires": { + "@emotion/memoize": "0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -1813,6 +1836,22 @@ "object.assign": "^4.1.0" } }, + "babel-plugin-styled-components": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.7.tgz", + "integrity": "sha512-MBMHGcIA22996n9hZRf/UJLVVgkEOITuR2SvjHLb5dSTUyR4ZRGn+ngITapes36FI3WLxZHfRhkA1ffHxihOrg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-module-imports": "^7.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -2125,6 +2164,11 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "caniuse-lite": { "version": "1.0.30001027", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001027.tgz", @@ -2462,6 +2506,21 @@ "randomfill": "^1.0.3" } }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" + }, + "css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", @@ -2600,6 +2659,12 @@ "stream-shift": "^1.0.0" } }, + "dynamic-public-path-webpack-plugin": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dynamic-public-path-webpack-plugin/-/dynamic-public-path-webpack-plugin-1.0.4.tgz", + "integrity": "sha1-5UUbG3AOhF8qQAnd96mo7byZRSc=", + "dev": true + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -3928,6 +3993,14 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -4995,6 +5068,11 @@ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, + "postcss-value-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz", + "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==" + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -5539,6 +5617,11 @@ "safe-buffer": "^5.0.1" } }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -5858,6 +5941,23 @@ "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", "dev": true }, + "styled-components": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.0.1.tgz", + "integrity": "sha512-E0xKTRIjTs4DyvC1MHu/EcCXIj6+ENCP8hP01koyoADF++WdBUOrSGwU1scJRw7/YaYOhDvvoad6VlMG+0j53A==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^0.8.3", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -6412,6 +6512,15 @@ } } }, + "webpack-subresource-integrity": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.4.0.tgz", + "integrity": "sha512-GB1kB/LwAWC3CxwcedGhMkxGpNZxSheCe1q+KJP1bakuieAdX/rGHEcf5zsEzhKXpqsGqokgsDoD9dIkr61VDQ==", + "dev": true, + "requires": { + "webpack-sources": "^1.3.0" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index b337a7dd..d065204c 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@babel/runtime": "^7.8.4", "@wordpress/babel-preset-default": "^4.10.0", "babel-loader": "^8.0.6", + "styled-components": "^5.0.1", "ua-parser-js": "^0.7.21", "webpack": "^4.41.6", "webpack-bundle-analyzer": "^3.6.0", @@ -31,7 +32,9 @@ }, "devDependencies": { "babel-eslint": "^10.0.3", + "dynamic-public-path-webpack-plugin": "^1.0.4", "eslint": "^6.8.0", - "eslint-config-prettier": "^6.10.0" + "eslint-config-prettier": "^6.10.0", + "webpack-subresource-integrity": "^1.4.0" } } diff --git a/src/audiences/edit/components/editor.js b/src/audiences/edit/components/editor.js new file mode 100644 index 00000000..973d7f11 --- /dev/null +++ b/src/audiences/edit/components/editor.js @@ -0,0 +1,13 @@ +import styled from 'styled-components'; + +const Editor = styled.div.attrs( () => ( { + className: "audience-editor", +} ) )` + margin: 0 0 40px; + + .audience-editor__include { + margin: 20px 0; + } +`; + +export default Editor; diff --git a/src/audiences/edit/components/group.js b/src/audiences/edit/components/group.js new file mode 100644 index 00000000..dc803e5d --- /dev/null +++ b/src/audiences/edit/components/group.js @@ -0,0 +1,29 @@ +import styled from 'styled-components'; + +const Group = styled.div.attrs( () => ( { + className: "audience-editor__group", +} ) )` + background: rgba(0, 0, 0, 0.02); + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, .1); + padding: 20px; + margin: 0 0 20px; + + .audience-editor__group-header { + margin: 0 0 15px; + display: flex; + align-items: baseline; + } + + h3 { + margin: 0 20px 0 0; + text-transform: lowercase; + font-variant: small-caps; + } + + .components-button { + margin-right: 10px; + } +`; + +export default Group; diff --git a/src/audiences/edit/components/options.js b/src/audiences/edit/components/options.js new file mode 100644 index 00000000..79232550 --- /dev/null +++ b/src/audiences/edit/components/options.js @@ -0,0 +1,42 @@ +import React from 'react'; +import styled from 'styled-components'; +import { defaultAudience } from '../data/defaults'; + +const { Button } = wp.components; +const { __ } = wp.i18n; + +const StyledOptions = styled.div.attrs( () => ( { + className: "audience-options" +} ) )` + +`; + +const Options = props => { + const { + audience, + } = props; + + return ( + +

{ __( 'Estimated audience size', 'altis-analytics' ) }

+

120

+ + +
+ ); +} + +Options.defaultProps = { + audience: defaultAudience, +}; + +export default Options; diff --git a/src/audiences/edit/components/rule.js b/src/audiences/edit/components/rule.js new file mode 100644 index 00000000..a42daa98 --- /dev/null +++ b/src/audiences/edit/components/rule.js @@ -0,0 +1,19 @@ +import styled from 'styled-components'; + +const Rule = styled.div.attrs( () => ( { + className: "audience-editor__rule", +} ) )` + margin: 0 0 15px; + display: flex; + + select { + flex: 1; + margin-right: 5px; + } + + .audience-editor__rule-operator { + flex 0; + } +`; + +export default Rule; diff --git a/src/audiences/edit/components/select-include.js b/src/audiences/edit/components/select-include.js new file mode 100644 index 00000000..f2aa3385 --- /dev/null +++ b/src/audiences/edit/components/select-include.js @@ -0,0 +1,32 @@ +import React from 'react'; +import styled from 'styled-components'; + +const { __ } = wp.i18n; + +const StyledSelect = styled.select` + vertical-align: middle; + + &:not(:hover, :focus) { + border: none; + background: none; + margin-right: 8px; + } +`; + +const SelectInclude = ( { value, onChange, label = '', name } ) => { + const id = name.replace( /\W+/g, '-' ).replace( /^\W+.*?\W+$/, '' ); + return ( + + + + + + ) +} + +export default SelectInclude; diff --git a/src/audiences/edit/data/defaults.js b/src/audiences/edit/data/defaults.js new file mode 100644 index 00000000..7dbf2a7d --- /dev/null +++ b/src/audiences/edit/data/defaults.js @@ -0,0 +1,20 @@ +export const defaultRule = { + field: Altis.Analytics.Audiences.DataMaps[ 0 ].field, + operator: '=', // =, !=, *, !* + value: '', // mixed + type: 'string', // data type, string or number +}; + +export const defaultGroup = { + include: 'all', // ANY, ALL, NONE + rules: [ + defaultRule + ], +}; + +export const defaultAudience = { + include: 'any', // ANY, ALL, NONE + groups: [ + defaultGroup, + ], +}; diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index afba6376..ae60ee99 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -1,32 +1,24 @@ import React, { useState } from 'react'; -import SelectInclude from './select-include'; +import ReactDOM from 'react-dom'; +import Editor from './components/editor'; +import Group from './components/group'; +import Options from './components/options'; +import Rule from './components/rule'; +import SelectInclude from './components/select-include'; +import { + defaultAudience, + defaultGroup, + defaultRule, +} from './data/defaults'; const { __ } = wp.i18n; const { Button } = wp.components; -const defaultRule = { - field: Altis.Analytics.Audiences.DataMaps[ 0 ].field, - operator: '=', // =, !=, *, !* - value: '', // mixed - type: 'string', // data type, string or number -}; - -const defaultGroup = { - include: 'any', // ANY, ALL, NONE - rules: [ - defaultRule - ], -}; +const AudienceOptionsUI = document.getElementById( 'altis-analytics-audience-options' ); const Edit = () => { - // Collect the audience state. - const [ audience, setAudience ] = useState( { - include: 'any', // ANY, ALL, NONE - groups: [ - defaultGroup, - ], - } ); + const [ audience, setAudience ] = useState( defaultAudience ); const updateAudience = ( newAudience ) => { setAudience( audienceToUpdate => { @@ -49,35 +41,35 @@ const Edit = () => { }; return ( -
-

- { __( 'Include visitors who match', 'altis-analytics' ) } - { ' ' } + + { AudienceOptionsUI && ReactDOM.createPortal( , AudienceOptionsUI ) } + +

updateAudience( { include: e.target.value } ) } value={ audience.include } name="audience_include" + label={ __( 'groups', 'altis-analytics' ) } /> - { ' ' } - { __( 'of the following', 'altis-analytics' ) }: -

+
{ audience.groups.map( ( group, groupId ) => { return ( -
- { group.rules.length > 1 && ( -

- updateGroup( groupId, { include: e.target.value } ) } - value={ group.include } - name={`audience_group[${groupId}][include]`} - /> -

- ) } + +
+

{ __( 'Group', 'altis-analytics' ) } { groupId + 1 }

+ updateGroup( groupId, { include: e.target.value } ) } + value={ group.include } + name={ `audience_group[${ groupId }][include]` } + label={ __( 'rules', 'altis-analytics' ) } + /> +
{ group.rules.map( ( rule, ruleId ) => { return ( -
+ - - - - - ) -} - -export default SelectInclude; diff --git a/webpack.config.js b/webpack.config.js index 80b5ae64..027ca6c2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,7 +1,10 @@ -const path = require("path"); +const path = require( "path" ); +const webpack = require( 'webpack' ); const mode = process.env.NODE_ENV || "production"; const BundleAnalyzerPlugin = require("webpack-bundle-analyzer") .BundleAnalyzerPlugin; +const DynamicPublicPathPlugin = require( 'dynamic-public-path-webpack-plugin' ); +const SriPlugin = require( 'webpack-subresource-integrity' ); const sharedConfig = { mode: mode, @@ -14,7 +17,8 @@ const sharedConfig = { filename: "[name].js", publicPath: ".", libraryTarget: "this", - jsonpFunction: "HManalyticsJSONP", + jsonpFunction: "AltisAnalyticsJSONP", + crossOriginLoading: 'anonymous', }, module: { rules: [ @@ -30,7 +34,7 @@ const sharedConfig = { ], plugins: [ require("@babel/plugin-transform-runtime"), - require('@wordpress/babel-plugin-import-jsx-pragma'), + require( '@wordpress/babel-plugin-import-jsx-pragma' ), ], }, }, @@ -40,7 +44,11 @@ const sharedConfig = { optimization: { noEmitOnErrors: true, }, - plugins: [], + plugins: [ + new webpack.EnvironmentPlugin( { + SC_ATTR: 'data-styled-components-altis-analytics', + } ), + ], externals: { "Altis": "Altis", "wp": "wp", @@ -49,13 +57,24 @@ const sharedConfig = { }, }; +if ( mode === "production" ) { + sharedConfig.plugins.push( new DynamicPublicPathPlugin( { + externalGlobal: 'window.Altis.Analytics.BuildURL', + chunkName: 'audiences', + } ) ); + sharedConfig.plugins.push( new SriPlugin( { + hashFuncNames: [ 'sha384' ], + enabled: true, + } ) ); +} + if ( mode !== "production" ) { sharedConfig.devtool = "cheap-module-eval-source-map"; } if (process.env.ANALYSE_BUNDLE) { // Add bundle analyser. - sharedConfig.plugins.push(new BundleAnalyzerPlugin()); + sharedConfig.plugins.push( new BundleAnalyzerPlugin() ); } module.exports = sharedConfig; From fb9487769ff7f748d84d3f7cbcf6655b34680b5b Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Wed, 4 Mar 2020 17:49:06 +0000 Subject: [PATCH 06/95] updated deploy excludes to leave src directory --- .circleci/deploy-exclude.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/deploy-exclude.txt b/.circleci/deploy-exclude.txt index 0248edd7..5e9f300f 100755 --- a/.circleci/deploy-exclude.txt +++ b/.circleci/deploy-exclude.txt @@ -10,5 +10,4 @@ node_modules package.json package-lock.json -src webpack.config.js From af75df9690e6d8e3f9c5c9d239ce7702409dddf7 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Wed, 4 Mar 2020 17:49:39 +0000 Subject: [PATCH 07/95] adding scaffolding for audience API endpoints --- inc/audiences/namespace.php | 51 +++++++++++++++++++--------- inc/audiences/rest_api/namespace.php | 18 ++++++++++ plugin.php | 1 + 3 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 inc/audiences/rest_api/namespace.php diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 3c2fe850..bcd93c01 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -11,26 +11,36 @@ use function Altis\Analytics\Utils\query; use WP_Post; -const POST_TYPE = 'audiences'; +const POST_TYPE = 'audience'; function setup() { add_action( 'init', __NAMESPACE__ . '\\register_post_type' ); add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\\admin_enqueue_scripts' ); add_action( 'edit_form_after_title', __NAMESPACE__ . '\\audience_ui' ); add_action( 'add_meta_boxes_' . POST_TYPE, __NAMESPACE__ . '\\meta_boxes' ); + + // Setup Audience REST API. + add_action( 'rest_api_init', __NAMESPACE__ . '\\REST_API\\init' ); } /** * Setup the audiences data store. */ function register_post_type() { - register_extended_post_type( POST_TYPE, [ - 'public' => false, - 'show_ui' => true, - 'supports' => [ 'title' ], - 'menu_icon' => 'dashicons-groups', - 'menu_position' => 151 - ] ); + register_extended_post_type( + POST_TYPE, + [ + 'public' => false, + 'show_ui' => true, + 'supports' => [ 'title' ], + 'menu_icon' => 'dashicons-groups', + 'menu_position' => 151, + ], + [ + 'singular' => __( 'Audience', 'altis-analytics' ), + 'plural' => __( 'Audiences', 'altis-analytics' ), + ] + ); } function meta_boxes() { @@ -82,6 +92,8 @@ function admin_enqueue_scripts() { true ); + wp_enqueue_style( 'wp-components' ); + // Register default audience data maps. register_event_data_map( 'attributes.referer', __( 'Referrer', 'altis-analytics' ) ); register_event_data_map( 'endpoint.Demographic.Model', __( 'Browser', 'altis-analytics' ) ); @@ -95,18 +107,23 @@ function admin_enqueue_scripts() { // TODO: Move this to API endpoint and fetch aggregation by key on demand. $query = [ - // 'query' => [ - // 'filter' => [ - // // For the past week. - // [ 'range' => [ - // 'event_timestamp' => [ 'gte' => intval( milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ) ) ], - // ] ], - // ], - // ], + 'query' => [ + 'bool' => [ + 'filter' => [ + // For the past week. + [ + 'range' => [ + 'event_timestamp' => [ 'gte' => milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ) ], + ], + ], + ], + ], + ], 'size' => 0, 'aggs' => [], 'sort' => [ 'event_timestamp' => 'desc' ], ]; + foreach ( $maps as $map ) { // Query for all the different values available for each. $query['aggs'][ $map['field'] ] = [ @@ -129,7 +146,9 @@ function admin_enqueue_scripts() { sprintf( 'window.Altis = window.Altis || {};' . 'window.Altis.Analytics = window.Altis.Analytics || {};' . + 'window.Altis.Analytics.BuildURL = %s;' . 'window.Altis.Analytics.Audiences = %s;', + wp_json_encode( plugins_url( 'build/', dirname( __FILE__, 2 ) ) ), wp_json_encode( $data ) ), 'before' diff --git a/inc/audiences/rest_api/namespace.php b/inc/audiences/rest_api/namespace.php new file mode 100644 index 00000000..2f2f1587 --- /dev/null +++ b/inc/audiences/rest_api/namespace.php @@ -0,0 +1,18 @@ + Date: Thu, 5 Mar 2020 18:37:02 +0000 Subject: [PATCH 08/95] adding data fetching from api using hooks --- package-lock.json | 20 ++++++++++++++ package.json | 1 + src/audiences/edit/components/options.js | 26 +++++++++++++++--- src/audiences/edit/data/defaults.js | 2 +- src/audiences/edit/data/index.js | 24 +++++++++++++++++ src/audiences/edit/index.js | 34 +++++++++++++++++------- 6 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 src/audiences/edit/data/index.js diff --git a/package-lock.json b/package-lock.json index 2472615c..09297fbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5182,6 +5182,16 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "query-string": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.11.1.tgz", + "integrity": "sha512-1ZvJOUl8ifkkBxu2ByVM/8GijMIPx+cef7u3yroO3Ogm4DOdZcF5dcrWTIlSHe3Pg/mtlt6/eFjObDfJureZZA==", + "requires": { + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -5804,6 +5814,11 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, + "split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -5885,6 +5900,11 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", diff --git a/package.json b/package.json index d065204c..3f63ae39 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@babel/runtime": "^7.8.4", "@wordpress/babel-preset-default": "^4.10.0", "babel-loader": "^8.0.6", + "query-string": "^6.11.1", "styled-components": "^5.0.1", "ua-parser-js": "^0.7.21", "webpack": "^4.41.6", diff --git a/src/audiences/edit/components/options.js b/src/audiences/edit/components/options.js index 79232550..4d54f315 100644 --- a/src/audiences/edit/components/options.js +++ b/src/audiences/edit/components/options.js @@ -1,6 +1,7 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; import { defaultAudience } from '../data/defaults'; +import { getEstimate } from '../data'; const { Button } = wp.components; const { __ } = wp.i18n; @@ -16,10 +17,29 @@ const Options = props => { audience, } = props; + const [ estimate, setEstimate ] = useState( { count: 0, histogram: [] } ); + const fetchEstimate = () => { + ( async () => { + const estimateResponse = await getEstimate( audience ); + setEstimate( estimateResponse ); + } )(); + } + + // Get initial estimate. + useEffect( fetchEstimate, [ audience ] ); + return ( -

{ __( 'Estimated audience size', 'altis-analytics' ) }

-

120

+
+

{ __( 'Audience Estimate', 'altis-analytics' ) }

+

{ estimate.count } { __( 'last week' ) }

+ +
{ group.rules.map( ( rule, ruleId ) => { + const currentField = fields.filter( fieldData => fieldData.name === rule.field )[0] ?? {}; + return ( - + { ! currentField.type || currentField.type === 'string' && ( + + { [ '=', '!=' ].indexOf( rule.operator ) >= 0 && ( + + ) } + { [ '*=', '!*', '^=' ].indexOf( rule.operator ) >= 0 && ( + updateRule( groupId, ruleId, { value: e.target.value } ) } + name={`audience[groups][${groupId}][rules][${ruleId}][value]`} + /> + ) } + + ) } + { currentField.type === 'number' && ( + updateRule( groupId, ruleId, { value: e.target.value } ) } + name={`audience[groups][${groupId}][rules][${ruleId}][value]`} + /> + ) } { group.rules.length > 1 && ( diff --git a/src/audiences/index.css b/src/audiences/index.css new file mode 100644 index 00000000..5d844b67 --- /dev/null +++ b/src/audiences/index.css @@ -0,0 +1,20 @@ +/** + * Audiences admin listing styles. + */ + +th#last_modified { + width: 10em; +} + +.column-estimate .audience-estimate { + display: flex; +} + +.column-estimate .audience-estimate__totals { + flex: 1; +} + +.column-estimate .audience-estimate svg { + flex: 0 1 200px; + width: 200px; +} diff --git a/src/audiences/index.js b/src/audiences/index.js index bcc58fb9..6935db92 100644 --- a/src/audiences/index.js +++ b/src/audiences/index.js @@ -2,6 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import Edit from './edit'; +import Estimate from './edit/components/estimate'; // Get the audience UI placeholder. const AudienceUI = document.getElementById( 'altis-analytics-audience-ui' ); @@ -14,3 +15,15 @@ if ( AudienceUI ) { fields={ JSON.parse( AudienceUI.dataset.fields || [] ) } />, AudienceUI ); } + +// Get the audience UI placeholder. +const AudienceEstimates = document.querySelectorAll( '.altis-analytics-audience-estimate' ); + +// Render any estimate blocks on page. +for ( let i = 0; i < AudienceEstimates.length; i++ ) { + const element = AudienceEstimates[ i ]; + // Mount audience react app. + ReactDOM.render( , element ); +} From 9971f92abd9169eb06edfff9a990201955c0be8c Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 10 Mar 2020 11:56:41 +0000 Subject: [PATCH 13/95] move estimate styling into the component --- src/audiences/edit/components/estimate.js | 42 +++++++++++++++-------- src/audiences/index.css | 13 ------- src/audiences/index.js | 1 + 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index a3490d99..63aff6b0 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -5,18 +5,6 @@ import { Sparklines, SparklinesLine } from 'react-sparklines'; const { __ } = wp.i18n; -const StyledEstimate = styled.div.attrs( () => ( { - className: "audience-estimate" -} ) )` - h4 { - font-variant: small-caps; - text-transform: lowercase; - } - strong { - font-size: 150%; - } -`; - const Estimate = props => { const { audience, @@ -49,7 +37,7 @@ const Estimate = props => { } return ( - +
{ title &&

{ title }

} { estimate.error && ( @@ -81,8 +69,32 @@ const Estimate = props => { ) } - +
); } -export default Estimate; +const StyledEstimate = styled(Estimate)` + display: ${ props => props.horizontal ? 'flex' : 'block' }; + + h4 { + font-variant: small-caps; + text-transform: lowercase; + flex: 0; + width: 100%; + } + + .audience-estimate__totals strong { + font-size: 150%; + } + + .audience-estimate__totals { + flex: 1; + } + + svg { + flex: 0 0 ${ props => props.horizontal ? '200px' : '100%' }; + width: ${ props => props.horizontal ? '200px' : '100%' }; + } +`; + +export default StyledEstimate; diff --git a/src/audiences/index.css b/src/audiences/index.css index 5d844b67..bfcfedfe 100644 --- a/src/audiences/index.css +++ b/src/audiences/index.css @@ -5,16 +5,3 @@ th#last_modified { width: 10em; } - -.column-estimate .audience-estimate { - display: flex; -} - -.column-estimate .audience-estimate__totals { - flex: 1; -} - -.column-estimate .audience-estimate svg { - flex: 0 1 200px; - width: 200px; -} diff --git a/src/audiences/index.js b/src/audiences/index.js index 6935db92..42f478bb 100644 --- a/src/audiences/index.js +++ b/src/audiences/index.js @@ -24,6 +24,7 @@ for ( let i = 0; i < AudienceEstimates.length; i++ ) { const element = AudienceEstimates[ i ]; // Mount audience react app. ReactDOM.render( , element ); } From acf765e5ec1ed274c0625eafce81053439efa9ce Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 10 Mar 2020 14:09:31 +0000 Subject: [PATCH 14/95] remove bulk and quick edit for audiences --- inc/audiences/namespace.php | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index d7de6e4f..a2db7bbb 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -24,6 +24,8 @@ function setup() { add_action( 'add_meta_boxes_' . POST_TYPE, __NAMESPACE__ . '\\meta_boxes' ); add_action( 'save_post_' . POST_TYPE, __NAMESPACE__ . '\\save_post', 10, 2 ); add_filter( 'wp_insert_post_data', __NAMESPACE__ . '\\default_post_settings', 10, 2 ); + add_filter( 'post_row_actions', __NAMESPACE__ . '\\remove_quick_edit', 10, 2 ); + add_filter( 'bulk_actions-edit-' . POST_TYPE, __NAMESPACE__ . '\\remove_bulk_actions' ); // Setup Audience REST API. add_action( 'rest_api_init', __NAMESPACE__ . '\\REST_API\\init' ); @@ -43,6 +45,7 @@ function register_post_type() { 'menu_position' => 151, 'show_in_rest' => true, 'rest_base' => 'audiences', + 'hierarchical' => false, 'admin_cols' => [ 'estimate' => [ 'title' => __( 'Estimate', 'altis-analytics' ), @@ -166,6 +169,33 @@ function default_post_settings( array $data ) : array { return $data; } +/** + * Remove quick edit inline action. + * + * @param array $actions Inline actions array. + * @param WP_Post $post The current post. + * @return array + */ +function remove_quick_edit( array $actions, WP_Post $post ) : array { + if ( $post->post_type !== POST_TYPE ) { + return $actions; + } + + unset( $actions['inline hide-if-no-js'] ); + return $actions; +} + +/** + * Removes the quick edit bulk action. + * + * @param array $actions Bulk actions array. + * @return array + */ +function remove_bulk_actions( array $actions ) : array { + unset( $actions['edit'] ); + return $actions; +} + /** * Get the audience configuration data. * From 333d1676322e208e35f538083298cb4841aa8ce3 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 10 Mar 2020 14:09:55 +0000 Subject: [PATCH 15/95] remove estimate header margin --- src/audiences/edit/components/estimate.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index 63aff6b0..ed9cda1a 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -81,6 +81,7 @@ const StyledEstimate = styled(Estimate)` text-transform: lowercase; flex: 0; width: 100%; + margin: 0; } .audience-estimate__totals strong { From 43d219718e3ab7add0f80ea3416f57cfe44cefef Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 10 Mar 2020 14:57:39 +0000 Subject: [PATCH 16/95] estimate styling tweaks --- src/audiences/edit/components/estimate.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index ed9cda1a..13edbcb5 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -84,8 +84,14 @@ const StyledEstimate = styled(Estimate)` margin: 0; } + .audience-estimate__totals p:last-child { + margin-bottom: 0; + } + .audience-estimate__totals strong { - font-size: 150%; + font-size: 135%; + font-weight: normal; + margin-right: 2px; } .audience-estimate__totals { From ca3c23519ad732a8289f1952009e1b2e4b95aafb Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 10 Mar 2020 15:48:50 +0000 Subject: [PATCH 17/95] estimate styling tweaks --- src/audiences/edit/components/estimate.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index 13edbcb5..caf5781e 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -77,8 +77,6 @@ const StyledEstimate = styled(Estimate)` display: ${ props => props.horizontal ? 'flex' : 'block' }; h4 { - font-variant: small-caps; - text-transform: lowercase; flex: 0; width: 100%; margin: 0; From 3ce3c66fcd5586b0d968c20a545067ee6ada40ff Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 10 Mar 2020 16:49:58 +0000 Subject: [PATCH 18/95] rename field registration functions --- README.md | 8 ++++++++ inc/audiences/namespace.php | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5d06c88a..0361d412 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,14 @@ The output will look something like the following: You can further trim the size of the returned response using the `filter_path` query parameter. For example if we're only interested in the stats aggregation we can set `filter_path=-aggregations.sessions` to remove it from the response. +## Audiences + +Audiences allow for the creation of conditions to narrow down event queries or endpoints but also can be used for determining effects on the client side. + +### Mapping Event Data + +To enable the use of event record data in the audience editor it needs to be mapped to a human readable label using the `Altis\Analytics\Audiences\register_` + ## Required Infrastructure A specific infrastructure set up is required to use this plugin: diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index a2db7bbb..90987dc9 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -70,13 +70,13 @@ function register_post_type() { * Set up default event data mappings. */ function register_default_event_data_maps() { - register_event_data_map( 'attributes.referer', __( 'Referrer', 'altis-analytics' ) ); - register_event_data_map( 'endpoint.Demographic.Model', __( 'Browser', 'altis-analytics' ) ); - register_event_data_map( 'endpoint.Demographic.ModelVersion', __( 'Browser version', 'altis-analytics' ) ); - register_event_data_map( 'endpoint.Demographic.Locale', __( 'Browser Locale', 'altis-analytics' ) ); - register_event_data_map( 'endpoint.Demographic.Platform', __( 'Operating system', 'altis-analytics' ) ); - register_event_data_map( 'endpoint.Demographic.PlatformVersion', __( 'Operating system version', 'altis-analytics' ) ); - register_event_data_map( 'endpoint.Location.Country', __( 'Country', 'altis-analytics' ) ); + register_field( 'attributes.referer', __( 'Referrer', 'altis-analytics' ) ); + register_field( 'endpoint.Demographic.Model', __( 'Browser', 'altis-analytics' ) ); + register_field( 'endpoint.Demographic.ModelVersion', __( 'Browser version', 'altis-analytics' ) ); + register_field( 'endpoint.Demographic.Locale', __( 'Browser Locale', 'altis-analytics' ) ); + register_field( 'endpoint.Demographic.Platform', __( 'Operating system', 'altis-analytics' ) ); + register_field( 'endpoint.Demographic.PlatformVersion', __( 'Operating system version', 'altis-analytics' ) ); + register_field( 'endpoint.Location.Country', __( 'Country', 'altis-analytics' ) ); } function meta_boxes() { @@ -211,7 +211,7 @@ function get_audience( int $post_id ) : ?array { * * @return array */ -function get_event_data_maps() : array { +function get_fields() : array { global $altis_analytics_event_data_maps; return array_values( $altis_analytics_event_data_maps ?: [] ); } @@ -222,7 +222,7 @@ function get_event_data_maps() : array { * @param string $field The elasticsearch field name. * @param string $label A human readable label for the field. */ -function register_event_data_map( string $field, string $label ) { +function register_field( string $field, string $label ) { global $altis_analytics_event_data_maps; $altis_analytics_event_data_maps = $altis_analytics_event_data_maps ?: []; $altis_analytics_event_data_maps[ $field ] = [ @@ -428,7 +428,7 @@ function get_unique_enpoint_count() : ?int { * @return ?array */ function get_field_data() : ?array { - $maps = get_event_data_maps(); + $maps = get_fields(); $key = sprintf( 'fields:%s', sha1( serialize( wp_list_pluck( $maps, 'name' ) ) ) ); $cache = wp_cache_get( $key, 'altis-audiences' ); From e1dc8523e6e24039d1198bd903aac3976e326dc5 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 10 Mar 2020 17:02:31 +0000 Subject: [PATCH 19/95] add docs for register_field() function --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0361d412..f79c9cd2 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,9 @@ A user session covers every event recorded between opening the website and closi - `ModelVersion`: Browser version. - `Platform`: The device operating system. - `PlatformVersion`: The operating system version. + - `Location` + - `Country`: The endpoint's country if known / available. + - `City`: The endpoint's city if known or available. - `User` - `UserAttributes` - Any custom attributes associated with the user if known. @@ -230,7 +233,19 @@ Audiences allow for the creation of conditions to narrow down event queries or e ### Mapping Event Data -To enable the use of event record data in the audience editor it needs to be mapped to a human readable label using the `Altis\Analytics\Audiences\register_` +To enable the use of any event record data in the audience editor it needs to be mapped to a human readable label using the `Altis\Analytics\Audiences\register_field()` function: + +```php +use function Altis\Analytics\Audiences\register_field; + +add_action( 'init', function () { + register_field( 'endpoint.Location.City', __( 'City' ) ); +} ); +``` + +In the above example the 1st parameter `endpoint.Location.City` represents the field in the event record to query against. Other examples include `attributes.utm_campaign` or `endpoint.User.UserAttibrutes.custom` for example. + +The 2nd parameter is a human readable label for the audience field. ## Required Infrastructure From f078c8fab2ae8a1915b7bc047e3c2dce7e79ebc8 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Wed, 11 Mar 2020 09:53:49 +0000 Subject: [PATCH 20/95] Fix the cache expiry times I had mistakenly used a timestamp, while this was handled by memcached and the memcached dropin the redis cache only expects a TTL in seconds. --- inc/audiences/namespace.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 90987dc9..2fe2d00e 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -347,7 +347,7 @@ function get_estimate( array $audience ) : ?array { 'histogram' => array_values( $histogram ), ]; - wp_cache_set( $key, $estimate, 'altis-audiences', milliseconds() + ( 60 * 60 * 1000 ) ); + wp_cache_set( $key, $estimate, 'altis-audiences', HOUR_IN_SECONDS ); return $estimate; } @@ -391,7 +391,7 @@ function get_unique_enpoint_count() : ?int { $count = intval( $result['aggregations']['count']['value'] ); - wp_cache_set( 'total-uniques', $count, 'altis-audiences', milliseconds() + ( 60 * 60 * 1000 ) ); + wp_cache_set( 'total-uniques', $count, 'altis-audiences', HOUR_IN_SECONDS ); return $count; } @@ -498,7 +498,7 @@ function get_field_data() : ?array { $fields = array_values( $fields ); // Cache the data. - wp_cache_set( $key, $fields, 'altis-audiences', time() + HOUR_IN_SECONDS ); + wp_cache_set( $key, $fields, 'altis-audiences', HOUR_IN_SECONDS ); return $fields; } From df3548a788a51602f1d49015e0b2b5fa2152f57a Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Thu, 12 Mar 2020 16:56:08 +0000 Subject: [PATCH 21/95] check histogram property is set This prevents a rendering error if the API response is an error object Co-Authored-By: Ryan McCue --- src/audiences/edit/components/estimate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index caf5781e..d2b4f823 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -60,7 +60,7 @@ const Estimate = props => { ) }
- { sparkline && estimate.histogram.length > 0 && ( + { sparkline && estimate.histogram && estimate.histogram.length > 0 && ( item.count ) } From c279a6b14d5d8c0cdb4688a196b1d10bec6dc5ad Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Mon, 16 Mar 2020 14:06:38 +0000 Subject: [PATCH 22/95] Apply suggestions from code review Co-Authored-By: Ryan McCue --- README.md | 2 + inc/audiences/rest_api/namespace.php | 17 +++++++-- plugin.php | 8 ++-- src/audiences/edit/components/estimate.js | 3 +- src/audiences/edit/components/options.js | 5 ++- src/audiences/edit/index.js | 46 +++++++++++------------ src/audiences/index.js | 26 ++++++++----- 7 files changed, 63 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index f79c9cd2..c4decb1c 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,8 @@ You can further trim the size of the returned response using the `filter_path` q ## Audiences +Audiences are user-defined categories of users, based on conditions related to their analytics data. + Audiences allow for the creation of conditions to narrow down event queries or endpoints but also can be used for determining effects on the client side. ### Mapping Event Data diff --git a/inc/audiences/rest_api/namespace.php b/inc/audiences/rest_api/namespace.php index f07b88c5..ecd041d1 100644 --- a/inc/audiences/rest_api/namespace.php +++ b/inc/audiences/rest_api/namespace.php @@ -15,6 +15,9 @@ use WP_REST_Response; use WP_REST_Server; +/** + * Hooks up the audience REST API endpoints. + */ function setup() { add_action( 'rest_api_init', __NAMESPACE__ . '\\init' ); } @@ -90,8 +93,11 @@ function init() { 'validate_callback' => function ( $param ) { $audience = json_decode( urldecode( $param ), true ); - if ( json_last_error() ) { - return new WP_Error( 'altis_audience_estimate_json_invalid', json_last_error_msg() ); + if ( json_last_error() !== JSON_ERROR_NONE ) { + return new WP_Error( + 'altis_audience_estimate_json_invalid', + 'Could not decode JSON: ' . json_last_error_msg() + ); } // Validate against the audience schema after decoding. @@ -100,8 +106,11 @@ function init() { 'sanitize_callback' => function ( $param ) { $audience = json_decode( urldecode( $param ), true ); - if ( json_last_error() ) { - return new WP_Error( 'altis_audience_estimate_json_invalid', json_last_error_msg() ); + if ( json_last_error() !== JSON_ERROR_NONE ) { + return new WP_Error( + 'altis_audience_estimate_json_invalid', + 'Could not decode JSON: ' . json_last_error_msg() + ); } return $audience; diff --git a/plugin.php b/plugin.php index ddfb596d..8f2ecd74 100644 --- a/plugin.php +++ b/plugin.php @@ -18,9 +18,9 @@ require_once ROOT_DIR . '/vendor/autoload.php'; } -require_once 'inc/namespace.php'; -require_once 'inc/audiences/namespace.php'; -require_once 'inc/audiences/rest_api/namespace.php'; -require_once 'inc/utils/namespace.php'; +require_once __DIR__ . '/inc/namespace.php'; +require_once __DIR__ . '/inc/audiences/namespace.php'; +require_once __DIR__ . '/inc/audiences/rest_api/namespace.php'; +require_once __DIR__ . '/inc/utils/namespace.php'; add_action( 'plugins_loaded', __NAMESPACE__ . '\\setup' ); diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index d2b4f823..f5d3a353 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -1,7 +1,8 @@ import React, { useState, useEffect } from 'react'; +import { Sparklines, SparklinesLine } from 'react-sparklines'; import styled from 'styled-components'; + import { getEstimate } from '../data'; -import { Sparklines, SparklinesLine } from 'react-sparklines'; const { __ } = wp.i18n; diff --git a/src/audiences/edit/components/options.js b/src/audiences/edit/components/options.js index 8b28cb0e..d1a4d206 100644 --- a/src/audiences/edit/components/options.js +++ b/src/audiences/edit/components/options.js @@ -1,5 +1,6 @@ import React from 'react'; import styled from 'styled-components'; + import Estimate from './estimate'; const { Button } = wp.components; @@ -20,8 +21,8 @@ const Options = props => { ); -} +}; export default Options; diff --git a/src/audiences/edit/components/rule.js b/src/audiences/edit/components/rule.js index 96786542..f3285cfe 100644 --- a/src/audiences/edit/components/rule.js +++ b/src/audiences/edit/components/rule.js @@ -1,7 +1,7 @@ import styled from 'styled-components'; const Rule = styled.div.attrs( () => ( { - className: "audience-editor__rule", + className: 'audience-editor__rule', } ) )` margin: 0 0 15px; display: flex; diff --git a/src/audiences/edit/components/select-include.js b/src/audiences/edit/components/select-include.js index 47f9c15d..acc9499f 100644 --- a/src/audiences/edit/components/select-include.js +++ b/src/audiences/edit/components/select-include.js @@ -25,7 +25,7 @@ const SelectInclude = ( { value, onChange, label = '', name } ) => { - ) -} + ); +}; export default SelectInclude; diff --git a/src/audiences/edit/data/defaults.js b/src/audiences/edit/data/defaults.js index f05bc6b2..a5c5b888 100644 --- a/src/audiences/edit/data/defaults.js +++ b/src/audiences/edit/data/defaults.js @@ -8,7 +8,7 @@ export const defaultRule = { export const defaultGroup = { include: 'any', // ANY, ALL, NONE rules: [ - defaultRule + defaultRule, ], }; diff --git a/src/audiences/edit/data/index.js b/src/audiences/edit/data/index.js index 4f0a1a0a..c2cecba2 100644 --- a/src/audiences/edit/data/index.js +++ b/src/audiences/edit/data/index.js @@ -1,6 +1,6 @@ const { apiFetch } = wp; -export const getEstimate = async ( audience ) => { +export const getEstimate = async audience => { try { const audienceQuery = encodeURIComponent( JSON.stringify( audience ) ); // Get audience estimate data. @@ -34,4 +34,4 @@ export const getAudience = async id => { } catch ( error ) { return { error }; } -} +}; diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index 2e04f7f4..f6236b20 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -36,21 +36,24 @@ const Edit = props => { } ( async () => { const fieldsResponse = await getFields(); - if ( ! fieldsResponse instanceof Array && fieldsResponse.error ) { - setError( fieldsResponse.error ); - } else { + if ( fieldsResponse instanceof Array && ! fieldsResponse.error ) { if ( error ) { setError( null ); } setFields( fieldsResponse ); + } else { + setError( fieldsResponse.error ); } } )(); }, [ fields, error ] ); // Audience update helpers. - const updateAudience = ( newAudience ) => { + const updateAudience = newAudience => { setAudience( audienceToUpdate => { - return { ...audienceToUpdate, ...newAudience }; + return { + ...audienceToUpdate, + ...newAudience, + }; } ); }; @@ -88,137 +91,136 @@ const Edit = props => { { audience.groups.map( ( group, groupId ) => ( - -
-

{ __( 'Group', 'altis-analytics' ) } { groupId + 1 }

- updateGroup( groupId, { include: e.target.value } ) } - /> -
- { group.rules.map( ( rule, ruleId ) => { - const currentField = fields.filter( fieldData => fieldData.name === rule.field )[0] ?? {}; - - return ( - - - - { ! currentField.type || currentField.type === 'string' && ( + +
+

{ __( 'Group', 'altis-analytics' ) } { groupId + 1 }

+ updateGroup( groupId, { include: e.target.value } ) } + /> +
+ { group.rules.map( ( rule, ruleId ) => { + const currentField = fields.filter( fieldData => fieldData.name === rule.field )[0] ?? {}; + + return ( + + + updateRule( groupId, ruleId, { value: e.target.value } ) } - > - - { fields.filter( field => field.name === rule.field ).map( field => field.data && field.data.map( ( datum, index ) => ( - - ) ) ) } - - ) } - { [ '*=', '!*', '^=' ].indexOf( rule.operator ) >= 0 && ( - updateRule( groupId, ruleId, { value: e.target.value } ) } - /> - ) } + + + + + ) } { currentField.type === 'number' && ( - updateRule( groupId, ruleId, { value: e.target.value } ) } - /> - ) } - - { group.rules.length > 1 && ( - + + + + + + ) } - - ); - } ) } - + + { ( ! currentField.type || currentField.type === 'string' ) && ( + + { [ '=', '!=' ].indexOf( rule.operator ) >= 0 && ( + + ) } + { [ '*=', '!*', '^=' ].indexOf( rule.operator ) >= 0 && ( + updateRule( groupId, ruleId, { value: e.target.value } ) } + /> + ) } + + ) } + { currentField.type === 'number' && ( + updateRule( groupId, ruleId, { value: e.target.value } ) } + /> + ) } + + { group.rules.length > 1 && ( + + ) } +
+ ); + } ) } + + + + { audience.groups.length > 1 && ( - - { audience.groups.length > 1 && ( - - ) } -
- ); - } ) } + ) } + + ) ) } ); -} +}; export default Edit; diff --git a/src/audiences/index.js b/src/audiences/index.js index b6bc4880..b7af120b 100644 --- a/src/audiences/index.js +++ b/src/audiences/index.js @@ -33,4 +33,4 @@ AudienceEstimates.forEach( element => { />, element ); -} +} ); From e8a5d1588aebc9b6b2e9d3e17fe2733d1a349a82 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Mon, 16 Mar 2020 16:39:44 +0000 Subject: [PATCH 25/95] remove id from select-include component --- src/audiences/edit/components/select-include.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/audiences/edit/components/select-include.js b/src/audiences/edit/components/select-include.js index acc9499f..51dde7b2 100644 --- a/src/audiences/edit/components/select-include.js +++ b/src/audiences/edit/components/select-include.js @@ -14,10 +14,8 @@ const StyledSelect = styled.select` `; const SelectInclude = ( { value, onChange, label = '', name } ) => { - const id = name.replace( /\W+/g, '-' ).replace( /^\W+.*?\W+$/, '' ); return ( Date: Tue, 17 Mar 2020 12:15:26 +0000 Subject: [PATCH 26/95] fix beforeunload leave site prompt --- inc/audiences/namespace.php | 41 ++++-- src/audiences/edit/components/estimate.js | 9 -- src/audiences/edit/components/options.js | 171 +++++++++++++++++++--- src/audiences/edit/data/index.js | 42 ++---- src/audiences/edit/index.js | 14 +- src/audiences/index.js | 1 + 6 files changed, 214 insertions(+), 64 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index d17779ab..f654eddd 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -83,9 +83,24 @@ function meta_boxes() { remove_meta_box( 'slugdiv', POST_TYPE, 'normal' ); // Add our replaced submitdiv meta box. - add_meta_box( 'audience-options', __( 'Options', 'altis-analytics' ), function () { - echo '
'; - }, POST_TYPE, 'side', 'high' ); + add_meta_box( + 'audience-options', + __( 'Audience Options', 'altis-analytics' ), + __NAMESPACE__ . '\\audience_options_ui', + POST_TYPE, + 'side', + 'high' + ); +} + +function audience_options_ui( WP_Post $post ) { + echo sprintf( + '
' . + '' . + '
', + $post->ID, + esc_html__( 'Javascript is required to use the audience editor.', 'altis-analytics' ) + ); } /** @@ -99,12 +114,15 @@ function audience_ui( WP_Post $post ) { } echo sprintf( - '
' . - '

%s

' . + '
' . + '

%s

' . + '' . '
', + $post->ID, esc_attr( wp_json_encode( get_audience( $post->ID ) ) ), esc_attr( wp_json_encode( get_field_data() ) ), - esc_html__( 'Loading...', 'altis-analytics' ) + esc_html__( 'Loading...', 'altis-analytics' ), + esc_html__( 'Javascript is required to use the audience editor.', 'altis-analytics' ) ); } @@ -118,12 +136,17 @@ function estimate_ui( WP_Post $post ) { return; } + $audience = get_audience( $post->ID ); + echo sprintf( '
' . - '

%s

' . + '

%s

' . + '' . '
', - esc_attr( wp_json_encode( get_audience( $post->ID ) ) ), - esc_html__( 'Loading...', 'altis-analytics' ) + esc_attr( wp_json_encode( $audience ) ), + esc_html__( 'Loading...', 'altis-analytics' ), + // translators: %d is the number of visitors matching the audience + sprintf( esc_html__( '%d visitors in the last 7 days', 'altis-analytics' ), $audience['count'] ?? 0 ) ); } diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index 7a9a2565..faf8cdd5 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -9,7 +9,6 @@ const { __ } = wp.i18n; const Estimate = props => { const { audience, - title, sparkline = true, } = props; @@ -39,8 +38,6 @@ const Estimate = props => { return (
- { title &&

{ title }

} - { estimate.error && (
{ estimate.error.message }
) } @@ -77,12 +74,6 @@ const Estimate = props => { const StyledEstimate = styled( Estimate )` display: ${ props => props.horizontal ? 'flex' : 'block' }; - h4 { - flex: 0; - width: 100%; - margin: 0; - } - .audience-estimate__totals p:last-child { margin-bottom: 0; } diff --git a/src/audiences/edit/components/options.js b/src/audiences/edit/components/options.js index 36cc851e..44b19a99 100644 --- a/src/audiences/edit/components/options.js +++ b/src/audiences/edit/components/options.js @@ -1,9 +1,11 @@ -import React from 'react'; +import React, { Component } from 'react'; import styled from 'styled-components'; import Estimate from './estimate'; -const { Button } = wp.components; +import { getAudience } from '../data'; + +const { Button, ToggleControl } = wp.components; const { __ } = wp.i18n; const StyledOptions = styled.div.attrs( () => ( { @@ -14,21 +16,156 @@ const StyledOptions = styled.div.attrs( () => ( { } `; -const Options = props => { - const { audience } = props; - - return ( - - - - - ); +class Options extends Component { + constructor( props ) { + super( props ); + + this.state = { + post: null, // The current post object if known. + title: '', // The current post title. + error: null, // Any error thrown / set manually. + active: false, // If true audience is 'published', else 'draft'. + checks: {}, // Any pre submit checks are stored here. + }; + + // Bind component to DOM event listeners. + this.onChangeTitle = this.onChangeTitle.bind( this ); + this.onSubmit = this.onSubmit.bind( this ); + } + + async componentDidMount() { + // Get the form element if there is one. + this.formElement = document.getElementById( 'post' ); + if ( this.formElement ) { + this.formElement.addEventListener( 'submit', this.onSubmit ); + } + + // Get the title element for the edit screen if there is one. + this.titleField = document.getElementById( 'title' ); + if ( this.titleField ) { + this.titleField.addEventListener( 'keyup', this.onChangeTitle ); + } + + // Fetch the post. + if ( this.props.postId ) { + try { + const post = await getAudience( this.props.postId ); + this.setState( { + active: post.status === 'publish', + post, + title: post.title.raw, + canSave: post.title.raw.length > 0 + } ); + } catch ( error ) { + this.setState( { error } ); + } + } else { + this.setState( { + error: { + message: __( 'Unable to determine current audience ID', 'altis-analytics' ), + }, + } ); + } + } + + componentWillUnmount() { + if ( this.titleField ) { + this.titleField.removeEventListener( this.onChangeTitle ); + this.titleField = null; + } + if ( this.formElement ) { + this.formElement.removeEventListener( this.onSubmit ); + this.formElement = null; + } + } + + onSubmit( event ) { + const checks = this.doPreSaveChecks(); + if ( Object.values( checks ).length > 0 ) { + event.preventDefault(); + } else { + // Prevent the "Leave site?" warning popup showing. + window.onbeforeunload = null; + window.jQuery && window.jQuery( window ).off( 'beforeunload' ); + } + this.setState( { checks } ); + } + + onChangeTitle( event ) { + this.setState( { title: event.target.value } ); + } + + doPreSaveChecks() { + let checks = this.state.checks; + + // Check for empty title. + if ( this.state.title.length === 0 ) { + checks['title_empty'] = __( 'The title cannot be empty', 'altis-analytics' ); + } else { + delete checks['title_empty']; + } + + // Update checks if any have been added or removed. + return checks; + } + + static getDerivedStateFromError( error ) { + return { error }; + } + + componentDidCatch( error, errorInfo ) { + console.error( error, errorInfo ); + } + + render() { + const { + audience, + } = this.props; + const { + post, + error, + active, + checks, + } = this.state; + + if ( error ) { + return ( +
+

{ error.message || __( 'There was an error loading the audience data.', 'altis-analytics' ) }

+
+ ); + } + + return ( + + + this.setState( state => ( { active: ! state.active } ) ) } + /> + + { Object.values( checks ).length > 0 && ( +
+ { Object.values( checks ).map( check =>

{ check }

) } +
+ ) } + +
+ ); + } }; export default Options; diff --git a/src/audiences/edit/data/index.js b/src/audiences/edit/data/index.js index c2cecba2..b6250f2a 100644 --- a/src/audiences/edit/data/index.js +++ b/src/audiences/edit/data/index.js @@ -1,37 +1,27 @@ const { apiFetch } = wp; export const getEstimate = async audience => { - try { - const audienceQuery = encodeURIComponent( JSON.stringify( audience ) ); - // Get audience estimate data. - const data = await apiFetch( { - path: `analytics/v1/audiences/estimate?audience=${ audienceQuery }`, - } ); + const audienceQuery = encodeURIComponent( JSON.stringify( audience ) ); + // Get audience estimate data. + const data = await apiFetch( { + path: `analytics/v1/audiences/estimate?audience=${ audienceQuery }`, + } ); - return data; - } catch ( error ) { - return { error }; - } + return data; }; export const getFields = async () => { - try { - const data = await apiFetch( { - path: 'analytics/v1/audiences/fields', - } ); - return data; - } catch ( error ) { - return { error }; - } + const data = await apiFetch( { + path: 'analytics/v1/audiences/fields', + } ); + + return data; }; export const getAudience = async id => { - try { - const data = await apiFetch( { - path: `wp/v2/audiences/${ id }`, - } ); - return data; - } catch ( error ) { - return { error }; - } + const data = await apiFetch( { + path: `wp/v2/audiences/${ id }?context=edit`, + } ); + + return data; }; diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index f6236b20..73649232 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -79,7 +79,15 @@ const Edit = props => {
) } - { AudienceOptionsUI && ReactDOM.createPortal( , AudienceOptionsUI ) } + { /* A portal is used here to support the legacy post edit screen publish meta boxes and to + pass the actively edited audience through to the Options */ } + { AudienceOptionsUI && ReactDOM.createPortal( + , + AudienceOptionsUI + ) }
{ > { ( ! currentField.type || currentField.type === 'string' ) && ( - - + + diff --git a/src/audiences/index.js b/src/audiences/index.js index b7af120b..0da23395 100644 --- a/src/audiences/index.js +++ b/src/audiences/index.js @@ -13,6 +13,7 @@ if ( AudienceUI ) { // Mount audience react app. ReactDOM.render( , From 26d3d23b866b5d64326cc0ca89725c47fcf168c0 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 17 Mar 2020 19:43:17 +0000 Subject: [PATCH 27/95] Refactor to class components Changes the Edit component to be a controller for fetching data. Introduces the AudienceEditor component and splits that further into Group and Rule components that indepently handle passing updates upstream. --- inc/audiences/namespace.php | 21 +- inc/audiences/rest_api/namespace.php | 4 +- .../edit/components/audience-editor.js | 99 ++++++ src/audiences/edit/components/editor.js | 13 - src/audiences/edit/components/group.js | 89 +++++- src/audiences/edit/components/rule.js | 90 +++++- .../edit/components/select-operator.js | 28 ++ src/audiences/edit/data/constants.js | 16 + src/audiences/edit/index.js | 292 +++++------------- 9 files changed, 405 insertions(+), 247 deletions(-) create mode 100644 src/audiences/edit/components/audience-editor.js delete mode 100644 src/audiences/edit/components/editor.js create mode 100644 src/audiences/edit/components/select-operator.js create mode 100644 src/audiences/edit/data/constants.js diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index f654eddd..262519ec 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -164,19 +164,34 @@ function save_post( $post_id ) { return; } + if ( ! is_array( $_POST['audiencce'] ) ) { + return; + } + + save_audience( $post_id, $_POST['audience'] ); +} + +/** + * Saves an audience config to the given post ID. + * + * @param integer $post_id The post ID to save the audience to. + * @param array $audience The audience configuration. + * @return bool True on successful update or false on failure. + */ +function save_audience( int $post_id, array $audience ) : bool { // Clear errors. delete_post_meta( $post_id, 'audience_error' ); // Validate using audience schema. - $valid = rest_validate_value_from_schema( $_POST['audience'], get_audience_schema(), 'audience' ); + $valid = rest_validate_value_from_schema( $audience, get_audience_schema(), 'audience' ); if ( is_wp_error( $valid ) ) { update_post_meta( $post_id, 'audience_error', $valid->get_error_message() ); - return; + return false; } // Save the audience configuration. - update_post_meta( $post_id, 'audience', wp_slash( $_POST['audience'] ) ); + return (bool) update_post_meta( $post_id, 'audience', wp_slash( $audience ) ); } /** diff --git a/inc/audiences/rest_api/namespace.php b/inc/audiences/rest_api/namespace.php index 7d13c521..3b8c31d5 100644 --- a/inc/audiences/rest_api/namespace.php +++ b/inc/audiences/rest_api/namespace.php @@ -11,6 +11,8 @@ use function Altis\Analytics\Audiences\get_audience; use function Altis\Analytics\Audiences\get_estimate; +use function Altis\Analytics\Audiences\save_audience; + use WP_Error; use WP_Post; use WP_REST_Request; @@ -118,7 +120,7 @@ function init() { return get_audience( $post['id'] ); }, 'update_callback' => function ( $value, WP_Post $post ) { - return update_post_meta( $post->ID, 'audience', wp_slash( $value ) ); + return save_audience( $post->ID, (array) $value ); }, 'schema' => get_audience_schema(), ] ); diff --git a/src/audiences/edit/components/audience-editor.js b/src/audiences/edit/components/audience-editor.js new file mode 100644 index 00000000..3b80cc7c --- /dev/null +++ b/src/audiences/edit/components/audience-editor.js @@ -0,0 +1,99 @@ +import React, { Component } from 'react'; +import styled from 'styled-components'; + +import Group from './group'; +import SelectInclude from './select-include'; +import { + defaultAudience, + defaultGroup, +} from '../data/defaults'; + +const { __ } = wp.i18n; +const { Button } = wp.components; + +class AudienceEditor extends Component { + + updateAudience( audience ) { + const updatedAudience = Object.assign( {}, this.props.audience, audience ); + this.props.onChange( updatedAudience ); + } + + updateGroup( groupId, group = {} ) { + const groups = this.props.audience.groups.slice(); + const newGroup = Object.assign( {}, groups[ groupId ], group ); + groups.splice( groupId, 1, newGroup ); + this.updateAudience( { groups } ); + }; + + render() { + const { + audience, + fields, + className, + } = this.props; + + return ( +
+
+ this.updateAudience( { include: e.target.value } ) } + /> +
+ + { audience.groups.map( ( group, groupId ) => ( + this.updateGroup( groupId, value ) } + namePrefix={ `audience[groups][${ groupId }]` } + fields={ fields } + { ...group } + > + { audience.groups.length > 1 && ( + + ) } + + ) ) } + + +
+ ); + } +} + +AudienceEditor.defaultProps = { + audience: defaultAudience, + fields: [], + onChange: () => {}, +}; + +const StyledAudienceEditor = styled(AudienceEditor)` + margin: 0 0 40px; + + .audience-editor__include { + margin: 20px 0; + } +`; + +export default StyledAudienceEditor; diff --git a/src/audiences/edit/components/editor.js b/src/audiences/edit/components/editor.js deleted file mode 100644 index ea0becc1..00000000 --- a/src/audiences/edit/components/editor.js +++ /dev/null @@ -1,13 +0,0 @@ -import styled from 'styled-components'; - -const Editor = styled.div.attrs( () => ( { - className: 'audience-editor', -} ) )` - margin: 0 0 40px; - - .audience-editor__include { - margin: 20px 0; - } -`; - -export default Editor; diff --git a/src/audiences/edit/components/group.js b/src/audiences/edit/components/group.js index 0d1bae58..6ebfdc0b 100644 --- a/src/audiences/edit/components/group.js +++ b/src/audiences/edit/components/group.js @@ -1,8 +1,87 @@ +import React from 'react'; import styled from 'styled-components'; -const Group = styled.div.attrs( () => ( { - className: 'audience-editor__group', -} ) )` +import Rule from './rule'; +import SelectInclude from './select-include'; + +import { defaultRule } from '../data/defaults'; + +const { __ } = wp.i18n; +const { Button } = wp.components; + +const Group = props => { + const { + title, + className, + onChange, + namePrefix, + include, + rules, + fields, + children, + } = props; + + const updateRule = ( ruleId, rule ) => { + const newRules = rules.slice(); + const newRule = Object.assign( {}, rules[ ruleId ], rule ); + newRules.splice( ruleId, 1, newRule ); + onChange( { rules: newRules } ); + }; + + return ( +
+
+ { title &&

{ title }

} + onChange( { include: e.target.value } ) } + /> +
+ + { rules.map( ( rule, ruleId ) => ( + updateRule( ruleId, value ) } + fields={ fields } + namePrefix={ `${ namePrefix }[rules][${ ruleId }]` } + { ...rule } + > + { rules.length > 1 && ( + + ) } + + ) ) } + +
+ + + { children } +
+
+ ); +}; + + +const StyledGroup = styled(Group)` background: rgba(0, 0, 0, 0.02); border-radius: 3px; border: 1px solid rgba(0, 0, 0, .1); @@ -21,9 +100,9 @@ const Group = styled.div.attrs( () => ( { font-variant: small-caps; } - > .components-button { + .audience-editor__group-footer .components-button { margin-right: 10px; } `; -export default Group; +export default StyledGroup; diff --git a/src/audiences/edit/components/rule.js b/src/audiences/edit/components/rule.js index f3285cfe..743b96f0 100644 --- a/src/audiences/edit/components/rule.js +++ b/src/audiences/edit/components/rule.js @@ -1,8 +1,90 @@ +import React, { Fragment } from 'react'; import styled from 'styled-components'; -const Rule = styled.div.attrs( () => ( { - className: 'audience-editor__rule', -} ) )` +import SelectOperator from './select-operator'; + +const { __ } = wp.i18n; + +const Rule = props => { + const { + className, + namePrefix, + fields, + field, + operator, + onChange, + value, + children = null, + } = props; + + const currentField = fields.filter( fieldData => fieldData.name === field )[0] || {}; + + return ( +
+ + + onChange( { operator: e.target.value } ) } + fieldType={ currentField.type || 'string' } + /> + + { ( ! currentField.type || currentField.type === 'string' ) && ( + + { [ '=', '!=' ].indexOf( operator ) >= 0 && ( + + ) } + { [ '*=', '!*', '^=' ].indexOf( operator ) >= 0 && ( + onChange( { value: e.target.value } ) } + /> + ) } + + ) } + + { currentField.type === 'number' && ( + onChange( { value: e.target.value } ) } + /> + ) } + + { children } +
+ ); +}; + +const StyledRule = styled(Rule)` margin: 0 0 15px; display: flex; @@ -20,4 +102,4 @@ const Rule = styled.div.attrs( () => ( { } `; -export default Rule; +export default StyledRule; diff --git a/src/audiences/edit/components/select-operator.js b/src/audiences/edit/components/select-operator.js new file mode 100644 index 00000000..41177025 --- /dev/null +++ b/src/audiences/edit/components/select-operator.js @@ -0,0 +1,28 @@ +import React from 'react'; + +import { + NUMERIC_OPERATIONS, + STRING_OPERATIONS, +} from '../data/constants'; + +const SelectOperator = props => { + const { + fieldType = 'string' + } = props; + + let options = STRING_OPERATIONS; + + if ( fieldType === 'number' ) { + options = NUMERIC_OPERATIONS; + } + + return ( + + ); +}; + +export default SelectOperator; diff --git a/src/audiences/edit/data/constants.js b/src/audiences/edit/data/constants.js new file mode 100644 index 00000000..68a138bc --- /dev/null +++ b/src/audiences/edit/data/constants.js @@ -0,0 +1,16 @@ +const { __ } = wp.i18n; + +export const STRING_OPERATIONS = { + '=': __( 'is', 'altis-analytics' ), + '!=': __( 'is not', 'altis-analytics' ), + '*=': __( 'contains', 'altis-analytics' ), + '!*': __( 'does not contain', 'altis-analytics' ), + '^=': __( 'begins with', 'altis-analytics' ), +}; + +export const NUMERIC_OPERATIONS = { + 'gt': __( 'is greater than', 'altis-analytics' ), + 'gte': __( 'is greater than or equal to', 'altis-analytics' ), + 'lt': __( 'is less than', 'altis-analytics' ), + 'lte': __( 'is less than or equal to', 'altis-analytics' ), +}; diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index 73649232..9e94c0bb 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -1,245 +1,95 @@ -import React, { useState, useEffect, Fragment } from 'react'; +import React, { Component } from 'react'; import ReactDOM from 'react-dom'; -import Editor from './components/editor'; -import Group from './components/group'; +import AudienceEditor from './components/audience-editor'; import Options from './components/options'; -import Rule from './components/rule'; -import SelectInclude from './components/select-include'; import { defaultAudience, - defaultGroup, - defaultRule, } from './data/defaults'; import { + getAudience, getFields, } from './data'; -const { __ } = wp.i18n; -const { Button } = wp.components; - // Check for standard post edit page options meta box. const AudienceOptionsUI = document.getElementById( 'altis-analytics-audience-options' ); -const Edit = props => { - // Set the audience from props or use default. - const [ audience, setAudience ] = useState( props.audience || defaultAudience ); - // Set the dropdown fields from props or fetch. - const [ fields, setFields ] = useState( props.fields || [] ); - // Show errors. - const [ error, setError ] = useState( null ); - - // Fetch fields data. - useEffect( () => { - if ( fields.length ) { - return; +class Edit extends Component { + constructor( props ) { + super( props ); + + this.state = { + post: null, + audience: defaultAudience, + fields: [], + loading: true, + }; + } + + componentDidMount() { + // Fetch fields. + this.setFields(); + // Fetch the post. + this.setAudience(); + } + + async setFields() { + try { + const fields = await getFields(); + this.setState( { fields } ); + } catch ( error ) { + this.setState( { + error, + loading: false, + } ); } - ( async () => { - const fieldsResponse = await getFields(); - if ( fieldsResponse instanceof Array && ! fieldsResponse.error ) { - if ( error ) { - setError( null ); - } - setFields( fieldsResponse ); - } else { - setError( fieldsResponse.error ); - } - } )(); - }, [ fields, error ] ); - - // Audience update helpers. - const updateAudience = newAudience => { - setAudience( audienceToUpdate => { - return { - ...audienceToUpdate, - ...newAudience, - }; - } ); - }; - - const updateGroup = ( groupId, group ) => { - const groups = audience.groups.slice(); - const newGroup = Object.assign( {}, groups[ groupId ], group ); - groups.splice( groupId, 1, newGroup ); - updateAudience( { groups } ); - }; + } + + async setAudience() { + try { + const post = await getAudience( this.props.postId ); + this.setState( { + post, + audience: post.audience, + loading: false, + } ); + } catch ( error ) { + this.setState( { + error, + loading: false, + } ); + } + } - const updateRule = ( groupId, ruleId, rule ) => { - const rules = audience.groups[ groupId ].rules.slice(); - const newRule = Object.assign( {}, rules[ ruleId ], rule ); - rules.splice( ruleId, 1, newRule ); - updateGroup( groupId, { rules } ); - }; + render() { + const { audience, fields, error } = this.state; - return ( - - { error && ( -
-

{ error.message }

-
- ) } + return ( +
+ { error && ( +
+

{ error.message }

+
+ ) } - { /* A portal is used here to support the legacy post edit screen publish meta boxes and to + { /* A portal is used here to support the legacy post edit screen publish meta boxes and to pass the actively edited audience through to the Options */ } - { AudienceOptionsUI && ReactDOM.createPortal( - , + AudienceOptionsUI + ) } + + , - AudienceOptionsUI - ) } - -
- updateAudience( { include: e.target.value } ) } + fields={ fields } + onChange={ value => this.setState( { audience: value } ) } />
- - { audience.groups.map( ( group, groupId ) => ( - -
-

{ __( 'Group', 'altis-analytics' ) } { groupId + 1 }

- updateGroup( groupId, { include: e.target.value } ) } - /> -
- { group.rules.map( ( rule, ruleId ) => { - const currentField = fields.filter( fieldData => fieldData.name === rule.field )[0] ?? {}; - - return ( - - - - { ( ! currentField.type || currentField.type === 'string' ) && ( - - { [ '=', '!=' ].indexOf( rule.operator ) >= 0 && ( - - ) } - { [ '*=', '!*', '^=' ].indexOf( rule.operator ) >= 0 && ( - updateRule( groupId, ruleId, { value: e.target.value } ) } - /> - ) } - - ) } - { currentField.type === 'number' && ( - updateRule( groupId, ruleId, { value: e.target.value } ) } - /> - ) } - - { group.rules.length > 1 && ( - - ) } - - ); - } ) } - - - - { audience.groups.length > 1 && ( - - ) } -
- ) ) } - - - - ); + ); + } }; export default Edit; From 9461dc94a9b177e45f8012599b5b4216a5fc89b2 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 17 Mar 2020 19:46:09 +0000 Subject: [PATCH 28/95] code style fixes --- src/audiences/edit/components/audience-editor.js | 4 ++-- src/audiences/edit/components/group.js | 3 +-- src/audiences/edit/components/options.js | 4 ++-- src/audiences/edit/components/rule.js | 2 +- src/audiences/edit/components/select-operator.js | 2 +- src/audiences/edit/index.js | 8 ++++++-- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/audiences/edit/components/audience-editor.js b/src/audiences/edit/components/audience-editor.js index 3b80cc7c..482aac6c 100644 --- a/src/audiences/edit/components/audience-editor.js +++ b/src/audiences/edit/components/audience-editor.js @@ -23,7 +23,7 @@ class AudienceEditor extends Component { const newGroup = Object.assign( {}, groups[ groupId ], group ); groups.splice( groupId, 1, newGroup ); this.updateAudience( { groups } ); - }; + } render() { const { @@ -88,7 +88,7 @@ AudienceEditor.defaultProps = { onChange: () => {}, }; -const StyledAudienceEditor = styled(AudienceEditor)` +const StyledAudienceEditor = styled( AudienceEditor )` margin: 0 0 40px; .audience-editor__include { diff --git a/src/audiences/edit/components/group.js b/src/audiences/edit/components/group.js index 6ebfdc0b..e646beb9 100644 --- a/src/audiences/edit/components/group.js +++ b/src/audiences/edit/components/group.js @@ -80,8 +80,7 @@ const Group = props => { ); }; - -const StyledGroup = styled(Group)` +const StyledGroup = styled( Group )` background: rgba(0, 0, 0, 0.02); border-radius: 3px; border: 1px solid rgba(0, 0, 0, .1); diff --git a/src/audiences/edit/components/options.js b/src/audiences/edit/components/options.js index 44b19a99..66898a42 100644 --- a/src/audiences/edit/components/options.js +++ b/src/audiences/edit/components/options.js @@ -54,7 +54,7 @@ class Options extends Component { active: post.status === 'publish', post, title: post.title.raw, - canSave: post.title.raw.length > 0 + canSave: post.title.raw.length > 0, } ); } catch ( error ) { this.setState( { error } ); @@ -166,6 +166,6 @@ class Options extends Component { ); } -}; +} export default Options; diff --git a/src/audiences/edit/components/rule.js b/src/audiences/edit/components/rule.js index 743b96f0..b675ed6a 100644 --- a/src/audiences/edit/components/rule.js +++ b/src/audiences/edit/components/rule.js @@ -84,7 +84,7 @@ const Rule = props => { ); }; -const StyledRule = styled(Rule)` +const StyledRule = styled( Rule )` margin: 0 0 15px; display: flex; diff --git a/src/audiences/edit/components/select-operator.js b/src/audiences/edit/components/select-operator.js index 41177025..7cfd6598 100644 --- a/src/audiences/edit/components/select-operator.js +++ b/src/audiences/edit/components/select-operator.js @@ -7,7 +7,7 @@ import { const SelectOperator = props => { const { - fieldType = 'string' + fieldType = 'string', } = props; let options = STRING_OPERATIONS; diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index 9e94c0bb..1e1a0fb9 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -62,7 +62,11 @@ class Edit extends Component { } render() { - const { audience, fields, error } = this.state; + const { + audience, + fields, + error, + } = this.state; return (
@@ -90,6 +94,6 @@ class Edit extends Component {
); } -}; +} export default Edit; From 15c97bfa38b0a43165eec86ca7160a8bb2fcf433 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 17 Mar 2020 19:55:40 +0000 Subject: [PATCH 29/95] moved the group and rule remove buttons into the components introduces `canRemove` and `onRemove` props to handle the display and callback respectively --- .../edit/components/audience-editor.js | 23 ++++-------- src/audiences/edit/components/group.js | 37 ++++++++++--------- src/audiences/edit/components/rule.js | 14 ++++++- 3 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/audiences/edit/components/audience-editor.js b/src/audiences/edit/components/audience-editor.js index 482aac6c..79390d65 100644 --- a/src/audiences/edit/components/audience-editor.js +++ b/src/audiences/edit/components/audience-editor.js @@ -50,23 +50,14 @@ class AudienceEditor extends Component { onChange={ value => this.updateGroup( groupId, value ) } namePrefix={ `audience[groups][${ groupId }]` } fields={ fields } + canRemove={ audience.groups.length > 1 } + onRemove={ () => { + const newGroups = audience.groups.slice(); + newGroups.splice( groupId, 1 ); + this.updateAudience( { groups: newGroups } ); + } } { ...group } - > - { audience.groups.length > 1 && ( - - ) } - + /> ) ) } - ) } - + /> ) ) }
@@ -74,7 +66,16 @@ const Group = props => { { __( 'Add a rule', 'altis-analytics' ) } - { children } + { canRemove && ( + + ) }
); diff --git a/src/audiences/edit/components/rule.js b/src/audiences/edit/components/rule.js index b675ed6a..a80d0b48 100644 --- a/src/audiences/edit/components/rule.js +++ b/src/audiences/edit/components/rule.js @@ -14,7 +14,8 @@ const Rule = props => { operator, onChange, value, - children = null, + canRemove, + onRemove, } = props; const currentField = fields.filter( fieldData => fieldData.name === field )[0] || {}; @@ -79,7 +80,16 @@ const Rule = props => { /> ) } - { children } + { canRemove && ( + + ) }
); }; From b24c776022fad31cc0aa8bbce442f798dd721150 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Wed, 18 Mar 2020 19:37:21 +0000 Subject: [PATCH 30/95] refactoring to use wp.data.registerStore() Still to do, Estimate component to switch over to `useSelect()`. This is in preparation for creating an audience selector and editor modal component. --- inc/audiences/namespace.php | 42 ++- inc/audiences/rest_api/namespace.php | 4 + .../edit/components/audience-editor.js | 29 +- src/audiences/edit/components/estimate.js | 57 ++-- src/audiences/edit/components/group.js | 55 ++-- src/audiences/edit/components/options.js | 171 ------------ src/audiences/edit/components/rule.js | 52 ++-- .../edit/components/select-include.js | 16 +- src/audiences/edit/data/defaults.js | 6 + src/audiences/edit/data/index.js | 187 ++++++++++++- src/audiences/edit/index.js | 260 +++++++++++++----- 11 files changed, 503 insertions(+), 376 deletions(-) delete mode 100644 src/audiences/edit/components/options.js diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 262519ec..cb6abd70 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -8,11 +8,12 @@ namespace Altis\Analytics\Audiences; use function Altis\Analytics\Audiences\REST_API\get_audience_schema; -use function Altis\Analytics\Utils\field_type_is; use function Altis\Analytics\Utils\get_field_type; use function Altis\Analytics\Utils\milliseconds; use function Altis\Analytics\Utils\query; use WP_Post; +use WP_REST_Request; +use WP_REST_Server; const POST_TYPE = 'audience'; @@ -20,7 +21,7 @@ function setup() { add_action( 'init', __NAMESPACE__ . '\\register_post_type' ); add_action( 'init', __NAMESPACE__ . '\\register_default_event_data_maps' ); add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\\admin_enqueue_scripts' ); - add_action( 'edit_form_after_title', __NAMESPACE__ . '\\audience_ui' ); + add_action( 'edit_form_top', __NAMESPACE__ . '\\audience_ui' ); add_action( 'add_meta_boxes_' . POST_TYPE, __NAMESPACE__ . '\\meta_boxes' ); add_action( 'save_post_' . POST_TYPE, __NAMESPACE__ . '\\save_post', 10, 2 ); add_filter( 'post_row_actions', __NAMESPACE__ . '\\remove_quick_edit', 10, 2 ); @@ -39,7 +40,7 @@ function register_post_type() { [ 'public' => false, 'show_ui' => true, - 'supports' => [ 'title' ], + 'supports' => false, 'menu_icon' => 'dashicons-groups', 'menu_position' => 151, 'show_in_rest' => true, @@ -81,26 +82,6 @@ function register_default_event_data_maps() { function meta_boxes() { remove_meta_box( 'submitdiv', POST_TYPE, 'side' ); remove_meta_box( 'slugdiv', POST_TYPE, 'normal' ); - - // Add our replaced submitdiv meta box. - add_meta_box( - 'audience-options', - __( 'Audience Options', 'altis-analytics' ), - __NAMESPACE__ . '\\audience_options_ui', - POST_TYPE, - 'side', - 'high' - ); -} - -function audience_options_ui( WP_Post $post ) { - echo sprintf( - '
' . - '' . - '
', - $post->ID, - esc_html__( 'Javascript is required to use the audience editor.', 'altis-analytics' ) - ); } /** @@ -164,7 +145,7 @@ function save_post( $post_id ) { return; } - if ( ! is_array( $_POST['audiencce'] ) ) { + if ( ! is_array( $_POST['audience'] ) ) { return; } @@ -266,6 +247,8 @@ function admin_enqueue_scripts() { return; } + wp_dequeue_script( 'post' ); + wp_enqueue_script( 'altis-analytics-audience-ui', plugins_url( 'build/audiences.js', dirname( __FILE__, 2 ) ), @@ -284,7 +267,16 @@ function admin_enqueue_scripts() { wp_enqueue_style( 'wp-components' ); - $data = []; + $data = [ + 'Fields' => get_field_data(), + ]; + + // Add post data server side to load front end quickly on legacy edit screens. + if ( isset( $_GET['post'] ) ) { + $response = rest_do_request( sprintf( '/wp/v2/audiences/%d', $_GET['post'] ) ); + $data['Current'] = $response->get_data(); + remove_post_type_support( POST_TYPE, 'title' ); + } wp_add_inline_script( 'altis-analytics-audience-ui', diff --git a/inc/audiences/rest_api/namespace.php b/inc/audiences/rest_api/namespace.php index 3b8c31d5..efd51a2e 100644 --- a/inc/audiences/rest_api/namespace.php +++ b/inc/audiences/rest_api/namespace.php @@ -30,6 +30,10 @@ function setup() { * Register audience API endpoints. */ function init() { + // Add post type support for title in the API only. + add_post_type_support( POST_TYPE, 'title' ); + add_post_type_support( POST_TYPE, 'excerpt' ); + // Fetch data for available fields and possible values. register_rest_route( 'analytics/v1', 'audiences/fields', [ [ diff --git a/src/audiences/edit/components/audience-editor.js b/src/audiences/edit/components/audience-editor.js index 79390d65..b484c121 100644 --- a/src/audiences/edit/components/audience-editor.js +++ b/src/audiences/edit/components/audience-editor.js @@ -11,6 +11,14 @@ import { const { __ } = wp.i18n; const { Button } = wp.components; +const StyledAudienceEditor = styled.div` + margin: 0 0 40px; + + .audience-editor__include { + margin: 20px 0; + } +`; + class AudienceEditor extends Component { updateAudience( audience ) { @@ -26,14 +34,10 @@ class AudienceEditor extends Component { } render() { - const { - audience, - fields, - className, - } = this.props; + const { audience } = this.props; return ( -
+
this.updateGroup( groupId, value ) } namePrefix={ `audience[groups][${ groupId }]` } - fields={ fields } canRemove={ audience.groups.length > 1 } onRemove={ () => { const newGroups = audience.groups.slice(); @@ -68,7 +71,7 @@ class AudienceEditor extends Component { > { __( 'Add a group', 'altis-analytics' ) } -
+
); } } @@ -79,12 +82,4 @@ AudienceEditor.defaultProps = { onChange: () => {}, }; -const StyledAudienceEditor = styled( AudienceEditor )` - margin: 0 0 40px; - - .audience-editor__include { - margin: 20px 0; - } -`; - -export default StyledAudienceEditor; +export default AudienceEditor; diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index faf8cdd5..5f76f3df 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -6,12 +6,40 @@ import { getEstimate } from '../data'; const { __ } = wp.i18n; +const StyledEstimate = styled.div` + display: ${ props => props.horizontal ? 'flex' : 'block' }; + margin: 0 0 ${ props => props.horizontal ? 0 : '20px' }; + + .audience-estimate__totals p:last-child { + margin-bottom: 0; + } + + .audience-estimate__totals strong { + font-size: 135%; + font-weight: normal; + margin-right: 2px; + } + + .audience-estimate__totals { + flex: 1; + } + + svg { + flex: 0 0 ${ props => props.horizontal ? '200px' : '100%' }; + width: ${ props => props.horizontal ? '200px' : '100%' }; + } +`; + const Estimate = props => { const { audience, sparkline = true, } = props; + if ( ! audience ) { + return null; + } + const [ loading, setLoading ] = useState( true ); const [ estimate, setEstimate ] = useState( { @@ -37,7 +65,7 @@ const Estimate = props => { } return ( -
+ { estimate.error && (
{ estimate.error.message }
) } @@ -67,31 +95,8 @@ const Estimate = props => { ) } -
+ ); }; -const StyledEstimate = styled( Estimate )` - display: ${ props => props.horizontal ? 'flex' : 'block' }; - - .audience-estimate__totals p:last-child { - margin-bottom: 0; - } - - .audience-estimate__totals strong { - font-size: 135%; - font-weight: normal; - margin-right: 2px; - } - - .audience-estimate__totals { - flex: 1; - } - - svg { - flex: 0 0 ${ props => props.horizontal ? '200px' : '100%' }; - width: ${ props => props.horizontal ? '200px' : '100%' }; - } -`; - -export default StyledEstimate; +export default Estimate; diff --git a/src/audiences/edit/components/group.js b/src/audiences/edit/components/group.js index 8e17ef0d..599c5039 100644 --- a/src/audiences/edit/components/group.js +++ b/src/audiences/edit/components/group.js @@ -9,10 +9,33 @@ import { defaultRule } from '../data/defaults'; const { __ } = wp.i18n; const { Button } = wp.components; +const StyledGroup = styled.div` + background: rgba(0, 0, 0, 0.02); + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, .1); + padding: 20px; + margin: 0 0 20px; + + .audience-editor__group-header { + margin: 0 0 15px; + display: flex; + align-items: baseline; + } + + h3 { + margin: 0 20px 0 0; + text-transform: lowercase; + font-variant: small-caps; + } + + .audience-editor__group-footer .components-button { + margin-right: 10px; + } +`; + const Group = props => { const { title, - className, onChange, namePrefix, include, @@ -30,7 +53,7 @@ const Group = props => { }; return ( -
+
{ title &&

{ title }

} { ) }
-
+ ); }; -const StyledGroup = styled( Group )` - background: rgba(0, 0, 0, 0.02); - border-radius: 3px; - border: 1px solid rgba(0, 0, 0, .1); - padding: 20px; - margin: 0 0 20px; - - .audience-editor__group-header { - margin: 0 0 15px; - display: flex; - align-items: baseline; - } - - h3 { - margin: 0 20px 0 0; - text-transform: lowercase; - font-variant: small-caps; - } - - .audience-editor__group-footer .components-button { - margin-right: 10px; - } -`; - -export default StyledGroup; +export default Group; diff --git a/src/audiences/edit/components/options.js b/src/audiences/edit/components/options.js deleted file mode 100644 index 66898a42..00000000 --- a/src/audiences/edit/components/options.js +++ /dev/null @@ -1,171 +0,0 @@ -import React, { Component } from 'react'; -import styled from 'styled-components'; - -import Estimate from './estimate'; - -import { getAudience } from '../data'; - -const { Button, ToggleControl } = wp.components; -const { __ } = wp.i18n; - -const StyledOptions = styled.div.attrs( () => ( { - className: 'audience-options', -} ) )` - .audience-estimate { - margin: 0 0 30px; - } -`; - -class Options extends Component { - constructor( props ) { - super( props ); - - this.state = { - post: null, // The current post object if known. - title: '', // The current post title. - error: null, // Any error thrown / set manually. - active: false, // If true audience is 'published', else 'draft'. - checks: {}, // Any pre submit checks are stored here. - }; - - // Bind component to DOM event listeners. - this.onChangeTitle = this.onChangeTitle.bind( this ); - this.onSubmit = this.onSubmit.bind( this ); - } - - async componentDidMount() { - // Get the form element if there is one. - this.formElement = document.getElementById( 'post' ); - if ( this.formElement ) { - this.formElement.addEventListener( 'submit', this.onSubmit ); - } - - // Get the title element for the edit screen if there is one. - this.titleField = document.getElementById( 'title' ); - if ( this.titleField ) { - this.titleField.addEventListener( 'keyup', this.onChangeTitle ); - } - - // Fetch the post. - if ( this.props.postId ) { - try { - const post = await getAudience( this.props.postId ); - this.setState( { - active: post.status === 'publish', - post, - title: post.title.raw, - canSave: post.title.raw.length > 0, - } ); - } catch ( error ) { - this.setState( { error } ); - } - } else { - this.setState( { - error: { - message: __( 'Unable to determine current audience ID', 'altis-analytics' ), - }, - } ); - } - } - - componentWillUnmount() { - if ( this.titleField ) { - this.titleField.removeEventListener( this.onChangeTitle ); - this.titleField = null; - } - if ( this.formElement ) { - this.formElement.removeEventListener( this.onSubmit ); - this.formElement = null; - } - } - - onSubmit( event ) { - const checks = this.doPreSaveChecks(); - if ( Object.values( checks ).length > 0 ) { - event.preventDefault(); - } else { - // Prevent the "Leave site?" warning popup showing. - window.onbeforeunload = null; - window.jQuery && window.jQuery( window ).off( 'beforeunload' ); - } - this.setState( { checks } ); - } - - onChangeTitle( event ) { - this.setState( { title: event.target.value } ); - } - - doPreSaveChecks() { - let checks = this.state.checks; - - // Check for empty title. - if ( this.state.title.length === 0 ) { - checks['title_empty'] = __( 'The title cannot be empty', 'altis-analytics' ); - } else { - delete checks['title_empty']; - } - - // Update checks if any have been added or removed. - return checks; - } - - static getDerivedStateFromError( error ) { - return { error }; - } - - componentDidCatch( error, errorInfo ) { - console.error( error, errorInfo ); - } - - render() { - const { - audience, - } = this.props; - const { - post, - error, - active, - checks, - } = this.state; - - if ( error ) { - return ( -
-

{ error.message || __( 'There was an error loading the audience data.', 'altis-analytics' ) }

-
- ); - } - - return ( - - - this.setState( state => ( { active: ! state.active } ) ) } - /> - - { Object.values( checks ).length > 0 && ( -
- { Object.values( checks ).map( check =>

{ check }

) } -
- ) } - -
- ); - } -} - -export default Options; diff --git a/src/audiences/edit/components/rule.js b/src/audiences/edit/components/rule.js index a80d0b48..1b106753 100644 --- a/src/audiences/edit/components/rule.js +++ b/src/audiences/edit/components/rule.js @@ -3,13 +3,31 @@ import styled from 'styled-components'; import SelectOperator from './select-operator'; +const { Button } = wp.components; +const { useSelect } = wp.data; const { __ } = wp.i18n; +const StyledRule = styled.div` + margin: 0 0 15px; + display: flex; + + select, input { + flex: 1; + } + + && > * + * { + margin-left: 5px; + } + + .audience-editor__rule-operator, + button { + flex 0; + } +`; + const Rule = props => { const { - className, namePrefix, - fields, field, operator, onChange, @@ -18,15 +36,17 @@ const Rule = props => { onRemove, } = props; + const fields = useSelect( select => select( 'audience' ).getFields(), [] ); const currentField = fields.filter( fieldData => fieldData.name === field )[0] || {}; return ( -
+ setTitle( e.target.value ) } + placeholder={ __( 'Add title', 'altis-analytics' ) } + ref={ this.titleRef } + disabled={ loading } + /> +
+ +
+ setAudience( value ) } + /> + +
+ + setStatus( post.status === 'publish' ? 'draft' : 'publish' ) } + disabled={ loading } + /> + + +
+
+ ); } } -export default Edit; +Edit.defaultProps = { + postId: null, + post: defaultPost, + loading: false, + onCreate: () => { }, + setTitle: () => { }, + setAudience: () => { }, + setStatus: () => { }, +}; + +const EditWithSelect = withSelect( ( select, props ) => { + let post = props.post; + + if ( props.postId ) { + post = select( 'audience' ).getPost( props.postId ); + } + + // If we have a post ID but no post then we're loading. + const loading = props.postId && ! post.id; + + return { + post, + loading, + }; +} )( Edit ); + +const EditWithDispatch = withDispatch( dispatch => { + const store = dispatch( 'audience' ); + + return { + setTitle: value => { + store.setPost( { title: { rendered: value } } ); + }, + setAudience: value => { + store.setPost( { audience: value } ); + }, + setStatus: value => { + store.setPost( { status: value ? 'publish' : 'draft' } ); + }, + }; +} )( EditWithSelect ); + +export default EditWithDispatch; From f4612cb2636b7d7bb983fed9d31e58f507ec6cd5 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Thu, 19 Mar 2020 14:18:42 +0000 Subject: [PATCH 31/95] completed updates to use redux store --- inc/audiences/namespace.php | 10 +- src/audiences/edit/components/estimate.js | 103 +++++++----------- src/audiences/edit/components/pie-chart.js | 38 +++++++ src/audiences/edit/components/rule.js | 2 +- .../edit/components/select-operator.js | 6 +- src/audiences/edit/data/index.js | 24 ++-- src/audiences/edit/index.js | 6 +- src/audiences/index.js | 5 +- 8 files changed, 104 insertions(+), 90 deletions(-) create mode 100644 src/audiences/edit/components/pie-chart.js diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index cb6abd70..b0b39e82 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -267,6 +267,7 @@ function admin_enqueue_scripts() { wp_enqueue_style( 'wp-components' ); + // Hydrate with data to speed up the front end. $data = [ 'Fields' => get_field_data(), ]; @@ -275,6 +276,9 @@ function admin_enqueue_scripts() { if ( isset( $_GET['post'] ) ) { $response = rest_do_request( sprintf( '/wp/v2/audiences/%d', $_GET['post'] ) ); $data['Current'] = $response->get_data(); + // Calling the rest function triggers the `rest_api_init` action + // where we reinstate title support. It's removed to provide a clean + // legacy postedit screen. remove_post_type_support( POST_TYPE, 'title' ); } @@ -320,11 +324,11 @@ function get_estimate( array $audience ) : ?array { ], ], 'aggs' => [ - 'estimate' => [ 'cardinality' => [ 'field' => 'endpoint.User.UserId.keyword' ] ], + 'estimate' => [ 'cardinality' => [ 'field' => 'endpoint.Id.keyword' ] ], 'histogram' => [ 'histogram' => [ 'field' => 'event_timestamp', - 'interval' => 4 * 60 * 60 * 1000, // 4 hour chunks + 'interval' => 6 * 60 * 60 * 1000, // 6 hour chunks. 'extended_bounds' => [ 'min' => milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ), 'max' => milliseconds(), @@ -389,7 +393,7 @@ function get_unique_enpoint_count() : ?int { ], ], 'aggs' => [ - 'count' => [ 'cardinality' => [ 'field' => 'endpoint.User.UserId.keyword' ] ], + 'count' => [ 'cardinality' => [ 'field' => 'endpoint.Id.keyword' ] ], ], 'size' => 0, 'sort' => [ 'event_timestamp' => 'desc' ], diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index 5f76f3df..397072f0 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -1,100 +1,75 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { Sparklines, SparklinesLine } from 'react-sparklines'; import styled from 'styled-components'; -import { getEstimate } from '../data'; +import PieChart from './pie-chart'; +const { useSelect } = wp.data; const { __ } = wp.i18n; const StyledEstimate = styled.div` - display: ${ props => props.horizontal ? 'flex' : 'block' }; - margin: 0 0 ${ props => props.horizontal ? 0 : '20px' }; + display: flex; + margin: 0; - .audience-estimate__totals p:last-child { - margin-bottom: 0; + .audience-estimate__error { + flex: 0 0 100%; } - .audience-estimate__totals strong { - font-size: 135%; - font-weight: normal; - margin-right: 2px; + .audience-estimate__percentage { + flex: 0 1 100px; + margin-right: 20px; } .audience-estimate__totals { - flex: 1; + flex: 2; + padding: 5px 0; + } + + .audience-estimate__totals svg { + max-width: 220px; + } + + .audience-estimate__totals p { + margin: 10px 0 0; } - svg { - flex: 0 0 ${ props => props.horizontal ? '200px' : '100%' }; - width: ${ props => props.horizontal ? '200px' : '100%' }; + .audience-estimate__totals strong { + font-size: 135%; + font-weight: normal; + margin-right: 2px; } `; const Estimate = props => { - const { - audience, - sparkline = true, - } = props; + const { audience } = props; if ( ! audience ) { return null; } - const [ loading, setLoading ] = useState( true ); - - const [ estimate, setEstimate ] = useState( { - count: 0, - total: 0, - histogram: [], - } ); - - const fetchEstimate = () => { - ( async () => { - const estimateResponse = await getEstimate( audience ); - setEstimate( estimateResponse ); - setLoading( false ); - } )(); - }; - - useEffect( fetchEstimate, [ audience ] ); - - if ( loading ) { - return ( -

{ __( 'Loading...', 'altis-analytics' ) }

- ); - } + const estimate = useSelect( select => select( 'audience' ).getEstimate( audience ), [ audience ] ); + const percent = estimate.total ? Math.round( ( estimate.count / estimate.total ) * 100 ) : 0; return ( - - { estimate.error && ( -
{ estimate.error.message }
- ) } - + +
-

- { estimate.count } - { ' ' } - { __( 'in the last 7 days' ) } -

- - { estimate.total > 0 && ( -

- { Math.round( ( estimate.count / estimate.total ) * 100 ) }% - { ' ' } - { __( 'of total traffic', 'altis-analytics' ) } -

- ) } -
- - { sparkline && estimate.histogram && estimate.histogram.length > 0 && ( item.count ) } preserveAspectRatio="xMidYMid meet" > - + - ) } +

+ { estimate.count } + { ' ' } + { __( 'in the last 7 days' ) } +

+
); }; diff --git a/src/audiences/edit/components/pie-chart.js b/src/audiences/edit/components/pie-chart.js new file mode 100644 index 00000000..011237dc --- /dev/null +++ b/src/audiences/edit/components/pie-chart.js @@ -0,0 +1,38 @@ +import React from 'react'; +import styled from 'styled-components'; + +const StyledPie = styled.svg` + circle { + fill: transparent; + stroke: ${ props => props.stroke || 'rgb(0, 124, 186)' }; + stroke-width: 3; + stroke-opacity: 0.2; + stroke-dasharray: 101 100; + stroke-dashoffset: 0; + transform: rotate(-90deg); + transform-origin: center; + transition: stroke-dasharray 0.3s ease-in-out; + } + circle[data-percent] { + stroke-dasharray: ${ props => props.percent === 100 ? 101 : props.percent } 100; + stroke-opacity: 1; + } + text { + stroke-width: 0; + font-size: 9px; + } +`; + +const PieChart = props => ( + + + + { props.percent }% + +); + +PieChart.defaultProps = { + percent: 0, +}; + +export default PieChart; diff --git a/src/audiences/edit/components/rule.js b/src/audiences/edit/components/rule.js index 1b106753..24b84538 100644 --- a/src/audiences/edit/components/rule.js +++ b/src/audiences/edit/components/rule.js @@ -59,7 +59,7 @@ const Rule = props => { name={ `${ namePrefix }[operator]` } value={ operator } onChange={ e => onChange( { operator: e.target.value } ) } - fieldType={ currentField.type || 'string' } + type={ currentField.type || 'string' } disabled={ fields.length === 0 } /> diff --git a/src/audiences/edit/components/select-operator.js b/src/audiences/edit/components/select-operator.js index 7cfd6598..7765801f 100644 --- a/src/audiences/edit/components/select-operator.js +++ b/src/audiences/edit/components/select-operator.js @@ -7,19 +7,19 @@ import { const SelectOperator = props => { const { - fieldType = 'string', + type = 'string' } = props; let options = STRING_OPERATIONS; - if ( fieldType === 'number' ) { + if ( type === 'number' ) { options = NUMERIC_OPERATIONS; } return ( ); diff --git a/src/audiences/edit/data/index.js b/src/audiences/edit/data/index.js index c9ff48da..8d27561f 100644 --- a/src/audiences/edit/data/index.js +++ b/src/audiences/edit/data/index.js @@ -10,7 +10,7 @@ const INITIAL_STATE = { posts: [], pagination: {}, fields: [], - estimates: [], + estimates: {}, post: defaultPost, }; @@ -36,9 +36,10 @@ const actions = { posts, }; }, - addEstimate( estimate ) { + addEstimate( audience, estimate ) { return { type: 'ADD_ESTIMATE', + audience, estimate, }; }, @@ -66,14 +67,15 @@ export const store = registerStore( 'audience', { fields: action.fields, } case 'ADD_ESTIMATE': - if ( state.estimates[ action.key ] ) { + const key = JSON.stringify( action.audience ); + if ( state.estimates[ key ] ) { return state; } return { ...state, estimates: { ...state.estimates, - [ action.key ]: action.estimate, + [ key ]: action.estimate, }, }; case 'ADD_POSTS': @@ -122,7 +124,7 @@ export const store = registerStore( 'audience', { return state.estimates[ key ] || { count: 0, total: 0, - histogram: [], + histogram: [].fill( { count: 0 }, 0, 27 ), // Build empty histrogram data. }; }, getPost( state ) { @@ -149,7 +151,7 @@ export const store = registerStore( 'audience', { const estimate = yield actions.fetch( { path: `analytics/v1/audiences/estimate?audience=${ audienceQuery }`, } ); - return actions.addEstimate( estimate ); + return actions.addEstimate( audience, estimate ); }, * getPost( id ) { const post = yield actions.fetch( { @@ -172,13 +174,3 @@ export const store = registerStore( 'audience', { }, }, } ); - -export const getEstimate = async audience => { - const audienceQuery = encodeURIComponent( JSON.stringify( audience ) ); - // Get audience estimate data. - const data = await apiFetch( { - path: `analytics/v1/audiences/estimate?audience=${ audienceQuery }`, - } ); - - return data; -}; diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index 1464c474..6f19eea3 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -39,6 +39,10 @@ const StyledEdit = styled.div` flex: 0 1 320px; margin: 20px 0 20px 40px; } + + .audience-estimate { + margin-bottom: 40px; + } `; class Edit extends Component { @@ -121,7 +125,7 @@ class Edit extends Component { } = this.props; return ( - + { error && ( , AudienceUI ); From 69de06b0c48cd3cda5b1ecef281292338ae0a505 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Thu, 19 Mar 2020 14:26:39 +0000 Subject: [PATCH 32/95] fix code style issues --- .../edit/components/select-include.js | 2 +- .../edit/components/select-operator.js | 2 +- src/audiences/edit/data/defaults.js | 2 +- src/audiences/edit/data/index.js | 38 +++++++++++-------- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/audiences/edit/components/select-include.js b/src/audiences/edit/components/select-include.js index 6d5c950d..ca85a3f1 100644 --- a/src/audiences/edit/components/select-include.js +++ b/src/audiences/edit/components/select-include.js @@ -24,6 +24,6 @@ const SelectInclude = props => { SelectInclude.defaultProps = { label: '', -} +}; export default SelectInclude; diff --git a/src/audiences/edit/components/select-operator.js b/src/audiences/edit/components/select-operator.js index 7765801f..e27c67c4 100644 --- a/src/audiences/edit/components/select-operator.js +++ b/src/audiences/edit/components/select-operator.js @@ -7,7 +7,7 @@ import { const SelectOperator = props => { const { - type = 'string' + type = 'string', } = props; let options = STRING_OPERATIONS; diff --git a/src/audiences/edit/data/defaults.js b/src/audiences/edit/data/defaults.js index 11807887..b164b7ac 100644 --- a/src/audiences/edit/data/defaults.js +++ b/src/audiences/edit/data/defaults.js @@ -22,5 +22,5 @@ export const defaultAudience = { export const defaultPost = { title: { rendered: '' }, audience: defaultAudience, - status: 'draft' + status: 'draft', }; diff --git a/src/audiences/edit/data/index.js b/src/audiences/edit/data/index.js index 8d27561f..973d5715 100644 --- a/src/audiences/edit/data/index.js +++ b/src/audiences/edit/data/index.js @@ -46,7 +46,7 @@ const actions = { setPost( post ) { return { type: 'SET_POST', - post + post, }; }, fetch( options ) { @@ -54,19 +54,20 @@ const actions = { type: 'FETCH_FROM_API', options, }; - } + }, }; export const store = registerStore( 'audience', { initialState: INITIAL_STATE, reducer( state, action ) { switch ( action.type ) { - case 'SET_FIELDS': + case 'SET_FIELDS': { return { ...state, fields: action.fields, - } - case 'ADD_ESTIMATE': + }; + } + case 'ADD_ESTIMATE': { const key = JSON.stringify( action.audience ); if ( state.estimates[ key ] ) { return state; @@ -78,7 +79,8 @@ export const store = registerStore( 'audience', { [ key ]: action.estimate, }, }; - case 'ADD_POSTS': + } + case 'ADD_POSTS': { const posts = state.posts.slice(); action.posts.forEach( post => { if ( ! posts.filter( existing => post.id === existing.id ).length ) { @@ -92,27 +94,33 @@ export const store = registerStore( 'audience', { ...state, posts, }; - case 'SET_POST': - let updatedPosts; - const existingPosts = state.posts.slice(); + } + case 'SET_POST': { + let posts = state.posts.slice(); if ( action.post.id ) { - updatedPosts = existingPosts.map( post => { + posts = posts.map( post => { if ( post.id === action.post.id ) { - post = { ...post, ...action.post }; + post = { + ...post, + ...action.post, + }; } return post; } ); } return { ...state, - posts: action.post.id ? updatedPosts : state.posts, + posts: action.post.id ? posts : state.posts, post: { ...state.post, ...action.post, - } + }, }; + } + default: { + return state; + } } - return state; }, actions, selectors: { @@ -132,7 +140,7 @@ export const store = registerStore( 'audience', { }, getPosts( state ) { return state.posts; - } + }, }, controls: { FETCH_FROM_API( action ) { From a74f65e766fee9d5443ddeba2c1f9f7dd2923822 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Thu, 19 Mar 2020 14:41:58 +0000 Subject: [PATCH 33/95] improve table listing estimate styling --- src/audiences/index.css | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/audiences/index.css b/src/audiences/index.css index bfcfedfe..a6ded673 100644 --- a/src/audiences/index.css +++ b/src/audiences/index.css @@ -2,6 +2,18 @@ * Audiences admin listing styles. */ -th#last_modified { +.post-type-audience .wp-list-table th#last_modified { width: 10em; } + +.post-type-audience .wp-list-table .audience-estimate__percentage { + max-width: 60px; +} + +.post-type-audience .wp-list-table .audience-estimate .audience-estimate__totals svg { + max-width: 120px; +} + +.post-type-audience .wp-list-table .audience-estimate .audience-estimate__totals svg polyline:last-child { + stroke-width: 4 !important; +} From be60628b8a34032272f539768bddafb66de73ce2 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Thu, 19 Mar 2020 15:52:05 +0000 Subject: [PATCH 34/95] switch to asset hash names instead of string replace on deploy --- .circleci/deploy.sh | 5 - inc/audiences/namespace.php | 9 +- inc/namespace.php | 4 +- inc/utils/namespace.php | 32 +++++ package-lock.json | 261 +++++++++++++++++++++++++++++++++++- package.json | 4 +- webpack.config.js | 11 +- 7 files changed, 307 insertions(+), 19 deletions(-) diff --git a/.circleci/deploy.sh b/.circleci/deploy.sh index 7edb89b3..0a33223a 100755 --- a/.circleci/deploy.sh +++ b/.circleci/deploy.sh @@ -62,11 +62,6 @@ fi rsync -av "$SRC_DIR/" "$BUILD_DIR" --exclude-from "$SRC_DIR/.circleci/deploy-exclude.txt" -# Swap commit placeholders -SCRIPT_HASH=$(md5sum build/analytics.js | awk '{ print $1 }') -find . -name '*.php' | xargs sed -i.bak 's/__SCRIPT_HASH__/'"$SCRIPT_HASH"'/g' -find . -name '*.bak' -delete - # Add changed files git add . diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index b0b39e82..4a3022c0 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -8,6 +8,7 @@ namespace Altis\Analytics\Audiences; use function Altis\Analytics\Audiences\REST_API\get_audience_schema; +use function Altis\Analytics\Utils\get_asset_url; use function Altis\Analytics\Utils\get_field_type; use function Altis\Analytics\Utils\milliseconds; use function Altis\Analytics\Utils\query; @@ -251,7 +252,7 @@ function admin_enqueue_scripts() { wp_enqueue_script( 'altis-analytics-audience-ui', - plugins_url( 'build/audiences.js', dirname( __FILE__, 2 ) ), + get_asset_url( 'audiences.js' ), [ 'react', 'react-dom', @@ -261,7 +262,7 @@ function admin_enqueue_scripts() { 'wp-components', 'wp-api-fetch', ], - '__AUDIENCE_SCRIPT_HASH__', + null, true ); @@ -289,7 +290,7 @@ function admin_enqueue_scripts() { 'window.Altis.Analytics = window.Altis.Analytics || {};' . 'window.Altis.Analytics.BuildURL = %s;' . 'window.Altis.Analytics.Audiences = %s;', - wp_json_encode( plugins_url( 'build/', dirname( __FILE__, 2 ) ) ), + wp_json_encode( plugins_url( 'build', dirname( __FILE__, 2 ) ) ), wp_json_encode( (object) $data ) ), 'before' @@ -299,7 +300,7 @@ function admin_enqueue_scripts() { 'altis-analytics-audience-ui', plugins_url( 'src/audiences/index.css', dirname( __FILE__, 2 ) ), [], - '__AUDIENCE_STYLE_HASH__' + '2020-03-19-1' ); } diff --git a/inc/namespace.php b/inc/namespace.php index b5c31da5..0aae0e98 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -7,6 +7,8 @@ namespace Altis\Analytics; +use function Altis\Analytics\Utils\get_asset_url; + function setup() { // Setup audiences. Audiences\setup(); @@ -158,7 +160,7 @@ function async_scripts( string $tag, string $handle ) : string { function enqueue_scripts() { global $wp_scripts; - wp_enqueue_script( 'altis-analytics', plugins_url( 'build/analytics.js', __DIR__ ), [], '__SCRIPT_HASH__', false ); + wp_enqueue_script( 'altis-analytics', get_asset_url( 'analytics.js' ), [], null, false ); wp_add_inline_script( 'altis-analytics', sprintf( diff --git a/inc/utils/namespace.php b/inc/utils/namespace.php index 74b3c2dc..36509a8c 100644 --- a/inc/utils/namespace.php +++ b/inc/utils/namespace.php @@ -5,6 +5,38 @@ namespace Altis\Analytics\Utils; +use const Altis\Analytics\ROOT_DIR; + +/** + * Return asset file name based on generated manifest.json file. + * + * @param string $filename + * @return string|false + */ +function get_asset_url( string $filename ) { + $manifest_file = ROOT_DIR . '/build/manifest.json'; + + if ( ! file_exists( $manifest_file ) ) { + return false; + } + + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + $manifest = file_get_contents( $manifest_file ); + $manifest = json_decode( $manifest, true ); + + if ( ! $manifest || ! isset( $manifest[ $filename ] ) ) { + return false; + } + + $path = $manifest[ $filename ]; + + if ( strpos( $path, 'http' ) !== false ) { + return $path; + } + + return plugins_url( $manifest[ $filename ], ROOT_DIR . '/build' ); +} + /** * Calculate the combined standard deviation for multiple groups of * averages, standard deviations and sizes. diff --git a/package-lock.json b/package-lock.json index 866180fa..89ab48a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1470,11 +1470,116 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, + "@types/anymatch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", + "dev": true + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "13.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.2.tgz", + "integrity": "sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg==", + "dev": true + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "@types/tapable": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.5.tgz", + "integrity": "sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ==", + "dev": true + }, + "@types/uglify-js": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz", + "integrity": "sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@types/webpack": { + "version": "4.41.7", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.7.tgz", + "integrity": "sha512-OQG9viYwO0V1NaNV7d0n79V+n6mjOV30CwgFPIfTzwmk8DHbt+C4f2aBGdCYbo3yFyYD6sjXfqqOjwkl1j+ulA==", + "dev": true, + "requires": { + "@types/anymatch": "*", + "@types/node": "*", + "@types/tapable": "*", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@types/webpack-sources": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.6.tgz", + "integrity": "sha512-FtAWR7wR5ocJ9+nP137DV81tveD/ZgB1sadnJ/axUGM3BUVfRPx8oQNMtv3JNfTeHx3VP7cXiyfR/jmtEsVHsQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -1852,6 +1957,21 @@ "is-string": "^1.0.5" } }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -2419,6 +2539,16 @@ } } }, + "clean-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-MciirUH5r+cYLGCOL5JX/ZLzOZbVr1ot3Fw+KcvbhUb6PM+yycqd9ZhIlcigQ5gl+XhppNmw3bEFuaaMNyLj3A==", + "dev": true, + "requires": { + "@types/webpack": "^4.4.31", + "del": "^4.1.1" + } + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -2821,6 +2951,21 @@ } } }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + } + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -3736,6 +3881,17 @@ "readable-stream": "^2.0.0" } }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", @@ -4356,6 +4512,27 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, "graceful-fs": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", @@ -4748,6 +4925,30 @@ } } }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + } + }, + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -4861,12 +5062,21 @@ }, "dependencies": { "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" } } }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "jsx-ast-utils": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz", @@ -4962,9 +5172,9 @@ } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" } } }, @@ -5558,6 +5768,12 @@ "p-limit": "^2.0.0" } }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -5698,6 +5914,21 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", @@ -7084,6 +7315,12 @@ "imurmurhash": "^0.1.4" } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -7330,6 +7567,18 @@ } } }, + "webpack-manifest-plugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz", + "integrity": "sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ==", + "dev": true, + "requires": { + "fs-extra": "^7.0.0", + "lodash": ">=3.5 <5", + "object.entries": "^1.1.0", + "tapable": "^1.0.0" + } + }, "webpack-sources": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", diff --git a/package.json b/package.json index f2b29d63..5cb018b2 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,9 @@ "webpack-bundle-analyzer": "^3.6.0", "webpack-cli": "^3.3.11", "dynamic-public-path-webpack-plugin": "^1.0.4", - "webpack-subresource-integrity": "^1.4.0" + "webpack-subresource-integrity": "^1.4.0", + "webpack-manifest-plugin": "^2.2.0", + "clean-webpack-plugin": "^3.0.0" }, "devDependencies": { "babel-eslint": "^10.1.0", diff --git a/webpack.config.js b/webpack.config.js index f525e02a..d353f1b4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,6 +5,8 @@ const BundleAnalyzerPlugin = require( 'webpack-bundle-analyzer' ) .BundleAnalyzerPlugin; const DynamicPublicPathPlugin = require( 'dynamic-public-path-webpack-plugin' ); const SriPlugin = require( 'webpack-subresource-integrity' ); +const ManifestPlugin = require( 'webpack-manifest-plugin' ); +const { CleanWebpackPlugin } = require( 'clean-webpack-plugin' ); const sharedConfig = { mode: mode, @@ -14,8 +16,9 @@ const sharedConfig = { }, output: { path: path.resolve( __dirname, 'build' ), - filename: '[name].js', - publicPath: '.', + filename: '[name].[hash:8].js', + chunkFilename: 'chunk.[id].[chunkhash:8].js', + publicPath: '/', libraryTarget: 'this', jsonpFunction: 'AltisAnalyticsJSONP', crossOriginLoading: 'anonymous', @@ -48,6 +51,10 @@ const sharedConfig = { new webpack.EnvironmentPlugin( { SC_ATTR: 'data-styled-components-altis-analytics', } ), + new ManifestPlugin( { + writeToFileEmit: true, + } ), + new CleanWebpackPlugin(), ], externals: { 'Altis': 'Altis', From 479b9f86b62ea93cd812886329ecc0f32973707a Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Thu, 19 Mar 2020 16:29:23 +0000 Subject: [PATCH 35/95] styling tweaks for estimate percentage --- inc/audiences/namespace.php | 11 ++++++++ inc/utils/namespace.php | 2 +- src/audiences/edit/components/estimate.js | 30 ++++++++++++---------- src/audiences/edit/components/pie-chart.js | 3 ++- src/audiences/edit/index.js | 2 +- src/audiences/index.css | 4 --- 6 files changed, 32 insertions(+), 20 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 4a3022c0..8e8e24e4 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -48,6 +48,17 @@ function register_post_type() { 'rest_base' => 'audiences', 'hierarchical' => false, 'admin_cols' => [ + 'active' => [ + 'title' => __( 'Status' ), + 'function' => function () { + $post = $GLOBALS['post']; + if ( $post->post_status === 'publish' ) { + esc_html_e( 'Active', 'altis-analytics' ); + } else { + esc_html_e( 'Inactive', 'altis-analytics' ); + } + }, + ], 'estimate' => [ 'title' => __( 'Estimate', 'altis-analytics' ), 'function' => function () { diff --git a/inc/utils/namespace.php b/inc/utils/namespace.php index 36509a8c..85f2a0bd 100644 --- a/inc/utils/namespace.php +++ b/inc/utils/namespace.php @@ -34,7 +34,7 @@ function get_asset_url( string $filename ) { return $path; } - return plugins_url( $manifest[ $filename ], ROOT_DIR . '/build' ); + return plugins_url( $manifest[ $filename ], ROOT_DIR . '/build/assets' ); } /** diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index 397072f0..cb5c47d4 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -22,26 +22,28 @@ const StyledEstimate = styled.div` .audience-estimate__totals { flex: 2; - padding: 5px 0; } .audience-estimate__totals svg { max-width: 220px; + margin-top: 5px; + margin-bottom: 10px; } .audience-estimate__totals p { - margin: 10px 0 0; + margin: 0; } .audience-estimate__totals strong { - font-size: 135%; - font-weight: normal; margin-right: 2px; } `; const Estimate = props => { - const { audience } = props; + const { + audience, + sparkline = false, + } = props; if ( ! audience ) { return null; @@ -57,17 +59,19 @@ const Estimate = props => { percent={ percent } />
- item.count ) } - preserveAspectRatio="xMidYMid meet" - > - - + { sparkline && ( + item.count ) } + preserveAspectRatio="xMidYMid meet" + > + + + ) }

{ estimate.count } { ' ' } - { __( 'in the last 7 days' ) } + { __( 'uniques in the last 7 days' ) }

diff --git a/src/audiences/edit/components/pie-chart.js b/src/audiences/edit/components/pie-chart.js index 011237dc..433ccee3 100644 --- a/src/audiences/edit/components/pie-chart.js +++ b/src/audiences/edit/components/pie-chart.js @@ -5,10 +5,11 @@ const StyledPie = styled.svg` circle { fill: transparent; stroke: ${ props => props.stroke || 'rgb(0, 124, 186)' }; - stroke-width: 3; + stroke-linecap: round; stroke-opacity: 0.2; stroke-dasharray: 101 100; stroke-dashoffset: 0; + stroke-width: 3; transform: rotate(-90deg); transform-origin: center; transition: stroke-dasharray 0.3s ease-in-out; diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index 6f19eea3..536c2dff 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -163,7 +163,7 @@ class Edit extends Component { />
- + Date: Thu, 19 Mar 2020 17:41:54 +0000 Subject: [PATCH 36/95] fix setStatus usage --- src/audiences/edit/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index 536c2dff..b42ed93b 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -229,7 +229,7 @@ const EditWithDispatch = withDispatch( dispatch => { store.setPost( { audience: value } ); }, setStatus: value => { - store.setPost( { status: value ? 'publish' : 'draft' } ); + store.setPost( { status: value } ); }, }; } )( EditWithSelect ); From 213a90b0fdd4e82b0c65714c62bdb3633b89cd59 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Thu, 19 Mar 2020 18:01:28 +0000 Subject: [PATCH 37/95] usability improvement: add title for audience estimate and options --- inc/audiences/namespace.php | 4 ++-- src/audiences/edit/components/estimate.js | 14 ++++++++++++-- src/audiences/edit/data/index.js | 2 +- src/audiences/edit/index.js | 7 ++++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 8e8e24e4..e7e224f4 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -49,7 +49,7 @@ function register_post_type() { 'hierarchical' => false, 'admin_cols' => [ 'active' => [ - 'title' => __( 'Status' ), + 'title' => __( 'Status', 'altis-analytics' ), 'function' => function () { $post = $GLOBALS['post']; if ( $post->post_status === 'publish' ) { @@ -60,7 +60,7 @@ function register_post_type() { }, ], 'estimate' => [ - 'title' => __( 'Estimate', 'altis-analytics' ), + 'title' => __( 'Size', 'altis-analytics' ), 'function' => function () { estimate_ui( $GLOBALS['post'] ); }, diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index cb5c47d4..35960b9c 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -9,10 +9,12 @@ const { __ } = wp.i18n; const StyledEstimate = styled.div` display: flex; + flex-wrap: wrap; margin: 0; - .audience-estimate__error { + .audience-estimate__title { flex: 0 0 100%; + margin: 0 0 20px; } .audience-estimate__percentage { @@ -42,7 +44,8 @@ const StyledEstimate = styled.div` const Estimate = props => { const { audience, - sparkline = false, + sparkline, + title, } = props; if ( ! audience ) { @@ -54,6 +57,7 @@ const Estimate = props => { return ( + { title &&

{ title }

} { ); }; +Estimate.defaultProps = { + audience: null, + sparkline: false, + title: '', +}; + export default Estimate; diff --git a/src/audiences/edit/data/index.js b/src/audiences/edit/data/index.js index 973d5715..14d67cc8 100644 --- a/src/audiences/edit/data/index.js +++ b/src/audiences/edit/data/index.js @@ -132,7 +132,7 @@ export const store = registerStore( 'audience', { return state.estimates[ key ] || { count: 0, total: 0, - histogram: [].fill( { count: 0 }, 0, 27 ), // Build empty histrogram data. + histogram: new Array( 28 ).fill( { count: 1 } ), // Build empty histrogram data. }; }, getPost( state ) { diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index b42ed93b..b43922b2 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -163,7 +163,12 @@ class Edit extends Component { />
- + +

{ __( 'Audience options', 'altis-analytics' ) }

Date: Fri, 20 Mar 2020 09:19:02 +0000 Subject: [PATCH 38/95] remove unused use statements --- inc/audiences/namespace.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index e7e224f4..f075257c 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -13,8 +13,6 @@ use function Altis\Analytics\Utils\milliseconds; use function Altis\Analytics\Utils\query; use WP_Post; -use WP_REST_Request; -use WP_REST_Server; const POST_TYPE = 'audience'; From cfd06b520b072e93c1bdf7fb34fb5ce9fe0ac46e Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Fri, 20 Mar 2020 11:23:03 +0000 Subject: [PATCH 39/95] code style fix, bracket spacing --- src/audiences/edit/components/estimate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index 35960b9c..03942370 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -69,7 +69,7 @@ const Estimate = props => { data={ estimate.histogram.map( item => item.count ) } preserveAspectRatio="xMidYMid meet" > - + ) }

From 755721db21770fa9407bd633a73fdbca7641fe2f Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 31 Mar 2020 17:36:51 +0100 Subject: [PATCH 40/95] fix a bug if a draft is autosaved but the audience config is null --- src/audiences/edit/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index b43922b2..c4a29dd5 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -4,7 +4,7 @@ import styled from 'styled-components'; import AudienceEditor from './components/audience-editor'; import Estimate from './components/estimate'; -import { defaultPost } from './data/defaults'; +import { defaultPost, defaultAudience } from './data/defaults'; const { withSelect, @@ -158,7 +158,7 @@ class Edit extends Component {

setAudience( value ) } /> From 6d2bbdaf7f819631e7a01cd897e3586e83549563 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Thu, 2 Apr 2020 13:29:09 +0100 Subject: [PATCH 41/95] Use the namespace instead of individual functions --- inc/audiences/namespace.php | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index f075257c..43206477 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -7,11 +7,7 @@ namespace Altis\Analytics\Audiences; -use function Altis\Analytics\Audiences\REST_API\get_audience_schema; -use function Altis\Analytics\Utils\get_asset_url; -use function Altis\Analytics\Utils\get_field_type; -use function Altis\Analytics\Utils\milliseconds; -use function Altis\Analytics\Utils\query; +use Altis\Analytics\Utils; use WP_Post; const POST_TYPE = 'audience'; @@ -174,7 +170,7 @@ function save_audience( int $post_id, array $audience ) : bool { delete_post_meta( $post_id, 'audience_error' ); // Validate using audience schema. - $valid = rest_validate_value_from_schema( $audience, get_audience_schema(), 'audience' ); + $valid = rest_validate_value_from_schema( $audience, REST_API\get_audience_schema(), 'audience' ); if ( is_wp_error( $valid ) ) { update_post_meta( $post_id, 'audience_error', $valid->get_error_message() ); @@ -244,7 +240,7 @@ function register_field( string $field, string $label ) { $altis_analytics_event_data_maps[ $field ] = [ 'name' => $field, 'label' => $label, - 'type' => get_field_type( $field ), + 'type' => Utils\get_field_type( $field ), ]; ksort( $altis_analytics_event_data_maps ); } @@ -261,7 +257,7 @@ function admin_enqueue_scripts() { wp_enqueue_script( 'altis-analytics-audience-ui', - get_asset_url( 'audiences.js' ), + Utils\get_asset_url( 'audiences.js' ), [ 'react', 'react-dom', @@ -327,7 +323,7 @@ function get_estimate( array $audience ) : ?array { // Set current site. [ 'term' => [ 'attributes.blogId.keyword' => get_current_blog_id() ] ], // Last 7 days. - [ 'range' => [ 'event_timestamp' => [ 'gte' => milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ) ] ] ], + [ 'range' => [ 'event_timestamp' => [ 'gte' => Utils\milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ) ] ] ], // Limit event type to pageView. [ 'term' => [ 'event_type.keyword' => 'pageView' ] ], ], @@ -340,8 +336,8 @@ function get_estimate( array $audience ) : ?array { 'field' => 'event_timestamp', 'interval' => 6 * 60 * 60 * 1000, // 6 hour chunks. 'extended_bounds' => [ - 'min' => milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ), - 'max' => milliseconds(), + 'min' => Utils\milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ), + 'max' => Utils\milliseconds(), ], ], ], @@ -359,7 +355,7 @@ function get_estimate( array $audience ) : ?array { return $cache; } - $result = query( $query ); + $result = Utils\query( $query ); if ( ! $result ) { return $result; @@ -396,7 +392,7 @@ function get_unique_enpoint_count() : ?int { // Set current site. [ 'term' => [ 'attributes.blogId.keyword' => get_current_blog_id() ] ], // Last 7 days. - [ 'range' => [ 'event_timestamp' => [ 'gte' => milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ) ] ] ], + [ 'range' => [ 'event_timestamp' => [ 'gte' => Utils\milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ) ] ] ], // Limit event type to pageView. [ 'term' => [ 'event_type.keyword' => 'pageView' ] ], ], @@ -414,7 +410,7 @@ function get_unique_enpoint_count() : ?int { return $cache; } - $result = query( $query ); + $result = Utils\query( $query ); if ( ! $result ) { return $result; @@ -474,7 +470,7 @@ function get_field_data() : ?array { // Query for current site. [ 'term' => [ 'attributes.blogId.keyword' => (string) get_current_blog_id() ] ], // Last 7 days. - [ 'range' => [ 'event_timestamp' => [ 'gte' => milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ) ] ] ], + [ 'range' => [ 'event_timestamp' => [ 'gte' => Utils\milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ) ] ] ], ], ], ], @@ -485,7 +481,7 @@ function get_field_data() : ?array { foreach ( $maps as $map ) { // For numeric fields get a simple stats aggregation. - if ( get_field_type( $map['name'] ) === 'number' ) { + if ( Utils\get_field_type( $map['name'] ) === 'number' ) { $query['aggs'][ $map['name'] ] = [ 'stats' => [ 'field' => $map['name'], @@ -493,7 +489,7 @@ function get_field_data() : ?array { ]; } // Default to terms aggregations for top 100 different values available for each field. - if ( get_field_type( $map['name'] ) === 'string' ) { + if ( Utils\get_field_type( $map['name'] ) === 'string' ) { $query['aggs'][ $map['name'] ] = [ 'terms' => [ 'field' => "{$map['name']}.keyword", @@ -503,7 +499,7 @@ function get_field_data() : ?array { } } - $result = query( $query ); + $result = Utils\query( $query ); if ( ! $result ) { return $result; @@ -578,7 +574,7 @@ function build_audience_query( array $audience ) : array { ]; // Handle string comparisons. - if ( get_field_type( $rule['field'] ) === 'string' ) { + if ( Utils\get_field_type( $rule['field'] ) === 'string' ) { switch ( $rule['operator'] ) { case '=': $rule_query['bool']['filter'][] = [ @@ -609,7 +605,7 @@ function build_audience_query( array $audience ) : array { } // Handle numeric field comparisons. - if ( get_field_type( $rule['field'] ) === 'number' ) { + if ( Utils\get_field_type( $rule['field'] ) === 'number' ) { $rule_query['bool']['filter'][] = [ 'range' => [ $rule['field'] => [ $rule['operator'] => intval( $rule['value'] ) ] ], ]; From a9eb6322ed036b5ef1592d5d50d36ccbe87e294b Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Thu, 2 Apr 2020 13:31:00 +0100 Subject: [PATCH 42/95] Use WordPress constants for time blocks --- inc/audiences/namespace.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 43206477..826b0b06 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -323,7 +323,7 @@ function get_estimate( array $audience ) : ?array { // Set current site. [ 'term' => [ 'attributes.blogId.keyword' => get_current_blog_id() ] ], // Last 7 days. - [ 'range' => [ 'event_timestamp' => [ 'gte' => Utils\milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ) ] ] ], + [ 'range' => [ 'event_timestamp' => [ 'gte' => Utils\milliseconds() - ( WEEK_IN_SECONDS * 1000 ) ] ] ], // Limit event type to pageView. [ 'term' => [ 'event_type.keyword' => 'pageView' ] ], ], @@ -334,9 +334,9 @@ function get_estimate( array $audience ) : ?array { 'histogram' => [ 'histogram' => [ 'field' => 'event_timestamp', - 'interval' => 6 * 60 * 60 * 1000, // 6 hour chunks. + 'interval' => 6 * HOUR_IN_SECONDS * 1000, // 6 hour chunks. 'extended_bounds' => [ - 'min' => Utils\milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ), + 'min' => Utils\milliseconds() - ( WEEK_IN_SECONDS * 1000 ), 'max' => Utils\milliseconds(), ], ], @@ -392,7 +392,7 @@ function get_unique_enpoint_count() : ?int { // Set current site. [ 'term' => [ 'attributes.blogId.keyword' => get_current_blog_id() ] ], // Last 7 days. - [ 'range' => [ 'event_timestamp' => [ 'gte' => Utils\milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ) ] ] ], + [ 'range' => [ 'event_timestamp' => [ 'gte' => Utils\milliseconds() - ( WEEK_IN_SECONDS * 1000 ) ] ] ], // Limit event type to pageView. [ 'term' => [ 'event_type.keyword' => 'pageView' ] ], ], @@ -470,7 +470,7 @@ function get_field_data() : ?array { // Query for current site. [ 'term' => [ 'attributes.blogId.keyword' => (string) get_current_blog_id() ] ], // Last 7 days. - [ 'range' => [ 'event_timestamp' => [ 'gte' => Utils\milliseconds() - ( 7 * 24 * 60 * 60 * 1000 ) ] ] ], + [ 'range' => [ 'event_timestamp' => [ 'gte' => Utils\milliseconds() - ( WEEK_IN_SECONDS * 1000 ) ] ] ], ], ], ], From ea36ad77e41d7c6461a1f225df20c629abcf5ee5 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Thu, 2 Apr 2020 13:31:45 +0100 Subject: [PATCH 43/95] Correct misspelling of function name --- inc/audiences/namespace.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 826b0b06..4705770c 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -370,7 +370,7 @@ function get_estimate( array $audience ) : ?array { $estimate = [ 'count' => $result['aggregations']['estimate']['value'], - 'total' => get_unique_enpoint_count(), + 'total' => get_unique_endpoint_count(), 'histogram' => array_values( $histogram ), ]; @@ -384,7 +384,7 @@ function get_estimate( array $audience ) : ?array { * * @return integer|null */ -function get_unique_enpoint_count() : ?int { +function get_unique_endpoint_count() : ?int { $query = [ 'query' => [ 'bool' => [ From 2849678f17068f860bcc5951c01036f5effeaa0a Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Thu, 2 Apr 2020 13:34:48 +0100 Subject: [PATCH 44/95] Use namespace instead of functions in REST API --- inc/audiences/rest_api/namespace.php | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/inc/audiences/rest_api/namespace.php b/inc/audiences/rest_api/namespace.php index efd51a2e..45fe4f28 100644 --- a/inc/audiences/rest_api/namespace.php +++ b/inc/audiences/rest_api/namespace.php @@ -7,12 +7,7 @@ namespace Altis\Analytics\Audiences\REST_API; -use const Altis\Analytics\Audiences\POST_TYPE; - -use function Altis\Analytics\Audiences\get_audience; -use function Altis\Analytics\Audiences\get_estimate; -use function Altis\Analytics\Audiences\save_audience; - +use Altis\Analytics\Audiences; use WP_Error; use WP_Post; use WP_REST_Request; @@ -31,8 +26,8 @@ function setup() { */ function init() { // Add post type support for title in the API only. - add_post_type_support( POST_TYPE, 'title' ); - add_post_type_support( POST_TYPE, 'excerpt' ); + add_post_type_support( Audiences\POST_TYPE, 'title' ); + add_post_type_support( Audiences\POST_TYPE, 'excerpt' ); // Fetch data for available fields and possible values. register_rest_route( 'analytics/v1', 'audiences/fields', [ @@ -119,12 +114,12 @@ function init() { ] ); // Handle the audience configuration data retrieval and saving via the REST API. - register_rest_field( POST_TYPE, 'audience', [ + register_rest_field( Audiences\POST_TYPE, 'audience', [ 'get_callback' => function ( array $post ) { - return get_audience( $post['id'] ); + return Audiences\get_audience( $post['id'] ); }, 'update_callback' => function ( $value, WP_Post $post ) { - return save_audience( $post->ID, (array) $value ); + return Audiences\save_audience( $post->ID, (array) $value ); }, 'schema' => get_audience_schema(), ] ); @@ -185,7 +180,7 @@ function get_audience_schema() : array { */ function handle_estimate_request( WP_REST_Request $request ) : WP_REST_Response { $audience = $request->get_param( 'audience' ); - $estimate = get_estimate( $audience ); + $estimate = Audiences\get_estimate( $audience ); return rest_ensure_response( $estimate ); } From 94f2bac7be65caf626e9333c76cb65225c33554a Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Thu, 2 Apr 2020 13:38:05 +0100 Subject: [PATCH 45/95] Rename permissions callback to make obvious --- inc/audiences/rest_api/namespace.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/inc/audiences/rest_api/namespace.php b/inc/audiences/rest_api/namespace.php index 45fe4f28..d61afa60 100644 --- a/inc/audiences/rest_api/namespace.php +++ b/inc/audiences/rest_api/namespace.php @@ -34,7 +34,7 @@ function init() { [ 'methods' => WP_REST_Server::READABLE, 'callback' => 'Altis\\Analytics\\Audiences\\get_field_data', - 'permissions_callback' => __NAMESPACE__ . '\\permissions', + 'permissions_callback' => __NAMESPACE__ . '\\check_edit_permission', ], 'schema' => [ 'type' => 'array', @@ -83,7 +83,7 @@ function init() { [ 'methods' => WP_REST_Server::READABLE, 'callback' => __NAMESPACE__ . '\\handle_estimate_request', - 'permissions_callback' => __NAMESPACE__ . '\\permissions', + 'permissions_callback' => __NAMESPACE__ . '\\check_edit_permission', 'args' => [ 'audience' => [ 'description' => __( 'A URL encoded audience configuration JSON string', 'altis-analytics' ), @@ -228,6 +228,6 @@ function sanitize_estimate_audience( $param ) { * * @return bool */ -function permissions() : bool { +function check_edit_permission() : bool { return current_user_can( 'edit_audience' ); } From 89de63b77e82da36145415f27098b1f4a9a3df6b Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Thu, 2 Apr 2020 13:40:27 +0100 Subject: [PATCH 46/95] Reformat trigger_error calls --- inc/utils/namespace.php | 43 +++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/inc/utils/namespace.php b/inc/utils/namespace.php index 85f2a0bd..27039806 100644 --- a/inc/utils/namespace.php +++ b/inc/utils/namespace.php @@ -136,18 +136,24 @@ function query( array $query, array $params = [], string $path = '_search' ) : ? if ( wp_remote_retrieve_response_code( $response ) !== 200 || is_wp_error( $response ) ) { if ( is_wp_error( $response ) ) { - trigger_error( sprintf( - "Analytics: elasticsearch query failed:\n%s\n%s", - $url, - $response->get_error_message() - ), E_USER_WARNING ); + trigger_error( + sprintf( + "Analytics: elasticsearch query failed:\n%s\n%s", + $url, + $response->get_error_message() + ), + E_USER_WARNING + ); } else { - trigger_error( sprintf( - "Analytics: elasticsearch query failed:\n%s\n%s\n%s", - $url, - json_encode( $query ), - wp_remote_retrieve_body( $response ) - ), E_USER_WARNING ); + trigger_error( + sprintf( + "Analytics: elasticsearch query failed:\n%s\n%s\n%s", + $url, + json_encode( $query ), + wp_remote_retrieve_body( $response ) + ), + E_USER_WARNING + ); } return null; } @@ -162,12 +168,15 @@ function query( array $query, array $params = [], string $path = '_search' ) : ? // Enable logging for analytics queries. if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { - trigger_error( sprintf( - "Analytics: elasticsearch query:\n%s\n%s\n%s", - $url, - json_encode( $query ), - wp_remote_retrieve_body( $response ) - ), E_USER_NOTICE ); + trigger_error( + sprintf( + "Analytics: elasticsearch query:\n%s\n%s\n%s", + $url, + json_encode( $query ), + wp_remote_retrieve_body( $response ) + ), + E_USER_NOTICE + ); } return $result; From 4cf92c96c30371bb4dd73d0c7003553d6af1146b Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Thu, 2 Apr 2020 13:41:11 +0100 Subject: [PATCH 47/95] Break up composite boolean check --- inc/utils/namespace.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/inc/utils/namespace.php b/inc/utils/namespace.php index 27039806..fd8009ae 100644 --- a/inc/utils/namespace.php +++ b/inc/utils/namespace.php @@ -302,12 +302,10 @@ function get_field_type( string $field ) : ?string { 'session.stop_timestamp', ]; - $is_numeric = ( - in_array( $field, $numeric_fields, true ) || - stripos( $field, 'metrics' ) !== false - ); + $is_numeric_field = in_array( $field, $numeric_fields, true ); + $is_metric = stripos( $field, 'metrics' ) !== false; - if ( $is_numeric ) { + if ( $is_numeric_field || $is_metric ) { return 'number'; } From 045c281f514f263e141d231a374f2b0163501423 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Thu, 2 Apr 2020 13:43:15 +0100 Subject: [PATCH 48/95] echo sprintf( -> printf( --- inc/audiences/namespace.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 4705770c..6233696f 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -100,7 +100,7 @@ function audience_ui( WP_Post $post ) { return; } - echo sprintf( + printf( '
' . '

%s

' . '' . @@ -125,7 +125,7 @@ function estimate_ui( WP_Post $post ) { $audience = get_audience( $post->ID ); - echo sprintf( + printf( '
' . '

%s

' . '' . From 1cb5dd0887e2684be377b04a521983bd47146cb8 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Thu, 2 Apr 2020 13:45:48 +0100 Subject: [PATCH 49/95] Use PHP-style variable default --- inc/audiences/namespace.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 6233696f..1478ef04 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -236,7 +236,10 @@ function get_fields() : array { */ function register_field( string $field, string $label ) { global $altis_analytics_event_data_maps; - $altis_analytics_event_data_maps = $altis_analytics_event_data_maps ?: []; + if ( empty( $altis_analytics_event_data_maps ) ) { + $altis_analytics_event_data_maps = []; + } + $altis_analytics_event_data_maps[ $field ] = [ 'name' => $field, 'label' => $label, From 3acb1d13b19037bb54d9ce039fd59f8e9ffc5372 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Thu, 2 Apr 2020 13:50:38 +0100 Subject: [PATCH 50/95] Format arrays for one entry per line --- inc/audiences/namespace.php | 103 +++++++++++++++++++++++++++++------- 1 file changed, 85 insertions(+), 18 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 1478ef04..5818e5d7 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -324,16 +324,36 @@ function get_estimate( array $audience ) : ?array { 'bool' => [ 'filter' => [ // Set current site. - [ 'term' => [ 'attributes.blogId.keyword' => get_current_blog_id() ] ], + [ + 'term' => [ + 'attributes.blogId.keyword' => get_current_blog_id(), + ], + ], + // Last 7 days. - [ 'range' => [ 'event_timestamp' => [ 'gte' => Utils\milliseconds() - ( WEEK_IN_SECONDS * 1000 ) ] ] ], + [ + 'range' => [ + 'event_timestamp' => [ + 'gte' => Utils\milliseconds() - ( WEEK_IN_SECONDS * 1000 ), + ], + ], + ], + // Limit event type to pageView. - [ 'term' => [ 'event_type.keyword' => 'pageView' ] ], + [ + 'term' => [ + 'event_type.keyword' => 'pageView', + ], + ], ], ], ], 'aggs' => [ - 'estimate' => [ 'cardinality' => [ 'field' => 'endpoint.Id.keyword' ] ], + 'estimate' => [ + 'cardinality' => [ + 'field' => 'endpoint.Id.keyword', + ], + ], 'histogram' => [ 'histogram' => [ 'field' => 'event_timestamp', @@ -346,7 +366,9 @@ function get_estimate( array $audience ) : ?array { ], ], 'size' => 0, - 'sort' => [ 'event_timestamp' => 'desc' ], + 'sort' => [ + 'event_timestamp' => 'desc', + ], ]; // Append the groups query. @@ -393,19 +415,41 @@ function get_unique_endpoint_count() : ?int { 'bool' => [ 'filter' => [ // Set current site. - [ 'term' => [ 'attributes.blogId.keyword' => get_current_blog_id() ] ], + [ + 'term' => [ + 'attributes.blogId.keyword' => get_current_blog_id(), + ], + ], + // Last 7 days. - [ 'range' => [ 'event_timestamp' => [ 'gte' => Utils\milliseconds() - ( WEEK_IN_SECONDS * 1000 ) ] ] ], + [ + 'range' => [ + 'event_timestamp' => [ + 'gte' => Utils\milliseconds() - ( WEEK_IN_SECONDS * 1000 ), + ], + ], + ], + // Limit event type to pageView. - [ 'term' => [ 'event_type.keyword' => 'pageView' ] ], + [ + 'term' => [ + 'event_type.keyword' => 'pageView', + ], + ], ], ], ], 'aggs' => [ - 'count' => [ 'cardinality' => [ 'field' => 'endpoint.Id.keyword' ] ], + 'count' => [ + 'cardinality' => [ + 'field' => 'endpoint.Id.keyword', + ], + ], ], 'size' => 0, - 'sort' => [ 'event_timestamp' => 'desc' ], + 'sort' => [ + 'event_timestamp' => 'desc', + ], ]; $cache = wp_cache_get( 'total-uniques', 'altis-audiences' ); @@ -471,15 +515,28 @@ function get_field_data() : ?array { 'bool' => [ 'filter' => [ // Query for current site. - [ 'term' => [ 'attributes.blogId.keyword' => (string) get_current_blog_id() ] ], + [ + 'term' => [ + 'attributes.blogId.keyword' => (string) get_current_blog_id(), + ], + ], + // Last 7 days. - [ 'range' => [ 'event_timestamp' => [ 'gte' => Utils\milliseconds() - ( WEEK_IN_SECONDS * 1000 ) ] ] ], + [ + 'range' => [ + 'event_timestamp' => [ + 'gte' => Utils\milliseconds() - ( WEEK_IN_SECONDS * 1000 ), + ], + ], + ], ], ], ], 'size' => 0, 'aggs' => [], - 'sort' => [ 'event_timestamp' => 'desc' ], + 'sort' => [ + 'event_timestamp' => 'desc', + ], ]; foreach ( $maps as $map ) { @@ -581,27 +638,37 @@ function build_audience_query( array $audience ) : array { switch ( $rule['operator'] ) { case '=': $rule_query['bool']['filter'][] = [ - 'term' => [ "{$rule['field']}.keyword" => $rule['value'] ], + 'term' => [ + "{$rule['field']}.keyword" => $rule['value'], + ], ]; break; case '!=': $rule_query['bool']['must_not'][] = [ - 'term' => [ "{$rule['field']}.keyword" => $rule['value'] ], + 'term' => [ + "{$rule['field']}.keyword" => $rule['value'], + ], ]; break; case '*=': $rule_query['bool']['filter'][] = [ - 'wildcard' => [ "{$rule['field']}.keyword" => "*{$rule['value']}*" ], + 'wildcard' => [ + "{$rule['field']}.keyword" => "*{$rule['value']}*", + ], ]; break; case '!*': $rule_query['bool']['must_not'][] = [ - 'wildcard' => [ "{$rule['field']}.keyword" => "*{$rule['value']}*" ], + 'wildcard' => [ + "{$rule['field']}.keyword" => "*{$rule['value']}*", + ], ]; break; case '^=': $rule_query['bool']['filter'][] = [ - 'wildcard' => [ "{$rule['field']}.keyword" => "{$rule['value']}*" ], + 'wildcard' => [ + "{$rule['field']}.keyword" => "{$rule['value']}*", + ], ]; break; } From 31515f1126eb730588a2604860cc1c5fbde6fab4 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 17:23:12 +0100 Subject: [PATCH 51/95] Add class properties transform --- package-lock.json | 184 +++++++++++++++++++++++++++++++--------------- package.json | 1 + webpack.config.js | 1 + 3 files changed, 126 insertions(+), 60 deletions(-) diff --git a/package-lock.json b/package-lock.json index 89ab48a8..0a31c98f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -571,6 +571,95 @@ "semver": "^5.5.0" } }, + "@babel/helper-create-class-features-plugin": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz", + "integrity": "sha512-klTBDdsr+VFFqaDHm5rR69OpEQtO2Qv8ECxHS1mNhJJvaHArR6a1xTf5K/eZW7eZpJbhCx3NW1Yt/sKsLXLblg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", + "@babel/helper-split-export-declaration": "^7.8.3" + }, + "dependencies": { + "@babel/generator": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", + "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", + "dev": true, + "requires": { + "@babel/types": "^7.9.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", + "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/traverse": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/parser": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", + "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", + "dev": true + }, + "@babel/traverse": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", + "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.0", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.0", + "@babel/types": "^7.9.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", + "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "@babel/helper-create-regexp-features-plugin": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz", @@ -715,6 +804,12 @@ "@babel/types": "^7.8.3" } }, + "@babel/helper-validator-identifier": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", + "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", + "dev": true + }, "@babel/helper-wrap-function": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", @@ -761,6 +856,16 @@ "@babel/plugin-syntax-async-generators": "^7.8.0" } }, + "@babel/plugin-proposal-class-properties": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz", + "integrity": "sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, "@babel/plugin-proposal-dynamic-import": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", @@ -1473,20 +1578,17 @@ "@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", - "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", - "dev": true + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==" }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", - "dev": true + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" }, "@types/glob": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", - "dev": true, "requires": { "@types/events": "*", "@types/minimatch": "*", @@ -1496,14 +1598,12 @@ "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" }, "@types/node": { "version": "13.9.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.2.tgz", - "integrity": "sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg==", - "dev": true + "integrity": "sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg==" }, "@types/parse-json": { "version": "4.0.0", @@ -1513,20 +1613,17 @@ "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", - "dev": true + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==" }, "@types/tapable": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.5.tgz", - "integrity": "sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ==", - "dev": true + "integrity": "sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ==" }, "@types/uglify-js": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz", "integrity": "sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==", - "dev": true, "requires": { "source-map": "^0.6.1" }, @@ -1534,8 +1631,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -1543,7 +1639,6 @@ "version": "4.41.7", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.7.tgz", "integrity": "sha512-OQG9viYwO0V1NaNV7d0n79V+n6mjOV30CwgFPIfTzwmk8DHbt+C4f2aBGdCYbo3yFyYD6sjXfqqOjwkl1j+ulA==", - "dev": true, "requires": { "@types/anymatch": "*", "@types/node": "*", @@ -1556,8 +1651,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -1565,7 +1659,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.6.tgz", "integrity": "sha512-FtAWR7wR5ocJ9+nP137DV81tveD/ZgB1sadnJ/axUGM3BUVfRPx8oQNMtv3JNfTeHx3VP7cXiyfR/jmtEsVHsQ==", - "dev": true, "requires": { "@types/node": "*", "@types/source-list-map": "*", @@ -1575,8 +1668,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -1961,7 +2053,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, "requires": { "array-uniq": "^1.0.1" } @@ -1969,8 +2060,7 @@ "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" }, "array-unique": { "version": "0.3.2", @@ -2543,7 +2633,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz", "integrity": "sha512-MciirUH5r+cYLGCOL5JX/ZLzOZbVr1ot3Fw+KcvbhUb6PM+yycqd9ZhIlcigQ5gl+XhppNmw3bEFuaaMNyLj3A==", - "dev": true, "requires": { "@types/webpack": "^4.4.31", "del": "^4.1.1" @@ -2955,7 +3044,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", - "dev": true, "requires": { "@types/glob": "^7.1.1", "globby": "^6.1.0", @@ -3137,7 +3225,6 @@ "version": "1.17.4", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", - "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -3156,7 +3243,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -3885,7 +3971,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, "requires": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -4516,7 +4601,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, "requires": { "array-union": "^1.0.1", "glob": "^7.0.3", @@ -4528,8 +4612,7 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" } } }, @@ -4551,7 +4634,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -4839,8 +4921,7 @@ "is-callable": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" }, "is-data-descriptor": { "version": "0.1.4", @@ -4863,8 +4944,7 @@ "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" }, "is-descriptor": { "version": "0.1.6", @@ -4928,14 +5008,12 @@ "is-path-cwd": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==" }, "is-path-in-cwd": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "dev": true, "requires": { "is-path-inside": "^2.1.0" } @@ -4944,7 +5022,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "dev": true, "requires": { "path-is-inside": "^1.0.2" } @@ -4967,7 +5044,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, "requires": { "has": "^1.0.3" } @@ -4987,7 +5063,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -5072,7 +5147,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, "requires": { "graceful-fs": "^4.1.6" } @@ -5593,8 +5667,7 @@ "object-inspect": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" }, "object-keys": { "version": "1.1.1", @@ -5624,7 +5697,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1", @@ -5771,8 +5843,7 @@ "p-map": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" }, "p-try": { "version": "2.2.0", @@ -5862,8 +5933,7 @@ "path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" }, "path-key": { "version": "2.0.1", @@ -5917,14 +5987,12 @@ "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, "requires": { "pinkie": "^2.0.0" } @@ -6974,7 +7042,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", - "dev": true, "requires": { "define-properties": "^1.1.3", "function-bind": "^1.1.1" @@ -6984,7 +7051,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", - "dev": true, "requires": { "define-properties": "^1.1.3", "function-bind": "^1.1.1" @@ -7318,8 +7384,7 @@ "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, "unpipe": { "version": "1.0.0", @@ -7571,7 +7636,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz", "integrity": "sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ==", - "dev": true, "requires": { "fs-extra": "^7.0.0", "lodash": ">=3.5 <5", diff --git a/package.json b/package.json index 5cb018b2..b7b0cb22 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "clean-webpack-plugin": "^3.0.0" }, "devDependencies": { + "@babel/plugin-proposal-class-properties": "^7.8.3", "babel-eslint": "^10.1.0", "eslint": "^5.16.0", "eslint-config-humanmade": "^0.8.0", diff --git a/webpack.config.js b/webpack.config.js index d353f1b4..47f1c721 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -37,6 +37,7 @@ const sharedConfig = { ], plugins: [ require( '@babel/plugin-transform-runtime' ), + require( '@babel/plugin-proposal-class-properties' ), require( '@wordpress/babel-plugin-import-jsx-pragma' ), ], }, From c8c9ed562a0da7737d1105573d677fcc5052b9bd Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 17:32:15 +0100 Subject: [PATCH 52/95] Avoid mutating variables in favour of spreads --- .../edit/components/audience-editor.js | 58 ++++++++++++++----- src/audiences/edit/components/group.js | 48 +++++++++++---- 2 files changed, 79 insertions(+), 27 deletions(-) diff --git a/src/audiences/edit/components/audience-editor.js b/src/audiences/edit/components/audience-editor.js index b484c121..03fd17ba 100644 --- a/src/audiences/edit/components/audience-editor.js +++ b/src/audiences/edit/components/audience-editor.js @@ -20,17 +20,47 @@ const StyledAudienceEditor = styled.div` `; class AudienceEditor extends Component { + onChangeInclude = e => { + this.props.onChange( { + ...this.props.audience, + include: e.target.value, + } ); + } - updateAudience( audience ) { - const updatedAudience = Object.assign( {}, this.props.audience, audience ); - this.props.onChange( updatedAudience ); + onAddGroup = () => { + this.props.onChange( { + ...this.props.audience, + groups: [ + ...this.props.audience.groups, + defaultGroup, + ], + } ); } - updateGroup( groupId, group = {} ) { - const groups = this.props.audience.groups.slice(); - const newGroup = Object.assign( {}, groups[ groupId ], group ); - groups.splice( groupId, 1, newGroup ); - this.updateAudience( { groups } ); + onUpdateGroup = ( id, group = {} ) => { + const { audience } = this.props; + this.props.onChange( { + ...audience, + groups: [ + ...audience.groups.slice( 0, id ), + { + ...audience.groups[ id ], + ...group, + }, + ...audience.groups.slice( id + 1 ), + ], + } ); + } + + onRemoveGroup = id => { + const { audience } = this.props; + this.props.onChange( { + ...audience, + groups: [ + ...audience.groups.slice( 0, id ), + ...audience.groups.slice( id + 1 ), + ], + } ); } render() { @@ -43,7 +73,7 @@ class AudienceEditor extends Component { name="audience[include]" label={ __( 'groups', 'altis-analytics' ) } value={ audience.include } - onChange={ e => this.updateAudience( { include: e.target.value } ) } + onChange={ this.onChangeInclude } />
@@ -51,14 +81,10 @@ class AudienceEditor extends Component { this.updateGroup( groupId, value ) } + onChange={ value => this.onUpdateGroup( groupId, value ) } namePrefix={ `audience[groups][${ groupId }]` } canRemove={ audience.groups.length > 1 } - onRemove={ () => { - const newGroups = audience.groups.slice(); - newGroups.splice( groupId, 1 ); - this.updateAudience( { groups: newGroups } ); - } } + onRemove={ () => this.onRemoveGroup( groupId ) } { ...group } /> ) ) } @@ -67,7 +93,7 @@ class AudienceEditor extends Component { className="audience-editor__group-add" isLarge isPrimary - onClick={ () => this.updateAudience( { groups: audience.groups.concat( [ defaultGroup ] ) } ) } + onClick={ this.onAddGroup } > { __( 'Add a group', 'altis-analytics' ) } diff --git a/src/audiences/edit/components/group.js b/src/audiences/edit/components/group.js index 599c5039..33161fe2 100644 --- a/src/audiences/edit/components/group.js +++ b/src/audiences/edit/components/group.js @@ -45,11 +45,41 @@ const Group = props => { onRemove, } = props; + const onAddRule = () => { + onChange( { + rules: [ + ...rules, + defaultRule, + ], + } ); + }; + + const onChangeRule = e => { + onChange( { + include: e.target.value, + } ); + }; + + const onRemoveRule = id => { + onChange( { + rules: [ + ...rules.slice( 0, id ), + ...rules.slice( id + 1 ), + ], + } ); + }; + const updateRule = ( ruleId, rule ) => { - const newRules = rules.slice(); - const newRule = Object.assign( {}, rules[ ruleId ], rule ); - newRules.splice( ruleId, 1, newRule ); - onChange( { rules: newRules } ); + onChange( { + rules: [ + ...rules.slice( 0, ruleId ), + { + ...rules[ ruleId ], + ...rule, + }, + ...rules.slice( ruleId + 1 ), + ], + } ); }; return ( @@ -60,7 +90,7 @@ const Group = props => { label={ __( 'rules', 'altis-analytics' ) } name={ `${ namePrefix }[include]` } value={ include } - onChange={ e => onChange( { include: e.target.value } ) } + onChange={ onChangeRule } />
@@ -71,11 +101,7 @@ const Group = props => { fields={ fields } namePrefix={ `${ namePrefix }[rules][${ ruleId }]` } canRemove={ rules.length > 1 } - onRemove={ () => { - const newRules = rules.slice(); - newRules.splice( ruleId, 1 ); - onChange( { rules: newRules } ); - } } + onRemove={ () => onRemoveRule( ruleId ) } { ...rule } /> ) ) } @@ -84,7 +110,7 @@ const Group = props => { From 04aa2886a6d4014cc9545f8548b1a455c53c7665 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 17:42:27 +0100 Subject: [PATCH 53/95] Convert Group to class to match AudienceEditor --- src/audiences/edit/components/group.js | 136 +++++++++++++------------ 1 file changed, 70 insertions(+), 66 deletions(-) diff --git a/src/audiences/edit/components/group.js b/src/audiences/edit/components/group.js index 33161fe2..e1d58e02 100644 --- a/src/audiences/edit/components/group.js +++ b/src/audiences/edit/components/group.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Component } from 'react'; import styled from 'styled-components'; import Rule from './rule'; @@ -33,35 +33,25 @@ const StyledGroup = styled.div` } `; -const Group = props => { - const { - title, - onChange, - namePrefix, - include, - rules, - fields, - canRemove, - onRemove, - } = props; - - const onAddRule = () => { - onChange( { +class Group extends Component { + onAddRule = () => { + this.props.onChange( { rules: [ - ...rules, + ...this.props.rules, defaultRule, ], } ); }; - const onChangeRule = e => { - onChange( { + onChangeRule = e => { + this.props.onChange( { include: e.target.value, } ); }; - const onRemoveRule = id => { - onChange( { + onRemoveRule = id => { + const { rules } = this.props; + this.props.onChange( { rules: [ ...rules.slice( 0, id ), ...rules.slice( id + 1 ), @@ -69,8 +59,9 @@ const Group = props => { } ); }; - const updateRule = ( ruleId, rule ) => { - onChange( { + updateRule = ( ruleId, rule ) => { + const { rules } = this.props; + this.props.onChange( { rules: [ ...rules.slice( 0, ruleId ), { @@ -82,52 +73,65 @@ const Group = props => { } ); }; - return ( - -
- { title &&

{ title }

} - -
- - { rules.map( ( rule, ruleId ) => ( - updateRule( ruleId, value ) } - fields={ fields } - namePrefix={ `${ namePrefix }[rules][${ ruleId }]` } - canRemove={ rules.length > 1 } - onRemove={ () => onRemoveRule( ruleId ) } - { ...rule } - /> - ) ) } - -
- - - { canRemove && ( + render() { + const { + title, + onChange, + namePrefix, + include, + rules, + fields, + canRemove, + onRemove, + } = this.props; + + return ( + +
+ { title &&

{ title }

} + +
+ + { rules.map( ( rule, ruleId ) => ( + this.updateRule( ruleId, value ) } + fields={ fields } + namePrefix={ `${ namePrefix }[rules][${ ruleId }]` } + canRemove={ rules.length > 1 } + onRemove={ () => this.onRemoveRule( ruleId ) } + { ...rule } + /> + ) ) } + +
- ) } -
-
- ); -}; + + { canRemove && ( + + ) } +
+
+ ); + } +} export default Group; From 4baa64a857e8eefb2f38b8921e405b5141c16a6d Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 17:44:23 +0100 Subject: [PATCH 54/95] Prop-sort props --- src/audiences/edit/components/audience-editor.js | 8 ++++---- src/audiences/edit/components/group.js | 13 ++++++------- src/audiences/edit/components/rule.js | 6 +++--- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/audiences/edit/components/audience-editor.js b/src/audiences/edit/components/audience-editor.js index 03fd17ba..4a607514 100644 --- a/src/audiences/edit/components/audience-editor.js +++ b/src/audiences/edit/components/audience-editor.js @@ -70,8 +70,8 @@ class AudienceEditor extends Component {
@@ -79,11 +79,11 @@ class AudienceEditor extends Component { { audience.groups.map( ( group, groupId ) => ( this.onUpdateGroup( groupId, value ) } - namePrefix={ `audience[groups][${ groupId }]` } canRemove={ audience.groups.length > 1 } + namePrefix={ `audience[groups][${ groupId }]` } + title={ `${ __( 'Group' ) } ${ groupId + 1 }` } + onChange={ value => this.onUpdateGroup( groupId, value ) } onRemove={ () => this.onRemoveGroup( groupId ) } { ...group } /> diff --git a/src/audiences/edit/components/group.js b/src/audiences/edit/components/group.js index e1d58e02..4fb95ec6 100644 --- a/src/audiences/edit/components/group.js +++ b/src/audiences/edit/components/group.js @@ -75,13 +75,12 @@ class Group extends Component { render() { const { - title, - onChange, - namePrefix, + canRemove, include, - rules, fields, - canRemove, + namePrefix, + rules, + title, onRemove, } = this.props; @@ -100,10 +99,10 @@ class Group extends Component { { rules.map( ( rule, ruleId ) => ( this.updateRule( ruleId, value ) } + canRemove={ rules.length > 1 } fields={ fields } namePrefix={ `${ namePrefix }[rules][${ ruleId }]` } - canRemove={ rules.length > 1 } + onChange={ value => this.updateRule( ruleId, value ) } onRemove={ () => this.onRemoveRule( ruleId ) } { ...rule } /> diff --git a/src/audiences/edit/components/rule.js b/src/audiences/edit/components/rule.js index 24b84538..e487c715 100644 --- a/src/audiences/edit/components/rule.js +++ b/src/audiences/edit/components/rule.js @@ -27,12 +27,12 @@ const StyledRule = styled.div` const Rule = props => { const { - namePrefix, + canRemove, field, + namePrefix, operator, - onChange, value, - canRemove, + onChange, onRemove, } = props; From 8f851332d188844fcf69ff37fd1fb30d678e41fa Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 17:56:04 +0100 Subject: [PATCH 55/95] Add newlines before titles --- src/audiences/edit/components/estimate.js | 4 +++- src/audiences/edit/components/group.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index 03942370..d2c4aea9 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -57,7 +57,9 @@ const Estimate = props => { return ( - { title &&

{ title }

} + { title && ( +

{ title }

+ ) }
- { title &&

{ title }

} + { title && ( +

{ title }

+ ) } Date: Fri, 3 Apr 2020 17:57:00 +0100 Subject: [PATCH 56/95] Group relative imports --- src/audiences/edit/components/group.js | 1 - src/audiences/edit/index.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/audiences/edit/components/group.js b/src/audiences/edit/components/group.js index 3269493f..68a6873e 100644 --- a/src/audiences/edit/components/group.js +++ b/src/audiences/edit/components/group.js @@ -3,7 +3,6 @@ import styled from 'styled-components'; import Rule from './rule'; import SelectInclude from './select-include'; - import { defaultRule } from '../data/defaults'; const { __ } = wp.i18n; diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index c4a29dd5..1bcd0904 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -3,7 +3,6 @@ import styled from 'styled-components'; import AudienceEditor from './components/audience-editor'; import Estimate from './components/estimate'; - import { defaultPost, defaultAudience } from './data/defaults'; const { From 12351b05d009612c7632d43959dc1f00b7416a10 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 18:11:57 +0100 Subject: [PATCH 57/95] Split rule input to component --- src/audiences/edit/components/rule.js | 116 +++++++++++++++++--------- 1 file changed, 76 insertions(+), 40 deletions(-) diff --git a/src/audiences/edit/components/rule.js b/src/audiences/edit/components/rule.js index e487c715..eeede9e8 100644 --- a/src/audiences/edit/components/rule.js +++ b/src/audiences/edit/components/rule.js @@ -25,6 +25,72 @@ const StyledRule = styled.div` } `; +const RuleInput = props => { + const { + disabled, + currentField, + field, + fields, + name, + operator, + value, + onChange, + } = props; + + switch ( currentField.type ) { + case 'number': + return ( + + ); + + case 'string': + default: + switch ( operator ) { + case '=': + case '!=': + return ( + + ); + + case '*=': + case '!*': + case '^=': + return ( + + ); + + default: + return null; + } + } +}; + const Rule = props => { const { canRemove, @@ -63,46 +129,16 @@ const Rule = props => { disabled={ fields.length === 0 } /> - { ( ! currentField.type || currentField.type === 'string' ) && ( - - { [ '=', '!=' ].indexOf( operator ) >= 0 && ( - - ) } - { [ '*=', '!*', '^=' ].indexOf( operator ) >= 0 && ( - onChange( { value: e.target.value } ) } - disabled={ fields.length === 0 } - /> - ) } - - ) } - - { currentField.type === 'number' && ( - onChange( { value: e.target.value } ) } - disabled={ fields.length === 0 } - /> - ) } + onChange( { value: e.target.value } ) } + /> { canRemove && ( From 21584820887e32141c1bed4fbb3d2876419eaf97 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 18:39:49 +0100 Subject: [PATCH 66/95] Bind in the initialiser --- src/audiences/edit/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index e265e9f9..17bf23dc 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -55,8 +55,6 @@ class Edit extends Component { }; this.titleRef = createRef(); - - this.onSubmit = this.onSubmit.bind( this ); } componentDidMount() { @@ -91,7 +89,7 @@ class Edit extends Component { console.error( error, errorInfo ); } - onSubmit( event ) { + onSubmit = event => { // Clear errors. this.setState( { error: null } ); From 54b3fee29cd7cebba45a76435f2080896b775ea6 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 18:40:43 +0100 Subject: [PATCH 67/95] Move state/titleRef initialisers to class --- src/audiences/edit/index.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index 17bf23dc..949751a4 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -46,16 +46,11 @@ const StyledEdit = styled.div` `; class Edit extends Component { - constructor( props ) { - super( props ); - - this.state = { - notice: null, - error: null, - }; - - this.titleRef = createRef(); - } + state = { + notice: null, + error: null, + }; + titleRef = createRef(); componentDidMount() { // Get the form element if there is one. This is for back compat with From 42c5130b3a4c5a83472a0ad4adadaa1813e8371a Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 18:44:26 +0100 Subject: [PATCH 68/95] Replace manual title focus with autoFocus --- src/audiences/edit/index.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index 949751a4..a76fb725 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -50,7 +50,6 @@ class Edit extends Component { notice: null, error: null, }; - titleRef = createRef(); componentDidMount() { // Get the form element if there is one. This is for back compat with @@ -58,16 +57,6 @@ class Edit extends Component { if ( formElement ) { formElement.addEventListener( 'submit', this.onSubmit ); } - - // Focus the title. - this.titleRef.current.focus(); - } - - componentDidUpdate( prevProps ) { - // Focus the title after loading. - if ( prevProps.loading && ! this.props.loading ) { - this.titleRef.current.focus(); - } } componentWillUnmount() { @@ -139,6 +128,7 @@ class Edit extends Component {
Date: Fri, 3 Apr 2020 18:45:23 +0100 Subject: [PATCH 69/95] Remove redundant arrow function --- src/audiences/edit/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index a76fb725..9f112eaf 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -142,7 +142,7 @@ class Edit extends Component {
onSetAudience( value ) } + onChange={ onSetAudience } />
From d06a71340040bcd3867e31e3f8366aec21b42532 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 18:45:46 +0100 Subject: [PATCH 70/95] Remove unused import --- src/audiences/edit/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audiences/edit/index.js b/src/audiences/edit/index.js index 9f112eaf..84c51116 100644 --- a/src/audiences/edit/index.js +++ b/src/audiences/edit/index.js @@ -1,4 +1,4 @@ -import React, { Component, createRef } from 'react'; +import React, { Component } from 'react'; import styled from 'styled-components'; import AudienceEditor from './components/audience-editor'; From 5351cd3a1db5dea878582a1abb98fc32b1541b12 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 18:48:56 +0100 Subject: [PATCH 71/95] Move export default into declaration --- .../edit/components/audience-editor.js | 4 +--- src/audiences/edit/components/estimate.js | 6 ++---- src/audiences/edit/components/group.js | 4 +--- src/audiences/edit/components/pie-chart.js | 18 +++++++++--------- src/audiences/edit/components/rule.js | 6 ++---- .../edit/components/select-include.js | 6 ++---- .../edit/components/select-operator.js | 6 ++---- 7 files changed, 19 insertions(+), 31 deletions(-) diff --git a/src/audiences/edit/components/audience-editor.js b/src/audiences/edit/components/audience-editor.js index 4a607514..87ea46e9 100644 --- a/src/audiences/edit/components/audience-editor.js +++ b/src/audiences/edit/components/audience-editor.js @@ -19,7 +19,7 @@ const StyledAudienceEditor = styled.div` } `; -class AudienceEditor extends Component { +export default class AudienceEditor extends Component { onChangeInclude = e => { this.props.onChange( { ...this.props.audience, @@ -107,5 +107,3 @@ AudienceEditor.defaultProps = { fields: [], onChange: () => {}, }; - -export default AudienceEditor; diff --git a/src/audiences/edit/components/estimate.js b/src/audiences/edit/components/estimate.js index d2c4aea9..f36d6f9d 100644 --- a/src/audiences/edit/components/estimate.js +++ b/src/audiences/edit/components/estimate.js @@ -41,7 +41,7 @@ const StyledEstimate = styled.div` } `; -const Estimate = props => { +export default function Estimate( props ) { const { audience, sparkline, @@ -82,12 +82,10 @@ const Estimate = props => {
); -}; +} Estimate.defaultProps = { audience: null, sparkline: false, title: '', }; - -export default Estimate; diff --git a/src/audiences/edit/components/group.js b/src/audiences/edit/components/group.js index 68a6873e..a18afdf1 100644 --- a/src/audiences/edit/components/group.js +++ b/src/audiences/edit/components/group.js @@ -32,7 +32,7 @@ const StyledGroup = styled.div` } `; -class Group extends Component { +export default class Group extends Component { onAddRule = () => { this.props.onChange( { rules: [ @@ -133,5 +133,3 @@ class Group extends Component { ); } } - -export default Group; diff --git a/src/audiences/edit/components/pie-chart.js b/src/audiences/edit/components/pie-chart.js index 433ccee3..6c06f370 100644 --- a/src/audiences/edit/components/pie-chart.js +++ b/src/audiences/edit/components/pie-chart.js @@ -24,16 +24,16 @@ const StyledPie = styled.svg` } `; -const PieChart = props => ( - - - - { props.percent }% - -); +export default function PieChart( props ) { + return ( + + + + { props.percent }% + + ); +} PieChart.defaultProps = { percent: 0, }; - -export default PieChart; diff --git a/src/audiences/edit/components/rule.js b/src/audiences/edit/components/rule.js index 2f4486f1..d4a29e42 100644 --- a/src/audiences/edit/components/rule.js +++ b/src/audiences/edit/components/rule.js @@ -95,7 +95,7 @@ const RuleInput = props => { } }; -const Rule = props => { +export default function Rule( props ) { const { canRemove, field, @@ -165,6 +165,4 @@ const Rule = props => { ) } ); -}; - -export default Rule; +} diff --git a/src/audiences/edit/components/select-include.js b/src/audiences/edit/components/select-include.js index ca85a3f1..d6ec5364 100644 --- a/src/audiences/edit/components/select-include.js +++ b/src/audiences/edit/components/select-include.js @@ -13,17 +13,15 @@ const StyledSelect = styled.select` } `; -const SelectInclude = props => { +export default function SelectInclude( props ) { return ( ); -}; +} SelectInclude.defaultProps = { label: '', }; - -export default SelectInclude; diff --git a/src/audiences/edit/components/select-operator.js b/src/audiences/edit/components/select-operator.js index e27c67c4..2aa9a3e0 100644 --- a/src/audiences/edit/components/select-operator.js +++ b/src/audiences/edit/components/select-operator.js @@ -5,7 +5,7 @@ import { STRING_OPERATIONS, } from '../data/constants'; -const SelectOperator = props => { +export default function SelectOperator( props ) { const { type = 'string', } = props; @@ -23,6 +23,4 @@ const SelectOperator = props => { ) ) } ); -}; - -export default SelectOperator; +} From 5365adbe824cc2c9f6f625c73528b1cd7a924f12 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 18:54:24 +0100 Subject: [PATCH 72/95] Split registerStore call up for readability --- src/audiences/edit/data/index.js | 185 ++++++++++------------------- src/audiences/edit/data/reducer.js | 63 ++++++++++ 2 files changed, 128 insertions(+), 120 deletions(-) create mode 100644 src/audiences/edit/data/reducer.js diff --git a/src/audiences/edit/data/index.js b/src/audiences/edit/data/index.js index 14d67cc8..c0b6623e 100644 --- a/src/audiences/edit/data/index.js +++ b/src/audiences/edit/data/index.js @@ -2,6 +2,7 @@ import { defaultAudience, defaultPost, } from './defaults'; +import reducer from './reducer'; const { apiFetch } = wp; const { registerStore } = wp.data; @@ -23,6 +24,12 @@ if ( window.Altis.Analytics.Audiences.Current ) { INITIAL_STATE.post = window.Altis.Analytics.Audiences.Current; } +const controls = { + FETCH_FROM_API( action ) { + return apiFetch( action.options ); + }, +}; + const actions = { setFields( fields ) { return { @@ -57,128 +64,66 @@ const actions = { }, }; -export const store = registerStore( 'audience', { - initialState: INITIAL_STATE, - reducer( state, action ) { - switch ( action.type ) { - case 'SET_FIELDS': { - return { - ...state, - fields: action.fields, - }; - } - case 'ADD_ESTIMATE': { - const key = JSON.stringify( action.audience ); - if ( state.estimates[ key ] ) { - return state; - } - return { - ...state, - estimates: { - ...state.estimates, - [ key ]: action.estimate, - }, - }; - } - case 'ADD_POSTS': { - const posts = state.posts.slice(); - action.posts.forEach( post => { - if ( ! posts.filter( existing => post.id === existing.id ).length ) { - posts.push( post ); - } - } ); - if ( state.posts.length === posts.length ) { - return state; - } - return { - ...state, - posts, - }; - } - case 'SET_POST': { - let posts = state.posts.slice(); - if ( action.post.id ) { - posts = posts.map( post => { - if ( post.id === action.post.id ) { - post = { - ...post, - ...action.post, - }; - } - return post; - } ); - } - return { - ...state, - posts: action.post.id ? posts : state.posts, - post: { - ...state.post, - ...action.post, - }, - }; - } - default: { - return state; - } - } +const selectors = { + getFields( state ) { + return state.fields; }, - actions, - selectors: { - getFields( state ) { - return state.fields; - }, - getEstimate( state, audience ) { - const key = JSON.stringify( audience ); - return state.estimates[ key ] || { - count: 0, - total: 0, - histogram: new Array( 28 ).fill( { count: 1 } ), // Build empty histrogram data. - }; - }, - getPost( state ) { - return state.post; - }, - getPosts( state ) { - return state.posts; - }, + getEstimate( state, audience ) { + const key = JSON.stringify( audience ); + return state.estimates[ key ] || { + count: 0, + total: 0, + histogram: new Array( 28 ).fill( { count: 1 } ), // Build empty histrogram data. + }; + }, + getPost( state ) { + return state.post; }, - controls: { - FETCH_FROM_API( action ) { - return apiFetch( action.options ); - }, + getPosts( state ) { + return state.posts; }, - resolvers: { - * getFields() { - const fields = yield actions.fetch( { - path: 'analytics/v1/audiences/fields', - } ); - return actions.setFields( fields ); - }, - * getEstimate( audience ) { - const audienceQuery = encodeURIComponent( JSON.stringify( audience ) ); - const estimate = yield actions.fetch( { - path: `analytics/v1/audiences/estimate?audience=${ audienceQuery }`, - } ); - return actions.addEstimate( audience, estimate ); - }, - * getPost( id ) { - const post = yield actions.fetch( { - path: `wp/v2/audiences/${ id }?context=edit`, - } ); - if ( post.status === 'auto-draft' ) { - post.title.rendered = ''; - } - if ( ! post.audience ) { - post.audience = defaultAudience; - } - yield actions.addPosts( [ post ] ); - return actions.setPost( post ); - }, - * getPosts( page = 1, search = '' ) { - const posts = yield actions.fetch( { - path: `wp/v2/audiences?context=edit&page=${ page }&search=${ search }`, - } ); - return actions.addPosts( posts ); - }, +}; + +const resolvers = { + * getFields() { + const fields = yield actions.fetch( { + path: 'analytics/v1/audiences/fields', + } ); + return actions.setFields( fields ); }, + * getEstimate( audience ) { + const audienceQuery = encodeURIComponent( JSON.stringify( audience ) ); + const estimate = yield actions.fetch( { + path: `analytics/v1/audiences/estimate?audience=${ audienceQuery }`, + } ); + return actions.addEstimate( audience, estimate ); + }, + * getPost( id ) { + const post = yield actions.fetch( { + path: `wp/v2/audiences/${ id }?context=edit`, + } ); + if ( post.status === 'auto-draft' ) { + post.title.rendered = ''; + } + if ( ! post.audience ) { + post.audience = defaultAudience; + } + yield actions.addPosts( [ post ] ); + return actions.setPost( post ); + }, + * getPosts( page = 1, search = '' ) { + const posts = yield actions.fetch( { + path: `wp/v2/audiences?context=edit&page=${ page }&search=${ search }`, + } ); + return actions.addPosts( posts ); + }, +}; + +export const store = registerStore( 'audience', { + actions, + controls, + initialState: INITIAL_STATE, + reducer, + resolvers, + selectors, } ); diff --git a/src/audiences/edit/data/reducer.js b/src/audiences/edit/data/reducer.js new file mode 100644 index 00000000..108f93b5 --- /dev/null +++ b/src/audiences/edit/data/reducer.js @@ -0,0 +1,63 @@ +export default function reducer( state, action ) { + switch ( action.type ) { + case 'SET_FIELDS': { + return { + ...state, + fields: action.fields, + }; + } + case 'ADD_ESTIMATE': { + const key = JSON.stringify( action.audience ); + if ( state.estimates[ key ] ) { + return state; + } + return { + ...state, + estimates: { + ...state.estimates, + [ key ]: action.estimate, + }, + }; + } + case 'ADD_POSTS': { + const posts = state.posts.slice(); + action.posts.forEach( post => { + if ( ! posts.filter( existing => post.id === existing.id ).length ) { + posts.push( post ); + } + } ); + if ( state.posts.length === posts.length ) { + return state; + } + return { + ...state, + posts, + }; + } + case 'SET_POST': { + let posts = state.posts.slice(); + if ( action.post.id ) { + posts = posts.map( post => { + if ( post.id === action.post.id ) { + post = { + ...post, + ...action.post, + }; + } + return post; + } ); + } + return { + ...state, + posts: action.post.id ? posts : state.posts, + post: { + ...state.post, + ...action.post, + }, + }; + } + default: { + return state; + } + } +} From f31bcca5296c8d0009cf68091440520f15f6f7c7 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 18:56:25 +0100 Subject: [PATCH 73/95] Rename and reformat initialState --- src/audiences/edit/data/index.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/audiences/edit/data/index.js b/src/audiences/edit/data/index.js index c0b6623e..2979be6c 100644 --- a/src/audiences/edit/data/index.js +++ b/src/audiences/edit/data/index.js @@ -7,21 +7,21 @@ import reducer from './reducer'; const { apiFetch } = wp; const { registerStore } = wp.data; -const INITIAL_STATE = { - posts: [], - pagination: {}, - fields: [], +const initialState = { estimates: {}, + fields: [], + pagination: {}, post: defaultPost, + posts: [], }; // Hydrate from server side. if ( window.Altis.Analytics.Audiences.Fields ) { - INITIAL_STATE.fields = window.Altis.Analytics.Audiences.Fields; + initialState.fields = window.Altis.Analytics.Audiences.Fields; } if ( window.Altis.Analytics.Audiences.Current ) { - INITIAL_STATE.posts.push( window.Altis.Analytics.Audiences.Current ); - INITIAL_STATE.post = window.Altis.Analytics.Audiences.Current; + initialState.posts.push( window.Altis.Analytics.Audiences.Current ); + initialState.post = window.Altis.Analytics.Audiences.Current; } const controls = { @@ -122,7 +122,7 @@ const resolvers = { export const store = registerStore( 'audience', { actions, controls, - initialState: INITIAL_STATE, + initialState, reducer, resolvers, selectors, From 7a4a1e224e70c728fa6e1f3359039bb6387b5c7b Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 19:01:58 +0100 Subject: [PATCH 74/95] Avoid unnecessary slice --- src/audiences/edit/data/reducer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audiences/edit/data/reducer.js b/src/audiences/edit/data/reducer.js index 108f93b5..a9ccf7eb 100644 --- a/src/audiences/edit/data/reducer.js +++ b/src/audiences/edit/data/reducer.js @@ -35,7 +35,7 @@ export default function reducer( state, action ) { }; } case 'SET_POST': { - let posts = state.posts.slice(); + let posts = state.posts; if ( action.post.id ) { posts = posts.map( post => { if ( post.id === action.post.id ) { From 38e1afaa5d5415b3daa89d05a55902d4430796d0 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 19:15:39 +0100 Subject: [PATCH 75/95] Use a standard else --- webpack.config.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 47f1c721..665e4af7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -74,9 +74,7 @@ if ( mode === 'production' ) { hashFuncNames: [ 'sha384' ], enabled: true, } ) ); -} - -if ( mode !== 'production' ) { +} else { sharedConfig.devtool = 'cheap-module-eval-source-map'; } From 56f632aa7a08353b158aa544c1b689477aaf6c9b Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 19:19:35 +0100 Subject: [PATCH 76/95] Refactor some of the reducer --- inc/audiences/namespace.php | 1 + src/audiences/edit/data/reducer.js | 46 ++++++++++++++++-------------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 5818e5d7..fe55a423 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -262,6 +262,7 @@ function admin_enqueue_scripts() { 'altis-analytics-audience-ui', Utils\get_asset_url( 'audiences.js' ), [ + 'lodash', 'react', 'react-dom', 'wp-i18n', diff --git a/src/audiences/edit/data/reducer.js b/src/audiences/edit/data/reducer.js index a9ccf7eb..83bccdc6 100644 --- a/src/audiences/edit/data/reducer.js +++ b/src/audiences/edit/data/reducer.js @@ -1,3 +1,5 @@ +import { unionBy } from 'lodash'; + export default function reducer( state, action ) { switch ( action.type ) { case 'SET_FIELDS': { @@ -20,36 +22,36 @@ export default function reducer( state, action ) { }; } case 'ADD_POSTS': { - const posts = state.posts.slice(); - action.posts.forEach( post => { - if ( ! posts.filter( existing => post.id === existing.id ).length ) { - posts.push( post ); - } - } ); - if ( state.posts.length === posts.length ) { - return state; - } return { ...state, - posts, + posts: unionBy( [ state.posts, action.posts ], post => post.id ), }; } case 'SET_POST': { - let posts = state.posts; - if ( action.post.id ) { - posts = posts.map( post => { - if ( post.id === action.post.id ) { - post = { - ...post, - ...action.post, - }; - } - return post; - } ); + if ( ! action.post.id ) { + return { + ...state, + post: { + ...state.post, + ...action.post, + }, + }; } + + const posts = state.posts.map( post => { + if ( post.id !== action.post.id ) { + return post; + } + + return { + ...post, + ...action.post, + }; + } ); + return { ...state, - posts: action.post.id ? posts : state.posts, + posts, post: { ...state.post, ...action.post, From 1484bfb0b0106ec899b3ca1d0e4501fe1357844f Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 19:23:02 +0100 Subject: [PATCH 77/95] Get spacey with it --- src/audiences/edit/data/reducer.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/audiences/edit/data/reducer.js b/src/audiences/edit/data/reducer.js index 83bccdc6..474cdbbb 100644 --- a/src/audiences/edit/data/reducer.js +++ b/src/audiences/edit/data/reducer.js @@ -8,6 +8,7 @@ export default function reducer( state, action ) { fields: action.fields, }; } + case 'ADD_ESTIMATE': { const key = JSON.stringify( action.audience ); if ( state.estimates[ key ] ) { @@ -21,12 +22,14 @@ export default function reducer( state, action ) { }, }; } + case 'ADD_POSTS': { return { ...state, posts: unionBy( [ state.posts, action.posts ], post => post.id ), }; } + case 'SET_POST': { if ( ! action.post.id ) { return { @@ -58,6 +61,7 @@ export default function reducer( state, action ) { }, }; } + default: { return state; } From 0ef26eadd9956bdcd2f22786550a2250254f87a8 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 3 Apr 2020 19:23:29 +0100 Subject: [PATCH 78/95] Conjoin generator symbol with function name --- src/audiences/edit/data/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/audiences/edit/data/index.js b/src/audiences/edit/data/index.js index 2979be6c..8506bf18 100644 --- a/src/audiences/edit/data/index.js +++ b/src/audiences/edit/data/index.js @@ -85,20 +85,20 @@ const selectors = { }; const resolvers = { - * getFields() { + *getFields() { const fields = yield actions.fetch( { path: 'analytics/v1/audiences/fields', } ); return actions.setFields( fields ); }, - * getEstimate( audience ) { + *getEstimate( audience ) { const audienceQuery = encodeURIComponent( JSON.stringify( audience ) ); const estimate = yield actions.fetch( { path: `analytics/v1/audiences/estimate?audience=${ audienceQuery }`, } ); return actions.addEstimate( audience, estimate ); }, - * getPost( id ) { + *getPost( id ) { const post = yield actions.fetch( { path: `wp/v2/audiences/${ id }?context=edit`, } ); @@ -111,7 +111,7 @@ const resolvers = { yield actions.addPosts( [ post ] ); return actions.setPost( post ); }, - * getPosts( page = 1, search = '' ) { + *getPosts( page = 1, search = '' ) { const posts = yield actions.fetch( { path: `wp/v2/audiences?context=edit&page=${ page }&search=${ search }`, } ); From 8699e780e6a4854734767dcd36b03f1a372fd01b Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 11:46:33 +0100 Subject: [PATCH 79/95] Use helpers instead of using globals directly --- inc/audiences/namespace.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index fe55a423..25dc9c4f 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -45,8 +45,7 @@ function register_post_type() { 'active' => [ 'title' => __( 'Status', 'altis-analytics' ), 'function' => function () { - $post = $GLOBALS['post']; - if ( $post->post_status === 'publish' ) { + if ( get_post_status() === 'publish' ) { esc_html_e( 'Active', 'altis-analytics' ); } else { esc_html_e( 'Inactive', 'altis-analytics' ); @@ -56,7 +55,7 @@ function register_post_type() { 'estimate' => [ 'title' => __( 'Size', 'altis-analytics' ), 'function' => function () { - estimate_ui( $GLOBALS['post'] ); + estimate_ui( get_post() ); }, ], 'last_modified' => [ From 90a6c96793c7ed3d57b5c0603ec76c203468d009 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 11:49:51 +0100 Subject: [PATCH 80/95] Unnest inline functions --- inc/audiences/namespace.php | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 25dc9c4f..e840b79b 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -44,19 +44,11 @@ function register_post_type() { 'admin_cols' => [ 'active' => [ 'title' => __( 'Status', 'altis-analytics' ), - 'function' => function () { - if ( get_post_status() === 'publish' ) { - esc_html_e( 'Active', 'altis-analytics' ); - } else { - esc_html_e( 'Inactive', 'altis-analytics' ); - } - }, + 'function' => __NAMESPACE__ . '\\render_status_column', ], 'estimate' => [ 'title' => __( 'Size', 'altis-analytics' ), - 'function' => function () { - estimate_ui( get_post() ); - }, + 'function' => __NAMESPACE__ . '\\estimate_ui', ], 'last_modified' => [ 'title' => __( 'Last Modified', 'altis-analytics' ), @@ -89,6 +81,17 @@ function meta_boxes() { remove_meta_box( 'slugdiv', POST_TYPE, 'normal' ); } +/** + * Render the "Status" column for an audience. + */ +function render_status_column() : void { + if ( get_post_status() === 'publish' ) { + esc_html_e( 'Active', 'altis-analytics' ); + } else { + esc_html_e( 'Inactive', 'altis-analytics' ); + } +} + /** * Add Audience UI placeholder. * @@ -117,7 +120,12 @@ function audience_ui( WP_Post $post ) { * * @param WP_Post $post */ -function estimate_ui( WP_Post $post ) { +function estimate_ui( WP_Post $post = null ) { + // Use current post if none passed. + if ( ! $post ) { + $post = get_post(); + } + if ( $post->post_type !== POST_TYPE ) { return; } From 1ce7a17853a454438515ebee2870b33b774d0538 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 11:58:22 +0100 Subject: [PATCH 81/95] Always slash meta values --- inc/audiences/namespace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index e840b79b..82a80707 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -180,7 +180,7 @@ function save_audience( int $post_id, array $audience ) : bool { $valid = rest_validate_value_from_schema( $audience, REST_API\get_audience_schema(), 'audience' ); if ( is_wp_error( $valid ) ) { - update_post_meta( $post_id, 'audience_error', $valid->get_error_message() ); + update_post_meta( $post_id, 'audience_error', wp_slash( $valid->get_error_message() ) ); return false; } From e61a8224ea16941cac6f9956639c85f6a7164a97 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 11:58:33 +0100 Subject: [PATCH 82/95] Indent comments with 4 spaces, not 2 --- inc/audiences/namespace.php | 54 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 82a80707..910d3c90 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -485,29 +485,29 @@ function get_unique_endpoint_count() : ?int { * Example results: * * [ - * [ - * 'name' => 'endpoint.Location.Country', - * 'label' => 'Country', - * 'type' => 'string', - * 'data' => [ - * [ 'key' => 'GB', 'doc_count' => 281 ], - * [ 'key' => 'US', 'doc_count' => 127 ] + * [ + * 'name' => 'endpoint.Location.Country', + * 'label' => 'Country', + * 'type' => 'string', + * 'data' => [ + * [ 'key' => 'GB', 'doc_count' => 281 ], + * [ 'key' => 'US', 'doc_count' => 127 ] + * ] + * ], + * [ + * 'name' => 'metrics.UserSpend', + * 'label' => 'Total User Spend', + * 'type' => 'number', + * 'stats' => [ + * 'sum' => 560, + * 'min' => 10, + * 'max' => 210, + * 'avg' => 35 + * ] * ] - * ], - * [ - * 'name' => 'metrics.UserSpend', - * 'label' => 'Total User Spend', - * 'type' => 'number', - * 'stats' => [ - * 'sum' => 560, - * 'min' => 10, - * 'max' => 210, - * 'avg' => 35 - * ] - * ] * ] * - * @return ?array + * @return array|null */ function get_field_data() : ?array { $maps = get_fields(); @@ -603,13 +603,13 @@ function get_field_data() : ?array { * The response is designed to be used within a bool filter query eg: * * $query = [ - * 'query' => [ - * 'bool' => [ - * 'filter' => [ - * get_filter_query( $audience ), - * ] - * ] - * ] + * 'query' => [ + * 'bool' => [ + * 'filter' => [ + * get_filter_query( $audience ), + * ], + * ], + * ], * ]; * * @param array $audience From f1cdce5add08c0c15df1afda43b9e943f63303c5 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 12:01:28 +0100 Subject: [PATCH 83/95] Move comparison operators to constant --- inc/audiences/namespace.php | 11 +++++++++++ inc/audiences/rest_api/namespace.php | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 910d3c90..063d36f6 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -10,6 +10,17 @@ use Altis\Analytics\Utils; use WP_Post; +const COMPARISON_OPERATORS = [ + '=', + '!=', + '*=', + '!*', + '^=', + 'gte', + 'lte', + 'gt', + 'lt', +]; const POST_TYPE = 'audience'; function setup() { diff --git a/inc/audiences/rest_api/namespace.php b/inc/audiences/rest_api/namespace.php index d61afa60..dc78df4b 100644 --- a/inc/audiences/rest_api/namespace.php +++ b/inc/audiences/rest_api/namespace.php @@ -157,7 +157,7 @@ function get_audience_schema() : array { ], 'operator' => [ 'type' => 'string', - 'enum' => [ '=', '!=', '*=', '!*', '^=', 'gte', 'lte', 'gt', 'lt' ], + 'enum' => Audiences\COMPARISON_OPERATORS, ], 'value' => [ 'type' => [ 'string', 'number' ], From 74ed6073d2cc114bdc02e0df9044f64a7d4d32fe Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 12:02:25 +0100 Subject: [PATCH 84/95] Reformat schema --- inc/audiences/rest_api/namespace.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/inc/audiences/rest_api/namespace.php b/inc/audiences/rest_api/namespace.php index dc78df4b..9eedce02 100644 --- a/inc/audiences/rest_api/namespace.php +++ b/inc/audiences/rest_api/namespace.php @@ -136,7 +136,11 @@ function get_audience_schema() : array { 'properties' => [ 'include' => [ 'type' => 'string', - 'enum' => [ 'any', 'all', 'none' ], + 'enum' => [ + 'any', + 'all', + 'none', + ], ], 'groups' => [ 'type' => 'array', @@ -145,7 +149,11 @@ function get_audience_schema() : array { 'properties' => [ 'include' => [ 'type' => 'string', - 'enum' => [ 'any', 'all', 'none' ], + 'enum' => [ + 'any', + 'all', + 'none', + ], ], 'rules' => [ 'type' => 'array', @@ -160,7 +168,10 @@ function get_audience_schema() : array { 'enum' => Audiences\COMPARISON_OPERATORS, ], 'value' => [ - 'type' => [ 'string', 'number' ], + 'type' => [ + 'string', + 'number', + ], ], ], ], From 70e3422851c7c55124e7ac60fcacbe95c254a8cb Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 12:24:49 +0100 Subject: [PATCH 85/95] Use "set up" for the verb form --- inc/audiences/namespace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 063d36f6..75b594a5 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -33,7 +33,7 @@ function setup() { add_filter( 'post_row_actions', __NAMESPACE__ . '\\remove_quick_edit', 10, 2 ); add_filter( 'bulk_actions-edit-' . POST_TYPE, __NAMESPACE__ . '\\remove_bulk_actions' ); - // Setup Audience REST API. + // Set up Audience REST API. add_action( 'rest_api_init', __NAMESPACE__ . '\\REST_API\\init' ); } From a53e14aefdd529cf776ece04053399f33cca0884 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 12:52:32 +0100 Subject: [PATCH 86/95] Switch from array_map to foreach --- inc/audiences/namespace.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 75b594a5..30d6f39b 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -587,7 +587,8 @@ function get_field_data() : ?array { $aggregations = $result['aggregations']; // Normalise aggregations to useful just the useful data. - $fields = array_map( function ( array $field ) use ( $aggregations ) { + $fields = []; + foreach ( $maps as $field ) { if ( isset( $aggregations[ $field['name'] ]['buckets'] ) ) { $field['data'] = array_map( function ( $bucket ) { return [ @@ -598,10 +599,9 @@ function get_field_data() : ?array { } else { $field['stats'] = $aggregations[ $field['name'] ]; } - return $field; - }, $maps ); - $fields = array_values( $fields ); + $fields[] = $field; + } // Cache the data. wp_cache_set( $key, $fields, 'altis-audiences', HOUR_IN_SECONDS ); From 609d1838e002d662830f5197b18f825a0b31e75d Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 13:11:38 +0100 Subject: [PATCH 87/95] Add title/excerpt support to audiences Rather than removing and hacking back into the REST API, we can do the minimal changes needed to adjust the UI. --- inc/audiences/namespace.php | 35 ++++++++++++++++++++++------ inc/audiences/rest_api/namespace.php | 4 ---- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 30d6f39b..ee52f4f0 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -28,10 +28,11 @@ function setup() { add_action( 'init', __NAMESPACE__ . '\\register_default_event_data_maps' ); add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\\admin_enqueue_scripts' ); add_action( 'edit_form_top', __NAMESPACE__ . '\\audience_ui' ); - add_action( 'add_meta_boxes_' . POST_TYPE, __NAMESPACE__ . '\\meta_boxes' ); + add_action( 'add_meta_boxes_' . POST_TYPE, __NAMESPACE__ . '\\adjust_meta_boxes' ); add_action( 'save_post_' . POST_TYPE, __NAMESPACE__ . '\\save_post', 10, 2 ); add_filter( 'post_row_actions', __NAMESPACE__ . '\\remove_quick_edit', 10, 2 ); add_filter( 'bulk_actions-edit-' . POST_TYPE, __NAMESPACE__ . '\\remove_bulk_actions' ); + add_action( 'edit_form_top', __NAMESPACE__ . '\\hide_title_field' ); // Set up Audience REST API. add_action( 'rest_api_init', __NAMESPACE__ . '\\REST_API\\init' ); @@ -46,7 +47,10 @@ function register_post_type() { [ 'public' => false, 'show_ui' => true, - 'supports' => false, + 'supports' => [ + 'title', + 'excerpt', + ], 'menu_icon' => 'dashicons-groups', 'menu_position' => 151, 'show_in_rest' => true, @@ -87,9 +91,30 @@ function register_default_event_data_maps() { register_field( 'endpoint.Location.Country', __( 'Country', 'altis-analytics' ) ); } -function meta_boxes() { +function adjust_meta_boxes() { remove_meta_box( 'submitdiv', POST_TYPE, 'side' ); remove_meta_box( 'slugdiv', POST_TYPE, 'normal' ); + remove_meta_box( 'postexcerpt', POST_TYPE, 'normal' ); +} + +/** + * Temporarily hide the title field. + * + * Removes post type support on the edit screen temporarily, then readds as + * soon as the UI no longer cares. + */ +function hide_title_field( WP_Post $post ) { + if ( $post->post_type !== POST_TYPE ) { + return; + } + + remove_post_type_support( POST_TYPE, 'title' ); + + $callback = function () use ( &$callback ) { + add_post_type_support( POST_TYPE, 'title' ); + remove_action( 'edit_form_after_title', $callback ); + }; + add_action( 'edit_form_after_title', $callback ); } /** @@ -304,10 +329,6 @@ function admin_enqueue_scripts() { if ( isset( $_GET['post'] ) ) { $response = rest_do_request( sprintf( '/wp/v2/audiences/%d', $_GET['post'] ) ); $data['Current'] = $response->get_data(); - // Calling the rest function triggers the `rest_api_init` action - // where we reinstate title support. It's removed to provide a clean - // legacy postedit screen. - remove_post_type_support( POST_TYPE, 'title' ); } wp_add_inline_script( diff --git a/inc/audiences/rest_api/namespace.php b/inc/audiences/rest_api/namespace.php index 9eedce02..301c6727 100644 --- a/inc/audiences/rest_api/namespace.php +++ b/inc/audiences/rest_api/namespace.php @@ -25,10 +25,6 @@ function setup() { * Register audience API endpoints. */ function init() { - // Add post type support for title in the API only. - add_post_type_support( Audiences\POST_TYPE, 'title' ); - add_post_type_support( Audiences\POST_TYPE, 'excerpt' ); - // Fetch data for available fields and possible values. register_rest_route( 'analytics/v1', 'audiences/fields', [ [ From ec26fea5314505d3bb92c15edb4e4b917b2c69cd Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 13:58:13 +0100 Subject: [PATCH 88/95] Break out schemas for readability --- inc/audiences/rest_api/namespace.php | 88 +++++++++++++++++----------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/inc/audiences/rest_api/namespace.php b/inc/audiences/rest_api/namespace.php index 301c6727..8dae6b30 100644 --- a/inc/audiences/rest_api/namespace.php +++ b/inc/audiences/rest_api/namespace.php @@ -122,7 +122,58 @@ function init() { } /** - * Returns the audience configuration JSON schema. + * Get the JSON schema for the rule object. + * + * @return array + */ +function get_rule_schema() : array { + return [ + 'type' => 'object', + 'properties' => [ + 'field' => [ + 'type' => 'string', + ], + 'operator' => [ + 'type' => 'string', + 'enum' => Audiences\COMPARISON_OPERATORS, + ], + 'value' => [ + 'type' => [ + 'string', + 'number', + ], + ], + ], + ]; +} + +/** + * Get the JSON schema for the group object. + * + * @return array + */ +function get_group_schema() : array { + return [ + 'type' => 'object', + 'properties' => [ + 'include' => [ + 'type' => 'string', + 'enum' => [ + 'any', + 'all', + 'none', + ], + ], + 'rules' => [ + 'type' => 'array', + 'items' => get_rule_schema(), + ], + ], + ]; +} + +/** + * Get the JSON schema for the audience configuration object. * * @return array */ @@ -140,40 +191,7 @@ function get_audience_schema() : array { ], 'groups' => [ 'type' => 'array', - 'items' => [ - 'type' => 'object', - 'properties' => [ - 'include' => [ - 'type' => 'string', - 'enum' => [ - 'any', - 'all', - 'none', - ], - ], - 'rules' => [ - 'type' => 'array', - 'items' => [ - 'type' => 'object', - 'properties' => [ - 'field' => [ - 'type' => 'string', - ], - 'operator' => [ - 'type' => 'string', - 'enum' => Audiences\COMPARISON_OPERATORS, - ], - 'value' => [ - 'type' => [ - 'string', - 'number', - ], - ], - ], - ], - ], - ], - ], + 'items' => get_group_schema(), ], ], ]; From 5c9f471b2ecb25315a2b03a40624c76b7d57349e Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 14:00:31 +0100 Subject: [PATCH 89/95] Translate JSON error messages --- inc/audiences/rest_api/namespace.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/inc/audiences/rest_api/namespace.php b/inc/audiences/rest_api/namespace.php index 8dae6b30..dc6411e6 100644 --- a/inc/audiences/rest_api/namespace.php +++ b/inc/audiences/rest_api/namespace.php @@ -221,7 +221,10 @@ function validate_estimate_audience( $param ) { if ( json_last_error() !== JSON_ERROR_NONE ) { return new WP_Error( 'altis_audience_estimate_json_invalid', - 'Could not decode JSON: ' . json_last_error_msg() + sprintf( + __( 'Could not decode JSON: %s', 'altis-analytics' ), + json_last_error_msg() + ) ); } @@ -241,7 +244,10 @@ function sanitize_estimate_audience( $param ) { if ( json_last_error() !== JSON_ERROR_NONE ) { return new WP_Error( 'altis_audience_estimate_json_invalid', - 'Could not decode JSON: ' . json_last_error_msg() + sprintf( + __( 'Could not decode JSON: %s', 'altis-analytics' ), + json_last_error_msg() + ) ); } From 00b54eb37dc71ffe2810722cf0fd575691df4116 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 14:01:48 +0100 Subject: [PATCH 90/95] Add additional phpdoc --- inc/audiences/namespace.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index ee52f4f0..444c2bc3 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -23,6 +23,9 @@ ]; const POST_TYPE = 'audience'; +/** + * Set up the Audiences UI and backend. + */ function setup() { add_action( 'init', __NAMESPACE__ . '\\register_post_type' ); add_action( 'init', __NAMESPACE__ . '\\register_default_event_data_maps' ); @@ -39,7 +42,7 @@ function setup() { } /** - * Setup the audiences data store. + * Set up the audiences data store. */ function register_post_type() { register_extended_post_type( @@ -91,6 +94,9 @@ function register_default_event_data_maps() { register_field( 'endpoint.Location.Country', __( 'Country', 'altis-analytics' ) ); } +/** + * Remove built-in metaboxes from the Audiences edit page. + */ function adjust_meta_boxes() { remove_meta_box( 'submitdiv', POST_TYPE, 'side' ); remove_meta_box( 'slugdiv', POST_TYPE, 'normal' ); From 2a2bdb193b8d0a7aa7c1b48a467a8d9b4a81a167 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 14:06:35 +0100 Subject: [PATCH 91/95] Only log queries on ALTIS_ANALYTICS_LOG_QUERIES Also, switch from trigger_error to error_log --- README.md | 4 ++++ inc/utils/namespace.php | 7 +++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c4decb1c..368664f5 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,10 @@ Similar to `registerAttribute()` above but for metrics. Allows you to define the Elasticsearch server URL directly. +**`ALTIS_ANALYTICS_LOG_QUERIES`** + +Define as true to enable logging queries to the error log. + ### Filters The plugin provides a few hooks for you to control the default endpoint data and attributes recorded with events. diff --git a/inc/utils/namespace.php b/inc/utils/namespace.php index fd8009ae..bc26dd9d 100644 --- a/inc/utils/namespace.php +++ b/inc/utils/namespace.php @@ -167,15 +167,14 @@ function query( array $query, array $params = [], string $path = '_search' ) : ? } // Enable logging for analytics queries. - if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { - trigger_error( + if ( defined( 'ALTIS_ANALYTICS_LOG_QUERIES' ) && ALTIS_ANALYTICS_LOG_QUERIES ) { + error_log( sprintf( "Analytics: elasticsearch query:\n%s\n%s\n%s", $url, json_encode( $query ), wp_remote_retrieve_body( $response ) - ), - E_USER_NOTICE + ) ); } From 668b1e0c2ddebfe55825257f67c7a34fe43d6fda Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 14:08:45 +0100 Subject: [PATCH 92/95] Avoid double array indexing --- inc/audiences/namespace.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 444c2bc3..746a06f9 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -616,15 +616,16 @@ function get_field_data() : ?array { // Normalise aggregations to useful just the useful data. $fields = []; foreach ( $maps as $field ) { - if ( isset( $aggregations[ $field['name'] ]['buckets'] ) ) { + $field_name = $field['name']; + if ( isset( $aggregations[ $field_name ]['buckets'] ) ) { $field['data'] = array_map( function ( $bucket ) { return [ 'value' => $bucket['key'], 'count' => $bucket['doc_count'], ]; - }, $aggregations[ $field['name'] ]['buckets'] ); + }, $aggregations[ $field_name ]['buckets'] ); } else { - $field['stats'] = $aggregations[ $field['name'] ]; + $field['stats'] = $aggregations[ $field_name ]; } $fields[] = $field; From 8d715cc0392bb34fa58efd9233d370431fc618b3 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 14:17:28 +0100 Subject: [PATCH 93/95] Add nonce check for audience updating --- inc/audiences/namespace.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/inc/audiences/namespace.php b/inc/audiences/namespace.php index 746a06f9..4aca3d46 100644 --- a/inc/audiences/namespace.php +++ b/inc/audiences/namespace.php @@ -155,6 +155,8 @@ function audience_ui( WP_Post $post ) { esc_html__( 'Loading...', 'altis-analytics' ), esc_html__( 'Javascript is required to use the audience editor.', 'altis-analytics' ) ); + + wp_nonce_field( 'altis-analytics', 'altis_analytics_nonce' ); } /** @@ -192,6 +194,14 @@ function estimate_ui( WP_Post $post = null ) { * @param int $post_id The current audience post ID. */ function save_post( $post_id ) { + if ( ! isset( $_POST['altis_analytics_nonce'] ) ) { + return; + } + + if ( ! wp_verify_nonce( $_POST['altis_analytics_nonce'], 'altis-analytics' ) ) { + return; + } + if ( ! isset( $_POST['audience'] ) ) { return; } From c7f6e5f11167cc629a2be4f5a79344423887d69b Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 14:32:17 +0100 Subject: [PATCH 94/95] Correct permission check for audience endpoints --- inc/audiences/rest_api/namespace.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/inc/audiences/rest_api/namespace.php b/inc/audiences/rest_api/namespace.php index dc6411e6..7e592c81 100644 --- a/inc/audiences/rest_api/namespace.php +++ b/inc/audiences/rest_api/namespace.php @@ -30,7 +30,7 @@ function init() { [ 'methods' => WP_REST_Server::READABLE, 'callback' => 'Altis\\Analytics\\Audiences\\get_field_data', - 'permissions_callback' => __NAMESPACE__ . '\\check_edit_permission', + 'permission_callback' => __NAMESPACE__ . '\\check_edit_permission', ], 'schema' => [ 'type' => 'array', @@ -79,7 +79,7 @@ function init() { [ 'methods' => WP_REST_Server::READABLE, 'callback' => __NAMESPACE__ . '\\handle_estimate_request', - 'permissions_callback' => __NAMESPACE__ . '\\check_edit_permission', + 'permission_callback' => __NAMESPACE__ . '\\check_edit_permission', 'args' => [ 'audience' => [ 'description' => __( 'A URL encoded audience configuration JSON string', 'altis-analytics' ), @@ -260,5 +260,6 @@ function sanitize_estimate_audience( $param ) { * @return bool */ function check_edit_permission() : bool { - return current_user_can( 'edit_audience' ); + $type = get_post_type_object( Audiences\POST_TYPE ); + return current_user_can( $type->cap->edit_posts ); } From a9a237678dc397cbe9384ac1c5853e6df6323d1d Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 4 May 2020 14:37:49 +0100 Subject: [PATCH 95/95] Add translator comments --- inc/audiences/rest_api/namespace.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inc/audiences/rest_api/namespace.php b/inc/audiences/rest_api/namespace.php index 7e592c81..bf814998 100644 --- a/inc/audiences/rest_api/namespace.php +++ b/inc/audiences/rest_api/namespace.php @@ -222,6 +222,7 @@ function validate_estimate_audience( $param ) { return new WP_Error( 'altis_audience_estimate_json_invalid', sprintf( + /* translators: %s: JSON error message */ __( 'Could not decode JSON: %s', 'altis-analytics' ), json_last_error_msg() ) @@ -245,6 +246,7 @@ function sanitize_estimate_audience( $param ) { return new WP_Error( 'altis_audience_estimate_json_invalid', sprintf( + /* translators: %s: JSON error message */ __( 'Could not decode JSON: %s', 'altis-analytics' ), json_last_error_msg() )