@woocommerce/data
Version:
WooCommerce Admin data store and utilities
488 lines (487 loc) • 20.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getReportTableData = exports.getReportTableQuery = exports.getTooltipValueFormat = exports.getReportChartData = exports.getSummaryNumbers = exports.getRequestQuery = exports.isReportDataEmpty = exports.getFilterQuery = exports.getQueryFromConfig = exports.timeStampFilterDates = void 0;
/**
* External dependencies
*/
const lodash_1 = require("lodash");
const moment_1 = __importDefault(require("moment"));
const date_1 = require("@woocommerce/date");
const navigation_1 = require("@woocommerce/navigation");
const deprecated_1 = __importDefault(require("@wordpress/deprecated"));
/**
* Internal dependencies
*/
const reportsUtils = __importStar(require("./utils"));
const constants_1 = require("../constants");
const constants_2 = require("./constants");
const utils_1 = require("../utils");
/**
* Add timestamp to advanced filter parameters involving date. The api
* expects a timestamp for these values similar to `before` and `after`.
*
* @param {Object} config - advancedFilters config object.
* @param {Object} activeFilter - an active filter.
* @return {Object} - an active filter with timestamp added to date values.
*/
function timeStampFilterDates(config, activeFilter) {
const advancedFilterConfig = config.filters[activeFilter.key];
if ((0, lodash_1.get)(advancedFilterConfig, ['input', 'component']) !== 'Date') {
return activeFilter;
}
const { rule, value } = activeFilter;
const timeOfDayMap = {
after: 'start',
before: 'end',
};
// If the value is an array, it signifies "between" values which must have a timestamp
// appended to each value.
if (Array.isArray(value)) {
const [after, before] = value;
return Object.assign({}, activeFilter, {
value: [
(0, date_1.appendTimestamp)((0, moment_1.default)(after), timeOfDayMap.after),
(0, date_1.appendTimestamp)((0, moment_1.default)(before), timeOfDayMap.before),
],
});
}
return Object.assign({}, activeFilter, {
value: (0, date_1.appendTimestamp)((0, moment_1.default)(value), timeOfDayMap[rule]),
});
}
exports.timeStampFilterDates = timeStampFilterDates;
function getQueryFromConfig(config, advancedFilters, query) {
const queryValue = query[config.param];
if (!queryValue) {
return {};
}
if (queryValue === 'advanced') {
const activeFilters = (0, navigation_1.getActiveFiltersFromQuery)(query, advancedFilters.filters);
if (activeFilters.length === 0) {
return {};
}
const filterQuery = (0, navigation_1.getQueryFromActiveFilters)(activeFilters.map((filter) => timeStampFilterDates(advancedFilters, filter)), {}, advancedFilters.filters);
return {
match: query.match || 'all',
...filterQuery,
};
}
const filter = (0, lodash_1.find)((0, navigation_1.flattenFilters)(config.filters), {
value: queryValue,
});
if (!filter) {
return {};
}
if (filter.settings && filter.settings.param) {
const { param } = filter.settings;
if (query[param]) {
return {
[param]: query[param],
};
}
return {};
}
return {
[config.param]: queryValue,
};
}
exports.getQueryFromConfig = getQueryFromConfig;
/**
* Add filters and advanced filters values to a query object.
*
* @param {Object} options arguments
* @param {string} options.endpoint Report API Endpoint
* @param {Object} options.query Query parameters in the url
* @param {Array} options.limitBy Properties used to limit the results. It will be used in the API call to send the IDs.
* @param {Array} [options.filters] config filters
* @param {Object} [options.advancedFilters] config advanced filters
* @return {Object} A query object with the values from filters and advanced fitlters applied.
*/
function getFilterQuery(options) {
const { endpoint, query, limitBy, filters = [], advancedFilters = {}, } = options;
if (query.search) {
const limitProperties = limitBy || [endpoint];
return limitProperties.reduce((result, limitProperty) => {
result[limitProperty] = query[limitProperty];
return result;
}, {});
}
return filters
.map((filter) => getQueryFromConfig(filter, advancedFilters, query))
.reduce((result, configQuery) => Object.assign(result, configQuery), {});
}
exports.getFilterQuery = getFilterQuery;
// Some stats endpoints don't have interval data, so they can ignore after/before params and omit that part of the response.
const noIntervalEndpoints = ['stock', 'customers'];
/**
* Returns true if a report object is empty.
*
* @param {Object} report Report to check
* @param {string} endpoint Endpoint slug
* @return {boolean} True if report is data is empty.
*/
function isReportDataEmpty(report, endpoint) {
if (!report) {
return true;
}
if (!report.data) {
return true;
}
if (!report.data.totals || (0, lodash_1.isNull)(report.data.totals)) {
return true;
}
const checkIntervals = !(0, lodash_1.includes)(noIntervalEndpoints, endpoint);
if (checkIntervals &&
(!report.data.intervals || report.data.intervals.length === 0)) {
return true;
}
return false;
}
exports.isReportDataEmpty = isReportDataEmpty;
/**
* Constructs and returns a query associated with a Report data request.
*
* @param {Object} options arguments
* @param {string} options.endpoint Report API Endpoint
* @param {string} options.dataType 'primary' or 'secondary'.
* @param {Object} options.query Query parameters in the url.
* @param {Array} [options.filters] config filters
* @param {Object} [options.advancedFilters] config advanced filters
* @param {Array} options.limitBy Properties used to limit the results. It will be used in the API call to send the IDs.
* @param {string} options.defaultDateRange User specified default date range.
* @return {Object} data request query parameters.
*/
function getRequestQuery(options) {
const { endpoint, dataType, query, fields, defaultDateRange } = options;
const datesFromQuery = (0, date_1.getCurrentDates)(query, defaultDateRange);
const interval = (0, date_1.getIntervalForQuery)(query, defaultDateRange);
const filterQuery = getFilterQuery(options);
const end = datesFromQuery[dataType].before;
const noIntervals = (0, lodash_1.includes)(noIntervalEndpoints, endpoint);
return noIntervals
? { ...filterQuery, fields }
: {
order: 'asc',
interval,
per_page: constants_1.MAX_PER_PAGE,
after: (0, date_1.appendTimestamp)(datesFromQuery[dataType].after, 'start'),
before: (0, date_1.appendTimestamp)(end, 'end'),
segmentby: query.segmentby,
fields,
...filterQuery,
};
}
exports.getRequestQuery = getRequestQuery;
/**
* Returns summary number totals needed to render a report page.
*
* @param {Object} options arguments
* @param {string} options.endpoint Report API Endpoint
* @param {Object} options.query Query parameters in the url
* @param {Object} options.select Instance of @wordpress/select
* @param {Array} [options.filters] config filters
* @param {Object} [options.advancedFilters] config advanced filters
* @param {Array} options.limitBy Properties used to limit the results. It will be used in the API call to send the IDs.
* @param {string} options.defaultDateRange User specified default date range.
* @return {Object} Object containing summary number responses.
*/
function getSummaryNumbers(options) {
const { endpoint, select } = options;
const { getReportStats, getReportStatsError, isResolving } = select(constants_2.STORE_NAME);
const response = {
isRequesting: false,
isError: false,
totals: {
primary: null,
secondary: null,
},
};
const primaryQuery = getRequestQuery({ ...options, dataType: 'primary' });
// Disable eslint rule requiring `getReportStats` to be defined below because the next two statements
// depend on `getReportStats` to have been called.
// eslint-disable-next-line @wordpress/no-unused-vars-before-return
const primary = getReportStats(endpoint, primaryQuery);
if (isResolving('getReportStats', [endpoint, primaryQuery])) {
return { ...response, isRequesting: true };
}
else if (getReportStatsError(endpoint, primaryQuery)) {
return { ...response, isError: true };
}
const primaryTotals = (primary && primary.data && primary.data.totals) || null;
const secondaryQuery = getRequestQuery({
...options,
dataType: 'secondary',
});
// Disable eslint rule requiring `getReportStats` to be defined below because the next two statements
// depend on `getReportStats` to have been called.
// eslint-disable-next-line @wordpress/no-unused-vars-before-return
const secondary = getReportStats(endpoint, secondaryQuery);
if (isResolving('getReportStats', [endpoint, secondaryQuery])) {
return { ...response, isRequesting: true };
}
else if (getReportStatsError(endpoint, secondaryQuery)) {
return { ...response, isError: true };
}
const secondaryTotals = (secondary && secondary.data && secondary.data.totals) || null;
return {
...response,
totals: { primary: primaryTotals, secondary: secondaryTotals },
};
}
exports.getSummaryNumbers = getSummaryNumbers;
/**
* Static responses object to avoid returning new references each call.
*/
const reportChartDataResponses = {
requesting: {
isEmpty: false,
isError: false,
isRequesting: true,
data: {
totals: {},
intervals: [],
},
},
error: {
isEmpty: false,
isError: true,
isRequesting: false,
data: {
totals: {},
intervals: [],
},
},
empty: {
isEmpty: true,
isError: false,
isRequesting: false,
data: {
totals: {},
intervals: [],
},
},
};
const EMPTY_ARRAY = [];
/**
* Cache helper for returning the full chart dataset after multiple
* requests. Memoized on the request query (string), only called after
* all the requests have resolved successfully.
*/
const getReportChartDataResponse = (0, lodash_1.memoize)((_requestString, totals, intervals) => ({
isEmpty: false,
isError: false,
isRequesting: false,
data: { totals, intervals },
}), (requestString, totals, intervals) => [requestString, totals.length, intervals.length].join(':'));
/**
* Returns all of the data needed to render a chart with summary numbers on a report page.
*
* @param {Object} options arguments
* @param {string} options.endpoint Report API Endpoint
* @param {string} options.dataType 'primary' or 'secondary'
* @param {Object} options.query Query parameters in the url
* @param {Object} options.selector Instance of @wordpress/select response
* @param {Object} options.select (Depreciated) Instance of @wordpress/select
* @param {Array} options.limitBy Properties used to limit the results. It will be used in the API call to send the IDs.
* @param {string} options.defaultDateRange User specified default date range.
* @return {Object} Object containing API request information (response, fetching, and error details)
*/
function getReportChartData(options) {
const { endpoint } = options;
let reportSelectors = options.selector;
if (options.select && !options.selector) {
(0, deprecated_1.default)('option.select', {
version: '1.7.0',
hint: 'You can pass the report selectors through option.selector now.',
});
reportSelectors = options.select(constants_2.STORE_NAME);
}
const { getReportStats, getReportStatsError, isResolving } = reportSelectors;
const requestQuery = getRequestQuery(options);
// Disable eslint rule requiring `stats` to be defined below because the next two if statements
// depend on `getReportStats` to have been called.
// eslint-disable-next-line @wordpress/no-unused-vars-before-return
const stats = getReportStats(endpoint, requestQuery);
if (isResolving('getReportStats', [endpoint, requestQuery])) {
return reportChartDataResponses.requesting;
}
if (getReportStatsError(endpoint, requestQuery)) {
return reportChartDataResponses.error;
}
if (isReportDataEmpty(stats, endpoint)) {
return reportChartDataResponses.empty;
}
const totals = (stats && stats.data && stats.data.totals) || null;
let intervals = (stats && stats.data && stats.data.intervals) || EMPTY_ARRAY;
// If we have more than 100 results for this time period,
// we need to make additional requests to complete the response.
if (stats.totalResults > constants_1.MAX_PER_PAGE) {
let isFetching = true;
let isError = false;
const pagedData = [];
const totalPages = Math.ceil(stats.totalResults / constants_1.MAX_PER_PAGE);
let pagesFetched = 1;
for (let i = 2; i <= totalPages; i++) {
const nextQuery = { ...requestQuery, page: i };
const _data = getReportStats(endpoint, nextQuery);
if (isResolving('getReportStats', [endpoint, nextQuery])) {
continue;
}
if (getReportStatsError(endpoint, nextQuery)) {
isError = true;
isFetching = false;
break;
}
pagedData.push(_data);
pagesFetched++;
if (pagesFetched === totalPages) {
isFetching = false;
break;
}
}
if (isFetching) {
return reportChartDataResponses.requesting;
}
else if (isError) {
return reportChartDataResponses.error;
}
(0, lodash_1.forEach)(pagedData, function (_data) {
if (_data.data &&
_data.data.intervals &&
Array.isArray(_data.data.intervals)) {
intervals = intervals.concat(_data.data.intervals);
}
});
}
return getReportChartDataResponse((0, utils_1.getResourceName)(endpoint, requestQuery), totals, intervals);
}
exports.getReportChartData = getReportChartData;
/**
* Returns a formatting function or string to be used by d3-format
*
* @param {string} type Type of number, 'currency', 'number', 'percent', 'average'
* @param {Function} formatAmount format currency function
* @return {string|Function} returns a number format based on the type or an overriding formatting function
*/
function getTooltipValueFormat(type, formatAmount) {
switch (type) {
case 'currency':
return formatAmount;
case 'percent':
return '.0%';
case 'number':
return ',';
case 'average':
return ',.2r';
default:
return ',';
}
}
exports.getTooltipValueFormat = getTooltipValueFormat;
/**
* Returns query needed for a request to populate a table.
*
* @param {Object} options arguments
* @param {string} options.endpoint Report API Endpoint
* @param {Object} options.query Query parameters in the url
* @param {Object} options.tableQuery Query parameters specific for that endpoint
* @param {string} options.defaultDateRange User specified default date range.
* @return {Object} Object Table data response
*/
function getReportTableQuery(options) {
const { query, tableQuery = {} } = options;
const filterQuery = getFilterQuery(options);
const datesFromQuery = (0, date_1.getCurrentDates)(query, options.defaultDateRange);
const noIntervals = (0, lodash_1.includes)(noIntervalEndpoints, options.endpoint);
return {
orderby: query.orderby || 'date',
order: query.order || 'desc',
after: noIntervals
? undefined
: (0, date_1.appendTimestamp)(datesFromQuery.primary.after, 'start'),
before: noIntervals
? undefined
: (0, date_1.appendTimestamp)(datesFromQuery.primary.before, 'end'),
page: query.paged || 1,
per_page: query.per_page || constants_1.QUERY_DEFAULTS.pageSize,
...filterQuery,
...tableQuery,
};
}
exports.getReportTableQuery = getReportTableQuery;
/**
* Returns table data needed to render a report page.
*
* @param {Object} options arguments
* @param {string} options.endpoint Report API Endpoint
* @param {Object} options.query Query parameters in the url
* @param {Object} options.selector Instance of @wordpress/select response
* @param {Object} options.select (depreciated) Instance of @wordpress/select
* @param {Object} options.tableQuery Query parameters specific for that endpoint
* @param {string} options.defaultDateRange User specified default date range.
* @return {Object} Object Table data response
*/
function getReportTableData(options) {
const { endpoint } = options;
let reportSelectors = options.selector;
if (options.select && !options.selector) {
(0, deprecated_1.default)('option.select', {
version: '1.7.0',
hint: 'You can pass the report selectors through option.selector now.',
});
reportSelectors = options.select(constants_2.STORE_NAME);
}
const { getReportItems, getReportItemsError, hasFinishedResolution } = reportSelectors;
const tableQuery = reportsUtils.getReportTableQuery(options);
const response = {
query: tableQuery,
isRequesting: false,
isError: false,
items: {
data: [],
totalResults: 0,
},
};
// Disable eslint rule requiring `items` to be defined below because the next two if statements
// depend on `getReportItems` to have been called.
// eslint-disable-next-line @wordpress/no-unused-vars-before-return
const items = getReportItems(endpoint, tableQuery);
const queryResolved = hasFinishedResolution('getReportItems', [
endpoint,
tableQuery,
]);
if (!queryResolved) {
return { ...response, isRequesting: true };
}
if (getReportItemsError(endpoint, tableQuery)) {
return { ...response, isError: true };
}
return { ...response, items };
}
exports.getReportTableData = getReportTableData;