woocommerce/plugins/woocommerce-admin/client/analytics/components/report-chart/index.js

435 lines
9.8 KiB
JavaScript
Raw Normal View History

/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { format as formatDate } from '@wordpress/date';
import { withSelect } from '@wordpress/data';
import { get, isEqual } from 'lodash';
import PropTypes from 'prop-types';
import { Chart } from '@woocommerce/components';
import {
getReportChartData,
getTooltipValueFormat,
SETTINGS_STORE_NAME,
REPORTS_STORE_NAME,
} from '@woocommerce/data';
import {
getAllowedIntervalsForQuery,
getCurrentDates,
getDateFormatsForInterval,
getIntervalForQuery,
2018-11-14 01:45:05 +00:00
getChartTypeForQuery,
getPreviousDate,
} from '@woocommerce/date';
/**
* Internal dependencies
*/
import { CurrencyContext } from '../../../lib/currency-context';
import ReportError from '../report-error';
import { getChartMode, getSelectedFilter, createDateFormatter } from './utils';
/**
* Component that renders the chart in reports.
*/
export class ReportChart extends Component {
shouldComponentUpdate( nextProps ) {
if (
nextProps.isRequesting !== this.props.isRequesting ||
nextProps.primaryData.isRequesting !==
this.props.primaryData.isRequesting ||
nextProps.secondaryData.isRequesting !==
this.props.secondaryData.isRequesting ||
! isEqual( nextProps.query, this.props.query )
) {
return true;
}
return false;
}
getItemChartData() {
const { primaryData, selectedChart } = this.props;
const chartData = primaryData.data.intervals.map( function (
interval
) {
const intervalData = {};
interval.subtotals.segments.forEach( function ( segment ) {
if ( segment.segment_label ) {
const label = intervalData[ segment.segment_label ]
? segment.segment_label +
' (#' +
segment.segment_id +
')'
: segment.segment_label;
intervalData[ segment.segment_id ] = {
label,
value: segment.subtotals[ selectedChart.key ] || 0,
};
}
} );
return {
date: formatDate( 'Y-m-d\\TH:i:s', interval.date_start ),
...intervalData,
};
} );
return chartData;
}
getTimeChartData() {
const {
query,
primaryData,
secondaryData,
selectedChart,
defaultDateRange,
} = this.props;
const currentInterval = getIntervalForQuery( query, defaultDateRange );
const { primary, secondary } = getCurrentDates(
query,
defaultDateRange
);
const chartData = primaryData.data.intervals.map( function (
interval,
index
) {
const secondaryDate = getPreviousDate(
interval.date_start,
primary.after,
secondary.after,
query.compare,
currentInterval
);
const secondaryInterval = secondaryData.data.intervals[ index ];
return {
date: formatDate( 'Y-m-d\\TH:i:s', interval.date_start ),
primary: {
label: `${ primary.label } (${ primary.range })`,
labelDate: interval.date_start,
value: interval.subtotals[ selectedChart.key ] || 0,
},
secondary: {
label: `${ secondary.label } (${ secondary.range })`,
labelDate: secondaryDate.format( 'YYYY-MM-DD HH:mm:ss' ),
value:
( secondaryInterval &&
secondaryInterval.subtotals[
selectedChart.key
] ) ||
0,
},
};
} );
return chartData;
}
getTimeChartTotals() {
const { primaryData, secondaryData, selectedChart } = this.props;
return {
primary: get(
primaryData,
[ 'data', 'totals', selectedChart.key ],
null
),
secondary: get(
secondaryData,
[ 'data', 'totals', selectedChart.key ],
null
),
};
}
renderChart( mode, isRequesting, chartData, legendTotals ) {
const {
emptySearchResults,
filterParam,
interactiveLegend,
itemsLabel,
legendPosition,
path,
query,
selectedChart,
showHeaderControls,
primaryData,
defaultDateRange,
} = this.props;
const currentInterval = getIntervalForQuery( query, defaultDateRange );
const allowedIntervals = getAllowedIntervalsForQuery(
query,
defaultDateRange
);
const formats = getDateFormatsForInterval(
currentInterval,
primaryData.data.intervals.length,
{ type: 'php' }
);
const emptyMessage = emptySearchResults
? __( 'No data for the current search', 'woocommerce' )
: __( 'No data for the selected date range', 'woocommerce' );
const { formatAmount, getCurrencyConfig } = this.context;
return (
<Chart
allowedIntervals={ allowedIntervals }
data={ chartData }
dateParser={ '%Y-%m-%dT%H:%M:%S' }
emptyMessage={ emptyMessage }
filterParam={ filterParam }
interactiveLegend={ interactiveLegend }
interval={ currentInterval }
isRequesting={ isRequesting }
itemsLabel={ itemsLabel }
legendPosition={ legendPosition }
legendTotals={ legendTotals }
mode={ mode }
path={ path }
query={ query }
screenReaderFormat={ createDateFormatter(
formats.screenReaderFormat
) }
showHeaderControls={ showHeaderControls }
title={ selectedChart.label }
tooltipLabelFormat={ createDateFormatter(
formats.tooltipLabelFormat
) }
tooltipTitle={
( mode === 'time-comparison' && selectedChart.label ) ||
null
}
tooltipValueFormat={ getTooltipValueFormat(
selectedChart.type,
formatAmount
) }
chartType={ getChartTypeForQuery( query ) }
valueType={ selectedChart.type }
xFormat={ createDateFormatter( formats.xFormat ) }
x2Format={ createDateFormatter( formats.x2Format ) }
currency={ getCurrencyConfig() }
/>
);
}
renderItemComparison() {
const { isRequesting, primaryData } = this.props;
if ( primaryData.isError ) {
return <ReportError />;
}
const isChartRequesting = isRequesting || primaryData.isRequesting;
const chartData = this.getItemChartData();
return this.renderChart(
'item-comparison',
isChartRequesting,
chartData
);
}
renderTimeComparison() {
const { isRequesting, primaryData, secondaryData } = this.props;
if ( ! primaryData || primaryData.isError || secondaryData.isError ) {
return <ReportError />;
}
const isChartRequesting =
isRequesting ||
primaryData.isRequesting ||
secondaryData.isRequesting;
const chartData = this.getTimeChartData();
const legendTotals = this.getTimeChartTotals();
return this.renderChart(
'time-comparison',
isChartRequesting,
chartData,
legendTotals
);
}
render() {
const { mode } = this.props;
if ( mode === 'item-comparison' ) {
return this.renderItemComparison();
}
return this.renderTimeComparison();
}
}
ReportChart.contextType = CurrencyContext;
ReportChart.propTypes = {
/**
* Filters available for that report.
*/
filters: PropTypes.array,
/**
* Whether there is an API call running.
*/
isRequesting: PropTypes.bool,
/**
* Label describing the legend items.
*/
itemsLabel: PropTypes.string,
/**
* Allows specifying properties different from the `endpoint` that will be used
* to limit the items when there is an active search.
*/
limitProperties: PropTypes.array,
/**
* `items-comparison` (default) or `time-comparison`, this is used to generate correct
* ARIA properties.
*/
mode: PropTypes.string,
/**
* Current path
*/
path: PropTypes.string.isRequired,
/**
* Primary data to display in the chart.
*/
primaryData: PropTypes.object,
/**
* The query string represented in object form.
*/
query: PropTypes.object.isRequired,
/**
* Secondary data to display in the chart.
*/
secondaryData: PropTypes.object,
/**
* Properties of the selected chart.
*/
2019-05-20 01:57:06 +00:00
selectedChart: PropTypes.shape( {
/**
* Key of the selected chart.
*/
key: PropTypes.string.isRequired,
/**
* Chart label.
*/
label: PropTypes.string.isRequired,
/**
* Order query argument.
*/
order: PropTypes.oneOf( [ 'asc', 'desc' ] ),
/**
* Order by query argument.
*/
orderby: PropTypes.string,
/**
* Number type for formatting.
*/
type: PropTypes.oneOf( [ 'average', 'number', 'currency' ] ).isRequired,
} ).isRequired,
};
ReportChart.defaultProps = {
isRequesting: false,
primaryData: {
data: {
intervals: [],
},
isError: false,
isRequesting: false,
},
secondaryData: {
data: {
intervals: [],
},
isError: false,
isRequesting: false,
},
};
export default compose(
withSelect( ( select, props ) => {
const {
charts,
endpoint,
filters,
isRequesting,
limitProperties,
query,
advancedFilters,
} = props;
const limitBy = limitProperties || [ endpoint ];
const selectedFilter = getSelectedFilter( filters, query );
const filterParam = get( selectedFilter, [ 'settings', 'param' ] );
const chartMode =
props.mode ||
getChartMode( selectedFilter, query ) ||
'time-comparison';
wp.data Settings refactor add data store for settings using wp.data add use-select-with-refresh example replace fresh-data usage with new settings data store for settings page Add data package move to packages Fix isDirty after save Add isBusy to primary button when saving update Readme remove comment readme to use useSelect Revert "update Readme" This reverts commit 7402fd49b8f384fde5878e0bee0616f0a87bb4f6. Data Layer: Settings page to use Settings store (https://github.com/woocommerce/woocommerce-admin/pull/3430) * Data Layer: Settings store as source of truth for settings page This reverts commit 7402fd49b8f384fde5878e0bee0616f0a87bb4f6. * fixup * save on reset * non mutable constants * add set/getSettings * save using setSettings * separate HOC * cleanup * remove settingsToData * withHydration * remove withSettings HOC * renmove useSettins for now * withSettingsHydration updates * Revert "withSettingsHydration updates" This reverts commit f2adf108fbe19b574978fea5925a1a18e7ed3007. * rename withSettingsHydration * redo withSettingsHydration simplification * restore * useSettings * render using useSettings * handleInputChange working * get setIsDirty working * saving works * reset and cleanup * cleanup * use snake case on hook files * use clearIsDirty * Avoid mutation on setting update * remove @todo * persiting -> isPersisting * better reducer ternaries * add wcSettings as arg to withSettingsHydration reset package-lock Settings: split out mutable wcAdminSettings (https://github.com/woocommerce/woocommerce-admin/pull/3675) Settings: handle async settings groups (https://github.com/woocommerce/woocommerce-admin/pull/3707)
2020-03-25 03:20:17 +00:00
const { woocommerce_default_date_range: defaultDateRange } = select(
SETTINGS_STORE_NAME
).getSetting( 'wc_admin', 'wcAdminSettings' );
/* eslint @wordpress/no-unused-vars-before-return: "off" */
const reportStoreSelector = select( REPORTS_STORE_NAME );
const newProps = {
mode: chartMode,
filterParam,
wp.data Settings refactor add data store for settings using wp.data add use-select-with-refresh example replace fresh-data usage with new settings data store for settings page Add data package move to packages Fix isDirty after save Add isBusy to primary button when saving update Readme remove comment readme to use useSelect Revert "update Readme" This reverts commit 7402fd49b8f384fde5878e0bee0616f0a87bb4f6. Data Layer: Settings page to use Settings store (https://github.com/woocommerce/woocommerce-admin/pull/3430) * Data Layer: Settings store as source of truth for settings page This reverts commit 7402fd49b8f384fde5878e0bee0616f0a87bb4f6. * fixup * save on reset * non mutable constants * add set/getSettings * save using setSettings * separate HOC * cleanup * remove settingsToData * withHydration * remove withSettings HOC * renmove useSettins for now * withSettingsHydration updates * Revert "withSettingsHydration updates" This reverts commit f2adf108fbe19b574978fea5925a1a18e7ed3007. * rename withSettingsHydration * redo withSettingsHydration simplification * restore * useSettings * render using useSettings * handleInputChange working * get setIsDirty working * saving works * reset and cleanup * cleanup * use snake case on hook files * use clearIsDirty * Avoid mutation on setting update * remove @todo * persiting -> isPersisting * better reducer ternaries * add wcSettings as arg to withSettingsHydration reset package-lock Settings: split out mutable wcAdminSettings (https://github.com/woocommerce/woocommerce-admin/pull/3675) Settings: handle async settings groups (https://github.com/woocommerce/woocommerce-admin/pull/3707)
2020-03-25 03:20:17 +00:00
defaultDateRange,
};
if ( isRequesting ) {
return newProps;
}
const hasLimitByParam = limitBy.some(
( item ) => query[ item ] && query[ item ].length
);
if ( query.search && ! hasLimitByParam ) {
return {
...newProps,
emptySearchResults: true,
};
}
const fields = charts && charts.map( ( chart ) => chart.key );
2019-03-21 03:25:05 +00:00
const primaryData = getReportChartData( {
endpoint,
dataType: 'primary',
query,
selector: reportStoreSelector,
2019-03-21 03:25:05 +00:00
limitBy,
filters,
advancedFilters,
wp.data Settings refactor add data store for settings using wp.data add use-select-with-refresh example replace fresh-data usage with new settings data store for settings page Add data package move to packages Fix isDirty after save Add isBusy to primary button when saving update Readme remove comment readme to use useSelect Revert "update Readme" This reverts commit 7402fd49b8f384fde5878e0bee0616f0a87bb4f6. Data Layer: Settings page to use Settings store (https://github.com/woocommerce/woocommerce-admin/pull/3430) * Data Layer: Settings store as source of truth for settings page This reverts commit 7402fd49b8f384fde5878e0bee0616f0a87bb4f6. * fixup * save on reset * non mutable constants * add set/getSettings * save using setSettings * separate HOC * cleanup * remove settingsToData * withHydration * remove withSettings HOC * renmove useSettins for now * withSettingsHydration updates * Revert "withSettingsHydration updates" This reverts commit f2adf108fbe19b574978fea5925a1a18e7ed3007. * rename withSettingsHydration * redo withSettingsHydration simplification * restore * useSettings * render using useSettings * handleInputChange working * get setIsDirty working * saving works * reset and cleanup * cleanup * use snake case on hook files * use clearIsDirty * Avoid mutation on setting update * remove @todo * persiting -> isPersisting * better reducer ternaries * add wcSettings as arg to withSettingsHydration reset package-lock Settings: split out mutable wcAdminSettings (https://github.com/woocommerce/woocommerce-admin/pull/3675) Settings: handle async settings groups (https://github.com/woocommerce/woocommerce-admin/pull/3707)
2020-03-25 03:20:17 +00:00
defaultDateRange,
fields,
2019-03-21 03:25:05 +00:00
} );
if ( chartMode === 'item-comparison' ) {
return {
...newProps,
primaryData,
};
}
2019-03-21 03:25:05 +00:00
const secondaryData = getReportChartData( {
endpoint,
dataType: 'secondary',
query,
selector: reportStoreSelector,
2019-03-21 03:25:05 +00:00
limitBy,
filters,
advancedFilters,
wp.data Settings refactor add data store for settings using wp.data add use-select-with-refresh example replace fresh-data usage with new settings data store for settings page Add data package move to packages Fix isDirty after save Add isBusy to primary button when saving update Readme remove comment readme to use useSelect Revert "update Readme" This reverts commit 7402fd49b8f384fde5878e0bee0616f0a87bb4f6. Data Layer: Settings page to use Settings store (https://github.com/woocommerce/woocommerce-admin/pull/3430) * Data Layer: Settings store as source of truth for settings page This reverts commit 7402fd49b8f384fde5878e0bee0616f0a87bb4f6. * fixup * save on reset * non mutable constants * add set/getSettings * save using setSettings * separate HOC * cleanup * remove settingsToData * withHydration * remove withSettings HOC * renmove useSettins for now * withSettingsHydration updates * Revert "withSettingsHydration updates" This reverts commit f2adf108fbe19b574978fea5925a1a18e7ed3007. * rename withSettingsHydration * redo withSettingsHydration simplification * restore * useSettings * render using useSettings * handleInputChange working * get setIsDirty working * saving works * reset and cleanup * cleanup * use snake case on hook files * use clearIsDirty * Avoid mutation on setting update * remove @todo * persiting -> isPersisting * better reducer ternaries * add wcSettings as arg to withSettingsHydration reset package-lock Settings: split out mutable wcAdminSettings (https://github.com/woocommerce/woocommerce-admin/pull/3675) Settings: handle async settings groups (https://github.com/woocommerce/woocommerce-admin/pull/3707)
2020-03-25 03:20:17 +00:00
defaultDateRange,
fields,
2019-03-21 03:25:05 +00:00
} );
return {
...newProps,
primaryData,
secondaryData,
};
} )
)( ReportChart );