03 октября 2023

Расширение Woocommerce Analytics

7 минут

Woocommerce Analytics — это инструмент отчетности и анализа данных, который отображает статистику в виде таблиц и графиков и однажды появилась задача расширить функционал данного инструмента. К сожалению в интернете очень мало информации на текущий момент по возможности взаимодействия с данным функционалом, что побудило меня написать данную статью.

Формат

Чтобы начать работать с Woocommerce Analytics нужно создать расширение для Woocommerce, тут есть инструкция, но мы все же пройдемся по основным моментам чтобы было понятнее.
Располагается расширение точно так же в папке /wp-content/plugins/ и имеет такую структуру:

Самое любопытное то что создается расширение при помощи репозитория Woocommerce admin для этого клонируем данный репозиторий в папку /wp-content/plugins/ командой 

git clone git@github.com:woocommerce/woocommerce-admin.git

После чего переходим в папку с только что склонированным проектом cd woocommerce-admin, устанавливаем node зависимости и собираем проект

npm install

npm run build

После сборки для создания расширения используем команду

npm run create-wc-extension

После чего можете зайти в папку со своим созданным расширением запустите установку node зависимостей и сборку

npm install

npm start

и увидите следующие файлы:

Как вы можете видеть пакет Woocommerce admin создает все что нужно для создания нового расширения под woocommerce что крайне удобно.

Пример 1

Далее действуем по обычной схеме для плагинов — в основном php файле нашего расширения используя хук admin_enqueue_scripts подключаем файлы скриптов и стилей которые будет собирать наш сборщик:

function add_extension_register_script() {
	if ( ! class_exists( 'Automattic\WooCommerce\Admin\Loader' ) || ! \Automattic\WooCommerce\Admin\Loader::is_admin_or_embed_page() ) {
		return;
	}

	$script_path       = '/build/index.js';
	$script_asset_path = dirname( __FILE__ ) . '/build/index.asset.php';
	$script_asset      = file_exists( $script_asset_path )
		? require( $script_asset_path )
		: array( 'dependencies' => array(), 'version' => filemtime( $script_path ) );
	$script_url        = plugins_url( $script_path, __FILE__ );


	wp_register_script(
		'woocommerce-analytics-data',
		$script_url,
		$script_asset['dependencies'],
		$script_asset['version'],
		true
	);


	wp_register_style(
		'woocommerce-analytics-data',
		plugins_url( '/build/index.css', __FILE__ ),
		array(), // Add any dependencies styles may have, such as wp-components.
		filemtime( dirname( __FILE__ ) . '/build/index.css' )
	);


	wp_enqueue_script( 'woocommerce-analytics-data' );
	wp_enqueue_style( 'woocommerce-analytics-data' );
}


add_action( 'admin_enqueue_scripts', 'add_extension_register_script' );

Далее покажем простейший пример как добавить новый столбец в статистику по пользователям:

import {addFilter} from '@wordpress/hooks';

addFilter(
    'woocommerce_admin_report_table',
    'woocommerce',
    (reportTableData) => {
        if (reportTableData.endpoint !== 'customers') {
            return reportTableData;
        }

        reportTableData.headers = [
            ...reportTableData.headers,
            {
                label: 'Customer handle',
                key: 'customer_id',
            },
        ];

        if (
            !reportTableData.items ||
            !reportTableData.items.data ||
            !reportTableData.items.data.length
        ) {
            return reportTableData;
        }

        reportTableData.rows = reportTableData.rows.map((row, index) => {
            const customer = reportTableData.items.data[index];

            const link = React.createElement("a", {
                    href: "https://app.reepay.com/#/rp/customers/customers/customer/customer-" + customer.user_id,
                    target: "_blank"
                },
                'customer-' + customer.user_id
            );

            return [
                ...row,
                {
                    display: link,
                    value: 'customer-' + customer.user_id,
                },
            ];
        });

        return reportTableData;
    }
);

Давайте разберем данный пример: 

  1. Так же как и через PHP добавляем функцию на хук woocommerce_admin_report_table
  2. Проверяем является ли текущая страница аналитики таблицей с пользователями используя переменную reportTableData.endpoint
  3. Добавляем новый столбец в таблицу с аналитикой по пользователям добавляя значение в массив reportTableData.headers
  4. Делаем проверку есть ли в объекте reportTableData в принципе значения для вывода
  5. Создаем ссылку которая будет выведена в виде данных для каждого пользователя в статистике используются инструменты React
  6. Добавляем данные в новый столбец добавляя в массив row значения display — созданная нами ранее ссылка и value — собственно само значение для данного столбца

Таким образом мы добавили в таблицу аналитики новый столбец Customer handle в который вывели новый идентификатор пользователя при нажатии на который откроется страница на стороннем сервисе с информацией по этому пользователю

Пример 2

Сейчас попробуем рассмотреть более сложный пример с использованием бэкенда и дополнительных джоинов

Перед нами стоит задача добавить новые данные в таблицу с аналитикой по заказам, но самое не просто в том что нужно добавлять данные которые находятся в других таблицах и по умолчанию их никак не получить в таблице с заказами, для этого нам придется зацепится за хук MySql запроса к базе данных заказов и сделать JOIN других таблиц для вывода.

Для этого используем 3 фильтра:

  • woocommerce_analytics_clauses_join_orders_subquery
  • woocommerce_analytics_clauses_join_orders_stats_total
  • woocommerce_analytics_clauses_join_orders_stats_interval

которые будут ссылаться на одну и ту же функцию. К примеру запрос для получения Customer ID выглядит так: 

$clauses[] = "JOIN {$wpdb->prefix}wc_customer_lookup user_customer ON {$wpdb->prefix}wc_order_stats.customer_id = user_customer.customer_id";

А вот так выглядит функция в целом:

/**
 * Add a JOIN clause.
 *
 * @param array $clauses an array of JOIN query strings.
 *
 * @return array augmented clauses.
 */
function add_join_subquery( $clauses ) {
	global $wpdb;

	$clauses[] = "JOIN {$wpdb->prefix}wc_customer_lookup user_customer ON {$wpdb->prefix}wc_order_stats.customer_id = user_customer.customer_id";
	$clauses[] = "LEFT JOIN {$wpdb->usermeta} user_meta ON user_meta.user_id = user_customer.user_id AND user_meta.meta_key = 'wp_capabilities'";
	$clauses[] = "LEFT JOIN {$wpdb->usermeta} user_meta_company ON user_meta_company.user_id = user_customer.user_id AND user_meta_company.meta_key = 'billing_company'";
	$clauses[] = "LEFT JOIN {$wpdb->postmeta} post_meta_billing ON post_meta_billing.post_id = {$wpdb->prefix}wc_order_stats.order_id AND post_meta_billing.meta_key = '_payment_method_title'";
	$clauses[] = "LEFT JOIN {$wpdb->postmeta} post_meta_shipping ON post_meta_shipping.post_id = {$wpdb->prefix}wc_order_stats.order_id AND post_meta_shipping.meta_key = '_order_shipping'";
	$clauses[] = "LEFT JOIN {$wpdb->postmeta} post_meta_discount ON post_meta_discount.post_id = {$wpdb->prefix}wc_order_stats.order_id AND post_meta_discount.meta_key = '_cart_discount'";


	$clauses[] = "LEFT JOIN {$wpdb->prefix}wc_order_coupon_lookup coupons ON {$wpdb->prefix}wc_order_stats.order_id = coupons.order_id";
	$clauses[] = "LEFT JOIN {$wpdb->posts} post_coupon ON post_coupon.ID = coupons.coupon_id";


	return $clauses;
}

add_filter( 'woocommerce_analytics_clauses_join_orders_subquery', 'add_join_subquery' );
add_filter( 'woocommerce_analytics_clauses_join_orders_stats_total', 'add_join_subquery' );
add_filter( 'woocommerce_analytics_clauses_join_orders_stats_interval', 'add_join_subquery' );

Далее сделав JOIN всех нужных нам таблиц нужно обозначить нужные нам значения уникальными названиями чтобы потом их можно было использовать в самой таблице аналитики. Для этого используем так же 3 хука:

  • woocommerce_analytics_clauses_select_orders_subquery
  • woocommerce_analytics_clauses_select_orders_stats_total
  • woocommerce_analytics_clauses_select_orders_stats_interval

которые так же будут ссылаться на одну функцию которая выглядит следующим образом:

/**
 * Add a SELECT clause.
 *
 * @param array $clauses an array of WHERE query strings.
 * @return array augmented clauses.
 */
function add_select_subquery( $clauses ) {
	$clauses[] = ', user_meta.meta_value AS customer_role';
	$clauses[] = ', user_meta_company.meta_value AS billing_company';
	$clauses[] = ', post_meta_billing.meta_value AS payment_method';
	$clauses[] = ', post_meta_shipping.meta_value AS order_shipping';
	$clauses[] = ', post_meta_discount.meta_value AS order_discount';
	$clauses[] = ', post_coupon.post_title AS coupon';


	return $clauses;
}

add_filter( 'woocommerce_analytics_clauses_select_orders_subquery', 'add_select_subquery' );
add_filter( 'woocommerce_analytics_clauses_select_orders_stats_total', 'add_select_subquery' );
add_filter( 'woocommerce_analytics_clauses_select_orders_stats_interval', 'add_select_subquery' );

Теперь перейдем в наш файл со скриптом и посмотрим что изменилось:

import './index.scss';

/**
 * External dependencies
 */
import {addFilter} from '@wordpress/hooks';

addFilter(
    'woocommerce_admin_report_table',
    'woocommerce',
    (reportTableData) => {
        if (reportTableData.endpoint !== 'orders') {
            return reportTableData;
        }

        reportTableData.headers = [
            ...reportTableData.headers,
            {
                label: 'Customer ID',
                key: 'customer_id',
            },
            {
                label: 'Customer Role',
                key: 'customer_role',
            },
            {
                label: 'Company',
                key: 'billing_company',
            },
            {
                label: 'Payment method',
                key: 'payment_method',
            },
            {
                label: 'Shipping',
                key: 'order_shipping',
            },
            {
                label: 'Discount',
                key: 'order_discount',
            },
            {
                label: 'Coupon',
                key: 'coupon',
            },
            {
                label: 'Subtotal',
                key: 'subtotal',
            },
        ];

        if (
            !reportTableData.items ||
            !reportTableData.items.data ||
            !reportTableData.items.data.length
        ) {
            return reportTableData;
        }

        reportTableData.rows = reportTableData.rows.map((row, index) => {
            const order = reportTableData.items.data[index];


            var data_role = order.customer_role;
            var role = '';
            Object.keys(roles_list).forEach(function (item, i, arr) {
                if (data_role.indexOf(item) !== -1) {
                    role = item;
                }
            });
            var subtotal = order.total_sales - order.order_discount - order.order_shipping;
            return [
                ...row,
                {
                    display: order.extended_info.customer.user_id,
                    value: order.extended_info.customer.user_id,
                },
                {
                    display: role,
                    value: role,
                },
                {
                    display: order.billing_company,
                    value: order.billing_company,
                },
                {
                    display: order.payment_method,
                    value: order.payment_method,
                },
                {
                    display: order.order_shipping,
                    value: order.order_shipping,
                },
                {
                    display: order.order_discount,
                    value: order.order_discount,
                },
                {
                    display: order.coupon,
                    value: order.coupon,
                },
                {
                    display: subtotal,
                    value: subtotal,
                },
            ];
        });

        return reportTableData;
    }
);

В принципе суть не изменилась, изменилось количество данных для вывода и то что эти данные получаются все из разных таблиц и все переменные которым мы присваивали уникальные названия в MySql выборке теперь доступны в объекте order. 

Также в данном примере передается массив данных напрямую в скрипт, но тут мы опять же используем все так же как при работе с плагинами wp_localize_script('woocommerce-analytics-data', 'roles_list', $roles);

Для того чтобы посмотреть внесенные изменения нужно собрать проект используя npm run build и очистить кэш аналитики (без этого можно долго искать что же вы сделали не так) перейдя в панели администратора WooCommerce->Status->Tools(таб) вы увидите опцию Clear analytics cache:

Заключение

По моему мнению сам процесс создания расширения довольно удобный, хоть и не очень простой (особенно для backend разработчика). Работа с Woocommerce Analytics сильно отличается в принципе от остальных элементов панели администратора WP и WooCommrece разве что кроме Gutenberg и лично для меня разработка на первый взгляд такой простой фичи стало целым испытанием, во многом потому что в интернете очень мало информации и примеров данной кастомизации. Но если разобраться глубже и иметь хотя бы небольшой опыт работы с React и сборщиками то это не составит для вас особого труда и надеюсь данная статья этому поспособствует.

Комментирование этой и других статей доступно в нашем Телеграм канале