diff --git a/plugins/woocommerce-admin/client/dashboard/leaderboard/__mocks__/top-selling-products-mock-data.js b/plugins/woocommerce-admin/client/analytics/components/leaderboard/__mocks__/top-selling-products-mock-data.js similarity index 100% rename from plugins/woocommerce-admin/client/dashboard/leaderboard/__mocks__/top-selling-products-mock-data.js rename to plugins/woocommerce-admin/client/analytics/components/leaderboard/__mocks__/top-selling-products-mock-data.js diff --git a/plugins/woocommerce-admin/client/dashboard/leaderboard/index.js b/plugins/woocommerce-admin/client/analytics/components/leaderboard/index.js similarity index 90% rename from plugins/woocommerce-admin/client/dashboard/leaderboard/index.js rename to plugins/woocommerce-admin/client/analytics/components/leaderboard/index.js index 399cabc3447..d6591c0746a 100644 --- a/plugins/woocommerce-admin/client/dashboard/leaderboard/index.js +++ b/plugins/woocommerce-admin/client/analytics/components/leaderboard/index.js @@ -23,9 +23,18 @@ import './style.scss'; export class Leaderboard extends Component { render() { - const { getHeadersContent, getRowsContent, isRequesting, isError, items, title } = this.props; + const { + getHeadersContent, + getRowsContent, + isRequesting, + isError, + items, + tableQuery, + title, + } = this.props; const data = get( items, [ 'data' ], [] ); const rows = getRowsContent( data ); + const totalRows = tableQuery ? tableQuery.per_page : 5; if ( isError ) { return ; @@ -47,9 +56,9 @@ export class Leaderboard extends Component { headers={ getHeadersContent() } isLoading={ isRequesting } rows={ rows } - rowsPerPage={ 5 } + rowsPerPage={ totalRows } title={ title } - totalRows={ 5 } + totalRows={ totalRows } /> ); } diff --git a/plugins/woocommerce-admin/client/dashboard/leaderboard/style.scss b/plugins/woocommerce-admin/client/analytics/components/leaderboard/style.scss similarity index 100% rename from plugins/woocommerce-admin/client/dashboard/leaderboard/style.scss rename to plugins/woocommerce-admin/client/analytics/components/leaderboard/style.scss diff --git a/plugins/woocommerce-admin/client/dashboard/leaderboard/test/index.js b/plugins/woocommerce-admin/client/analytics/components/leaderboard/test/index.js similarity index 100% rename from plugins/woocommerce-admin/client/dashboard/leaderboard/test/index.js rename to plugins/woocommerce-admin/client/analytics/components/leaderboard/test/index.js diff --git a/plugins/woocommerce-admin/client/analytics/components/report-chart/index.js b/plugins/woocommerce-admin/client/analytics/components/report-chart/index.js index 89d468bfbf3..283beabbb40 100644 --- a/plugins/woocommerce-admin/client/analytics/components/report-chart/index.js +++ b/plugins/woocommerce-admin/client/analytics/components/report-chart/index.js @@ -68,7 +68,18 @@ export class ReportChart extends Component { } render() { - const { query, itemsLabel, mode, path, primaryData, secondaryData, selectedChart } = this.props; + const { + interactiveLegend, + itemsLabel, + legendPosition, + mode, + path, + primaryData, + query, + secondaryData, + selectedChart, + showHeaderControls, + } = this.props; if ( primaryData.isError || secondaryData.isError ) { return ; @@ -106,23 +117,26 @@ export class ReportChart extends Component { return ( ); } diff --git a/plugins/woocommerce-admin/client/analytics/components/report-table/index.js b/plugins/woocommerce-admin/client/analytics/components/report-table/index.js index e014003eebd..5e120727ad5 100644 --- a/plugins/woocommerce-admin/client/analytics/components/report-table/index.js +++ b/plugins/woocommerce-admin/client/analytics/components/report-table/index.js @@ -6,7 +6,7 @@ import { applyFilters } from '@wordpress/hooks'; import { Component } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import { withDispatch } from '@wordpress/data'; -import { get, orderBy } from 'lodash'; +import { get } from 'lodash'; import PropTypes from 'prop-types'; /** @@ -84,15 +84,13 @@ class ReportTable extends Component { } const isRequesting = tableData.isRequesting || primaryData.isRequesting; - const orderedItems = orderBy( items.data, query.orderby, query.order ); const totals = get( primaryData, [ 'data', 'totals' ], null ); const totalResults = items.totalResults || 0; const { headers, ids, rows, summary } = applyFilters( TABLE_FILTER, { endpoint: endpoint, headers: getHeadersContent(), - orderedItems: orderedItems, - ids: itemIdField ? orderedItems.map( item => item[ itemIdField ] ) : null, - rows: getRowsContent( orderedItems ), + ids: itemIdField ? items.data.map( item => item[ itemIdField ] ) : null, + rows: getRowsContent( items.data ), totals: totals, summary: getSummary ? getSummary( totals, totalResults ) : null, } ); diff --git a/plugins/woocommerce-admin/client/analytics/report/categories/config.js b/plugins/woocommerce-admin/client/analytics/report/categories/config.js index 047d5c04561..a64aeab9c40 100644 --- a/plugins/woocommerce-admin/client/analytics/report/categories/config.js +++ b/plugins/woocommerce-admin/client/analytics/report/categories/config.js @@ -18,7 +18,7 @@ export const charts = [ }, { key: 'net_revenue', - label: __( 'Gross Revenue', 'wc-admin' ), + label: __( 'Net Revenue', 'wc-admin' ), type: 'currency', }, { diff --git a/plugins/woocommerce-admin/client/analytics/report/categories/table.js b/plugins/woocommerce-admin/client/analytics/report/categories/table.js index 5b4b19791a8..16e1cdbfce2 100644 --- a/plugins/woocommerce-admin/client/analytics/report/categories/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/categories/table.js @@ -33,6 +33,7 @@ class CategoriesReportTable extends Component { label: __( 'Category', 'wc-admin' ), key: 'category', required: true, + isSortable: true, isLeftAligned: true, }, { @@ -110,7 +111,7 @@ class CategoriesReportTable extends Component { value: numberFormat( totals.items_sold ), }, { - label: __( 'gross revenue', 'wc-admin' ), + label: __( 'net revenue', 'wc-admin' ), value: formatCurrency( totals.net_revenue ), }, { diff --git a/plugins/woocommerce-admin/client/analytics/report/coupons/table.js b/plugins/woocommerce-admin/client/analytics/report/coupons/table.js index f773e6a3d1a..f91163e0824 100644 --- a/plugins/woocommerce-admin/client/analytics/report/coupons/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/coupons/table.js @@ -10,8 +10,8 @@ import { map } from 'lodash'; /** * WooCommerce dependencies */ -import { getIntervalForQuery, getDateFormatsForInterval } from '@woocommerce/date'; import { Link } from '@woocommerce/components'; +import { defaultTableDateFormat } from '@woocommerce/date'; import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency'; /** @@ -33,9 +33,10 @@ export default class CouponsReportTable extends Component { return [ { label: __( 'Coupon Code', 'wc-admin' ), - key: 'coupon_id', + key: 'code', required: true, isLeftAligned: true, + isSortable: true, }, { label: __( 'Orders', 'wc-admin' ), @@ -67,10 +68,6 @@ export default class CouponsReportTable extends Component { } getRowsContent( coupons ) { - const { query } = this.props; - const currentInterval = getIntervalForQuery( query ); - const { tableFormat } = getDateFormatsForInterval( currentInterval ); - return map( coupons, coupon => { const { amount, coupon_id, extended_info, orders_count } = coupon; const { code, date_created, date_expires, discount_type } = extended_info; @@ -105,11 +102,13 @@ export default class CouponsReportTable extends Component { value: getCurrencyFormatDecimal( amount ), }, { - display: formatDate( tableFormat, date_created ), + display: formatDate( defaultTableDateFormat, date_created ), value: date_created, }, { - display: date_expires ? formatDate( tableFormat, date_expires ) : __( 'N/A', 'wc-admin' ), + display: date_expires + ? formatDate( defaultTableDateFormat, date_expires ) + : __( 'N/A', 'wc-admin' ), value: date_expires, }, { diff --git a/plugins/woocommerce-admin/client/analytics/report/customers/config.js b/plugins/woocommerce-admin/client/analytics/report/customers/config.js index 181f776b911..5ee8e7b1bcb 100644 --- a/plugins/woocommerce-admin/client/analytics/report/customers/config.js +++ b/plugins/woocommerce-admin/client/analytics/report/customers/config.js @@ -18,7 +18,7 @@ export const filters = [ param: 'filter', showFilters: () => true, filters: [ - { label: __( 'All Registered Customers', 'wc-admin' ), value: 'all' }, + { label: __( 'All Customers', 'wc-admin' ), value: 'all' }, { label: __( 'Advanced Filters', 'wc-admin' ), value: 'advanced' }, ], }, diff --git a/plugins/woocommerce-admin/client/analytics/report/customers/index.js b/plugins/woocommerce-admin/client/analytics/report/customers/index.js index 43faa037f02..7b68ebaee8b 100644 --- a/plugins/woocommerce-admin/client/analytics/report/customers/index.js +++ b/plugins/woocommerce-admin/client/analytics/report/customers/index.js @@ -19,6 +19,11 @@ import CustomersReportTable from './table'; export default class CustomersReport extends Component { render() { const { query, path } = this.props; + const tableQuery = { + orderby: 'date_registered', + order: 'desc', + ...query, + }; return ( @@ -29,7 +34,7 @@ export default class CustomersReport extends Component { showDatePicker={ false } advancedFilters={ advancedFilters } /> - + ); } diff --git a/plugins/woocommerce-admin/client/analytics/report/customers/table.js b/plugins/woocommerce-admin/client/analytics/report/customers/table.js index 09a5c0b0638..0748db241d4 100644 --- a/plugins/woocommerce-admin/client/analytics/report/customers/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/customers/table.js @@ -9,7 +9,7 @@ import { format as formatDate } from '@wordpress/date'; /** * WooCommerce dependencies */ -import { getDateFormatsForInterval, getIntervalForQuery } from '@woocommerce/date'; +import { defaultTableDateFormat } from '@woocommerce/date'; import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency'; import { Link } from '@woocommerce/components'; @@ -43,7 +43,7 @@ export default class CustomersReportTable extends Component { }, { label: __( 'Sign Up', 'wc-admin' ), - key: 'date_sign_up', + key: 'date_registered', defaultSort: true, isSortable: true, }, @@ -92,33 +92,34 @@ export default class CustomersReportTable extends Component { } getRowsContent( customers ) { - const { query } = this.props; - const currentInterval = getIntervalForQuery( query ); - const formats = getDateFormatsForInterval( currentInterval ); - return customers.map( customer => { const { avg_order_value, - billing, date_last_active, - date_sign_up, + date_registered, email, - first_name, - id, - last_name, + name, + user_id, orders_count, username, total_spend, + postcode, + city, + country, } = customer; - const { postcode, city, country } = billing || {}; - const name = `${ first_name } ${ last_name }`; - const customerNameLink = ( - { email }, @@ -149,7 +150,7 @@ export default class CustomersReportTable extends Component { value: getCurrencyFormatDecimal( avg_order_value ), }, { - display: formatDate( formats.tableFormat, date_last_active ), + display: formatDate( defaultTableDateFormat, date_last_active ), value: date_last_active, }, { @@ -186,7 +187,7 @@ export default class CustomersReportTable extends Component { labels={ { placeholder: __( 'Search by customer name', 'wc-admin' ) } } searchBy="customers" searchParam="name_includes" - title={ __( 'Registered Customers', 'wc-admin' ) } + title={ __( 'Customers', 'wc-admin' ) } columnPrefsKey="customers_report_columns" /> ); diff --git a/plugins/woocommerce-admin/client/analytics/report/downloads/config.js b/plugins/woocommerce-admin/client/analytics/report/downloads/config.js index 12a86982a6e..15293e39134 100644 --- a/plugins/woocommerce-admin/client/analytics/report/downloads/config.js +++ b/plugins/woocommerce-admin/client/analytics/report/downloads/config.js @@ -12,7 +12,7 @@ import { NAMESPACE } from 'store/constants'; export const charts = [ { - key: 'downloads_count', + key: 'download_count', label: __( 'Downloads', 'wc-admin' ), type: 'number', }, @@ -70,7 +70,7 @@ export const advancedFilters = { } ) ), }, }, - username: { + user: { labels: { add: __( 'Username', 'wc-admin' ), placeholder: __( 'Search customer username', 'wc-admin' ), @@ -132,7 +132,7 @@ export const advancedFilters = { } ) ), }, }, - downloadIp: { + ip_address: { labels: { add: __( 'IP Address', 'wc-admin' ), placeholder: __( 'Search IP address', 'wc-admin' ), diff --git a/plugins/woocommerce-admin/client/analytics/report/downloads/index.js b/plugins/woocommerce-admin/client/analytics/report/downloads/index.js index 6f6518544af..0a274981034 100644 --- a/plugins/woocommerce-admin/client/analytics/report/downloads/index.js +++ b/plugins/woocommerce-admin/client/analytics/report/downloads/index.js @@ -29,7 +29,6 @@ export default class DownloadsReport extends Component { query={ query } path={ path } filters={ filters } - showDatePicker={ false } advancedFilters={ advancedFilters } /> { - const { date, file_name, ip_address, order_id, product_id, user_id } = download; + const { _embedded, date, file_name, file_path, ip_address, order_id, product_id } = download; + const { name: productName } = _embedded.product[ 0 ]; + const { name: userName } = _embedded.user[ 0 ]; const productLink = getNewPath( persistedQuery, 'products', { filter: 'single_product', @@ -80,19 +82,23 @@ export default class CouponsReportTable extends Component { return [ { - display: formatDate( tableFormat, date ), + display: formatDate( defaultTableDateFormat, date ), value: date, }, { display: ( - { product_id } + { productName } ), - value: product_id, + value: productName, }, { - display: file_name, + display: ( + + { file_name } + + ), value: file_name, }, { @@ -104,8 +110,8 @@ export default class CouponsReportTable extends Component { value: order_id, }, { - display: user_id, - value: user_id, + display: userName, + value: userName, }, { display: ip_address, @@ -119,14 +125,20 @@ export default class CouponsReportTable extends Component { if ( ! totals ) { return []; } + const { query } = this.props; + const dates = getCurrentDates( query ); + const after = moment( dates.primary.after ); + const before = moment( dates.primary.before ); + const days = before.diff( after, 'days' ) + 1; + return [ { - label: _n( 'day', 'days', totals.days, 'wc-admin' ), - value: numberFormat( totals.days ), // @TODO it's not defined + label: _n( 'day', 'days', days, 'wc-admin' ), + value: numberFormat( days ), }, { - label: _n( 'download', 'downloads', totals.downloads_count, 'wc-admin' ), - value: numberFormat( totals.downloads_count ), + label: _n( 'download', 'downloads', totals.download_count, 'wc-admin' ), + value: numberFormat( totals.download_count ), }, ]; } @@ -141,6 +153,9 @@ export default class CouponsReportTable extends Component { getRowsContent={ this.getRowsContent } getSummary={ this.getSummary } query={ query } + tableQuery={ { + _embed: true, + } } title={ __( 'Downloads', 'wc-admin' ) } columnPrefsKey="downloads_report_columns" /> diff --git a/plugins/woocommerce-admin/client/analytics/report/orders/table.js b/plugins/woocommerce-admin/client/analytics/report/orders/table.js index 5581cf2489b..56117e77c68 100644 --- a/plugins/woocommerce-admin/client/analytics/report/orders/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/orders/table.js @@ -11,12 +11,7 @@ import { map } from 'lodash'; /** * WooCommerce dependencies */ -import { - appendTimestamp, - getCurrentDates, - getIntervalForQuery, - getDateFormatsForInterval, -} from '@woocommerce/date'; +import { appendTimestamp, defaultTableDateFormat, getCurrentDates } from '@woocommerce/date'; import { Link, OrderStatus, ViewMoreList } from '@woocommerce/components'; import { formatCurrency } from '@woocommerce/currency'; @@ -102,9 +97,6 @@ class OrdersReportTable extends Component { } getRowsContent( tableData ) { - const { query } = this.props; - const currentInterval = getIntervalForQuery( query ); - const { tableFormat } = getDateFormatsForInterval( currentInterval ); return map( tableData, row => { const { date, @@ -134,7 +126,7 @@ class OrdersReportTable extends Component { return [ { - display: formatDate( tableFormat, date ), + display: formatDate( defaultTableDateFormat, date ), value: date, }, { diff --git a/plugins/woocommerce-admin/client/analytics/report/products/table.js b/plugins/woocommerce-admin/client/analytics/report/products/table.js index 9fd0a17bbbb..412dd9ff51e 100644 --- a/plugins/woocommerce-admin/client/analytics/report/products/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/products/table.js @@ -36,14 +36,16 @@ class ProductsReportTable extends Component { return [ { label: __( 'Product Title', 'wc-admin' ), - key: 'name', + key: 'product_name', required: true, isLeftAligned: true, + isSortable: true, }, { label: __( 'SKU', 'wc-admin' ), key: 'sku', hiddenByDefault: true, + isSortable: true, }, { label: __( 'Items Sold', 'wc-admin' ), @@ -118,9 +120,11 @@ class ProductsReportTable extends Component { products: product_id, } ); const categories = this.props.categories; - const productCategories = category_ids - .map( category_id => categories[ category_id ] ) - .filter( Boolean ); + + const productCategories = + ( category_ids && + category_ids.map( category_id => categories[ category_id ] ).filter( Boolean ) ) || + []; return [ { diff --git a/plugins/woocommerce-admin/client/analytics/report/revenue/table.js b/plugins/woocommerce-admin/client/analytics/report/revenue/table.js index ba791b07f90..a20128de3e9 100644 --- a/plugins/woocommerce-admin/client/analytics/report/revenue/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/revenue/table.js @@ -11,12 +11,7 @@ import { get } from 'lodash'; /** * WooCommerce dependencies */ -import { - appendTimestamp, - getCurrentDates, - getDateFormatsForInterval, - getIntervalForQuery, -} from '@woocommerce/date'; +import { appendTimestamp, defaultTableDateFormat, getCurrentDates } from '@woocommerce/date'; import { Link } from '@woocommerce/components'; import { formatCurrency, getCurrencyFormatDecimal } from '@woocommerce/currency'; @@ -100,10 +95,6 @@ class RevenueReportTable extends Component { } getRowsContent( data = [] ) { - const { query } = this.props; - const currentInterval = getIntervalForQuery( query ); - const formats = getDateFormatsForInterval( currentInterval ); - return data.map( row => { const { coupons, @@ -127,7 +118,7 @@ class RevenueReportTable extends Component { ); return [ { - display: formatDate( formats.tableFormat, row.date_start ), + display: formatDate( defaultTableDateFormat, row.date_start ), value: row.date_start, }, { diff --git a/plugins/woocommerce-admin/client/analytics/report/stock/table.js b/plugins/woocommerce-admin/client/analytics/report/stock/table.js index a5435849921..4e8f523da82 100644 --- a/plugins/woocommerce-admin/client/analytics/report/stock/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/stock/table.js @@ -30,13 +30,15 @@ export default class StockReportTable extends Component { return [ { label: __( 'Product / Variation', 'wc-admin' ), - key: 'product_variation', + key: 'title', required: true, isLeftAligned: true, + isSortable: true, }, { label: __( 'SKU', 'wc-admin' ), key: 'sku', + isSortable: true, }, { label: __( 'Status', 'wc-admin' ), diff --git a/plugins/woocommerce-admin/client/dashboard/dashboard-charts/block.js b/plugins/woocommerce-admin/client/dashboard/dashboard-charts/block.js index 8c8e56ffabc..aedb9ac7d6d 100644 --- a/plugins/woocommerce-admin/client/dashboard/dashboard-charts/block.js +++ b/plugins/woocommerce-admin/client/dashboard/dashboard-charts/block.js @@ -2,13 +2,15 @@ /** * External dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import PropTypes from 'prop-types'; +import { __, sprintf } from '@wordpress/i18n'; /** * WooCommerce dependencies */ import { Card } from '@woocommerce/components'; +import { getAdminLink, history } from '@woocommerce/navigation'; /** * Internal dependencies @@ -17,6 +19,16 @@ import ReportChart from 'analytics/components/report-chart'; import './block.scss'; class ChartBlock extends Component { + handleChartClick = () => { + const { charts } = this.props; + + if ( ! charts || ! charts.length ) { + return null; + } + + history.push( 'analytics/' + charts[ 0 ].endpoint + '?chart=' + charts[ 0 ].key ); + }; + render() { const { charts, endpoint, path, query } = this.props; @@ -25,18 +37,36 @@ class ChartBlock extends Component { } return ( - + ); } } diff --git a/plugins/woocommerce-admin/client/dashboard/dashboard-charts/block.scss b/plugins/woocommerce-admin/client/dashboard/dashboard-charts/block.scss index c01df312ed9..349ad1ba636 100644 --- a/plugins/woocommerce-admin/client/dashboard/dashboard-charts/block.scss +++ b/plugins/woocommerce-admin/client/dashboard/dashboard-charts/block.scss @@ -1,5 +1,31 @@ /** @format */ +.woocommerce-dashboard__chart-block-wrapper { + cursor: pointer; + &:hover { + .woocommerce-chart, + .woocommerce-card__header { + background: #f8f9f9; + } + .woocommerce-legend__item button { + background: transparent; + } + } + .woocommerce-chart__footer { + position: relative; + &:after { + content: ''; + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + cursor: pointer; + z-index: 1; + } + } +} + .woocommerce-dashboard__chart-block { .woocommerce-card__body { padding: 0; @@ -34,4 +60,22 @@ &:hover { background: $core-grey-light-100; } + + .screen-reader-text:focus { + clip: auto; + clip-path: none; + z-index: 1; + left: 6px; + top: 7px; + height: auto; + width: auto; + display: block; + @include font-size( 14 ); + font-weight: 600; + padding: 15px 23px 14px; + background: #f1f1f1; + color: #0073aa; + text-decoration: none; + box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.6); + } } diff --git a/plugins/woocommerce-admin/client/dashboard/dashboard-charts/config.js b/plugins/woocommerce-admin/client/dashboard/dashboard-charts/config.js index 073d5bd747d..74a30900c75 100644 --- a/plugins/woocommerce-admin/client/dashboard/dashboard-charts/config.js +++ b/plugins/woocommerce-admin/client/dashboard/dashboard-charts/config.js @@ -21,19 +21,11 @@ const allCharts = ordersCharts ); // Need to remove duplicate charts, by key, from the configs -const uniqCharts = allCharts.reduce( ( a, b ) => { +export const uniqCharts = allCharts.reduce( ( a, b ) => { if ( a.findIndex( d => d.key === b.key ) < 0 ) { a.push( b ); } return a; }, [] ); -// Default charts. -// TODO: Implement user-based toggling/persistence. -const defaultCharts = [ 'items_sold', 'gross_revenue' ]; - -export const showCharts = uniqCharts.map( d => ( { - ...d, - show: defaultCharts.indexOf( d.key ) >= 0, -} ) ); export const getChartFromKey = key => allCharts.filter( d => d.key === key ); diff --git a/plugins/woocommerce-admin/client/dashboard/dashboard-charts/index.js b/plugins/woocommerce-admin/client/dashboard/dashboard-charts/index.js index 56bb8dccdb2..5de7c54e8f0 100644 --- a/plugins/woocommerce-admin/client/dashboard/dashboard-charts/index.js +++ b/plugins/woocommerce-admin/client/dashboard/dashboard-charts/index.js @@ -4,59 +4,99 @@ */ import { __ } from '@wordpress/i18n'; import classNames from 'classnames'; -import Gridicon from 'gridicons'; -import { ToggleControl, IconButton, NavigableMenu } from '@wordpress/components'; import { Component, Fragment } from '@wordpress/element'; +import { compose } from '@wordpress/compose'; +import Gridicon from 'gridicons'; +import { isEqual, xor } from 'lodash'; import PropTypes from 'prop-types'; +import { ToggleControl, IconButton, NavigableMenu, SelectControl } from '@wordpress/components'; +import { withDispatch } from '@wordpress/data'; /** * WooCommerce dependencies */ import { EllipsisMenu, MenuItem, SectionHeader } from '@woocommerce/components'; +import { getAllowedIntervalsForQuery } from '@woocommerce/date'; /** * Internal dependencies */ import ChartBlock from './block'; -import { getChartFromKey, showCharts } from './config'; +import { getChartFromKey, uniqCharts } from './config'; +import withSelect from 'wc-api/with-select'; import './style.scss'; class DashboardCharts extends Component { constructor( props ) { super( ...arguments ); this.state = { - showCharts, - query: props.query, + chartType: props.userPrefChartType || 'line', + hiddenChartKeys: props.userPrefCharts || [], + interval: props.userPrefIntervals || 'day', }; this.toggle = this.toggle.bind( this ); } + componentDidUpdate( { + userPrefCharts: prevUserPrefCharts, + userPrefChartType: prevUserPrefChartType, + userPrefChartInterval: prevUserPrefChartInterval, + } ) { + const { userPrefCharts, userPrefChartInterval, userPrefChartType } = this.props; + if ( userPrefCharts && ! isEqual( userPrefCharts, prevUserPrefCharts ) ) { + /* eslint-disable react/no-did-update-set-state */ + this.setState( { + hiddenChartKeys: userPrefCharts, + } ); + /* eslint-enable react/no-did-update-set-state */ + } + if ( userPrefChartType && userPrefChartType !== prevUserPrefChartType ) { + /* eslint-disable react/no-did-update-set-state */ + this.setState( { + chartType: userPrefChartType, + } ); + /* eslint-enable react/no-did-update-set-state */ + } + if ( userPrefChartInterval !== prevUserPrefChartInterval ) { + /* eslint-disable react/no-did-update-set-state */ + this.setState( { + interval: userPrefChartInterval, + } ); + /* eslint-enable react/no-did-update-set-state */ + } + } + toggle( key ) { return () => { - this.setState( state => { - const foundIndex = state.showCharts.findIndex( x => x.key === key ); - state.showCharts[ foundIndex ].show = ! state.showCharts[ foundIndex ].show; - return state; - } ); + const hiddenChartKeys = xor( this.state.hiddenChartKeys, [ key ] ); + this.setState( { hiddenChartKeys } ); + const userDataFields = { + [ 'dashboard_charts' ]: hiddenChartKeys, + }; + this.props.updateCurrentUserData( userDataFields ); }; } handleTypeToggle( type ) { return () => { - this.setState( state => ( { query: { ...state.query, type } } ) ); + this.setState( { chartType: type } ); + const userDataFields = { + [ 'dashboard_chart_type' ]: type, + }; + this.props.updateCurrentUserData( userDataFields ); }; } renderMenu() { return ( - { this.state.showCharts.map( chart => { + { uniqCharts.map( chart => { return ( @@ -66,13 +106,56 @@ class DashboardCharts extends Component { ); } + renderIntervalSelector() { + const allowedIntervals = getAllowedIntervalsForQuery( this.props.query ); + if ( ! allowedIntervals || allowedIntervals.length < 1 ) { + return null; + } + + const intervalLabels = { + hour: __( 'By hour', 'wc-admin' ), + day: __( 'By day', 'wc-admin' ), + week: __( 'By week', 'wc-admin' ), + month: __( 'By month', 'wc-admin' ), + quarter: __( 'By quarter', 'wc-admin' ), + year: __( 'By year', 'wc-admin' ), + }; + + return ( + ( { + value: allowedInterval, + label: intervalLabels[ allowedInterval ], + } ) ) } + onChange={ this.setInterval } + /> + ); + } + + setInterval = interval => { + this.setState( { interval }, () => { + const userDataFields = { + [ 'dashboard_chart_interval' ]: this.state.interval, + }; + this.props.updateCurrentUserData( userDataFields ); + } ); + }; + render() { const { path } = this.props; - const { query } = this.state; + const { chartType, hiddenChartKeys, interval } = this.state; + const query = { ...this.props.query, type: chartType, interval }; return (
- + + { this.renderIntervalSelector() }
- { this.state.showCharts.map( chart => { - return ! chart.show ? null : ( -
- -
+ { uniqCharts.map( chart => { + return hiddenChartKeys.includes( chart.key ) ? null : ( + ); } ) }
@@ -127,4 +209,22 @@ DashboardCharts.propTypes = { query: PropTypes.object.isRequired, }; -export default DashboardCharts; +export default compose( + withSelect( select => { + const { getCurrentUserData } = select( 'wc-api' ); + const userData = getCurrentUserData(); + + return { + userPrefCharts: userData.dashboard_charts, + userPrefChartType: userData.dashboard_chart_type, + userPrefChartInterval: userData.dashboard_chart_interval, + }; + } ), + withDispatch( dispatch => { + const { updateCurrentUserData } = dispatch( 'wc-api' ); + + return { + updateCurrentUserData, + }; + } ) +)( DashboardCharts ); diff --git a/plugins/woocommerce-admin/client/dashboard/index.js b/plugins/woocommerce-admin/client/dashboard/index.js index dc81e1ede9e..bd200f5efdc 100644 --- a/plugins/woocommerce-admin/client/dashboard/index.js +++ b/plugins/woocommerce-admin/client/dashboard/index.js @@ -9,11 +9,11 @@ import { Component, Fragment } from '@wordpress/element'; * Internal dependencies */ import './style.scss'; -import Header from 'header'; -import StorePerformance from './store-performance'; -import TopSellingProducts from './top-selling-products'; import DashboardCharts from './dashboard-charts'; +import Header from 'header'; +import Leaderboards from './leaderboards'; import { ReportFilters } from '@woocommerce/components'; +import StorePerformance from './store-performance'; export default class Dashboard extends Component { render() { @@ -23,11 +23,7 @@ export default class Dashboard extends Component {
-
-
- -
-
+ ); diff --git a/plugins/woocommerce-admin/client/dashboard/leaderboards/index.js b/plugins/woocommerce-admin/client/dashboard/leaderboards/index.js new file mode 100644 index 00000000000..522d5fdb73a --- /dev/null +++ b/plugins/woocommerce-admin/client/dashboard/leaderboards/index.js @@ -0,0 +1,165 @@ +/** @format */ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Component, Fragment } from '@wordpress/element'; +import { compose } from '@wordpress/compose'; +import { isEqual, xor } from 'lodash'; +import PropTypes from 'prop-types'; +import { SelectControl, ToggleControl } from '@wordpress/components'; +import { withDispatch } from '@wordpress/data'; + +/** + * WooCommerce dependencies + */ +import { EllipsisMenu, MenuItem, MenuTitle, SectionHeader } from '@woocommerce/components'; + +/** + * Internal dependencies + */ +import withSelect from 'wc-api/with-select'; +import TopSellingProducts from './top-selling-products'; +import './style.scss'; + +class Leaderboards extends Component { + constructor( props ) { + super( ...arguments ); + this.state = { + hiddenLeaderboardKeys: props.userPrefLeaderboards || [], + rowsPerTable: props.userPrefLeaderboardRows || 5, + }; + + this.toggle = this.toggle.bind( this ); + } + + componentDidUpdate( { + userPrefLeaderboardRows: prevUserPrefLeaderboardRows, + userPrefLeaderboards: prevUserPrefLeaderboards, + } ) { + const { userPrefLeaderboardRows, userPrefLeaderboards } = this.props; + if ( userPrefLeaderboards && ! isEqual( userPrefLeaderboards, prevUserPrefLeaderboards ) ) { + /* eslint-disable react/no-did-update-set-state */ + this.setState( { + hiddenLeaderboardKeys: userPrefLeaderboards, + } ); + /* eslint-enable react/no-did-update-set-state */ + } + if ( + userPrefLeaderboardRows && + parseInt( userPrefLeaderboardRows ) !== parseInt( prevUserPrefLeaderboardRows ) + ) { + /* eslint-disable react/no-did-update-set-state */ + this.setState( { + rowsPerTable: parseInt( userPrefLeaderboardRows ), + } ); + /* eslint-enable react/no-did-update-set-state */ + } + } + + toggle( key ) { + return () => { + const hiddenLeaderboardKeys = xor( this.state.hiddenLeaderboardKeys, [ key ] ); + this.setState( { hiddenLeaderboardKeys } ); + const userDataFields = { + [ 'dashboard_leaderboards' ]: hiddenLeaderboardKeys, + }; + this.props.updateCurrentUserData( userDataFields ); + }; + } + + setRowsPerTable = rows => { + this.setState( { rowsPerTable: parseInt( rows ) } ); + const userDataFields = { + [ 'dashboard_leaderboard_rows' ]: parseInt( rows ), + }; + this.props.updateCurrentUserData( userDataFields ); + }; + + renderMenu() { + const { hiddenLeaderboardKeys, rowsPerTable } = this.state; + const allLeaderboards = [ + { + key: 'top-products', + label: __( 'Top Products', 'wc-admin' ), + }, + { + key: 'top-categories', + label: __( 'Top Categories', 'wc-admin' ), + }, + { + key: 'top-coupons', + label: __( 'Top Coupons', 'wc-admin' ), + }, + ]; + return ( + + + { __( 'Leaderboards', 'wc-admin' ) } + { allLeaderboards.map( leaderboard => { + return ( + + + + ); + } ) } + { __( 'Rows Per Table', 'wc-admin' ) } + ( { + v: key + 1, + label: key + 1, + } ) ) } + onChange={ this.setRowsPerTable } + /> + + + ); + } + + render() { + const { hiddenLeaderboardKeys, rowsPerTable } = this.state; + return ( + +
+ +
+
+ { ! hiddenLeaderboardKeys.includes( 'top-products' ) && ( + + ) } +
+
+
+
+ ); + } +} + +Leaderboards.propTypes = { + query: PropTypes.object.isRequired, +}; + +export default compose( + withSelect( select => { + const { getCurrentUserData } = select( 'wc-api' ); + const userData = getCurrentUserData(); + + return { + userPrefLeaderboards: userData.dashboard_leaderboards, + userPrefLeaderboardRows: userData.dashboard_leaderboard_rows, + }; + } ), + withDispatch( dispatch => { + const { updateCurrentUserData } = dispatch( 'wc-api' ); + + return { + updateCurrentUserData, + }; + } ) +)( Leaderboards ); diff --git a/plugins/woocommerce-admin/client/dashboard/leaderboards/style.scss b/plugins/woocommerce-admin/client/dashboard/leaderboards/style.scss new file mode 100644 index 00000000000..20c743c5647 --- /dev/null +++ b/plugins/woocommerce-admin/client/dashboard/leaderboards/style.scss @@ -0,0 +1,11 @@ +/** @format */ + +.woocommerce-dashboard__dashboard-leaderboards { + .components-base-control__field { + width: 100%; + } + .components-select-control__input { + border: 1px solid $core-grey-light-700; + height: 34px; + } +} diff --git a/plugins/woocommerce-admin/client/dashboard/top-selling-products/index.js b/plugins/woocommerce-admin/client/dashboard/leaderboards/top-selling-products.js similarity index 93% rename from plugins/woocommerce-admin/client/dashboard/top-selling-products/index.js rename to plugins/woocommerce-admin/client/dashboard/leaderboards/top-selling-products.js index 5732ede5303..4deb1c280b6 100644 --- a/plugins/woocommerce-admin/client/dashboard/top-selling-products/index.js +++ b/plugins/woocommerce-admin/client/dashboard/leaderboards/top-selling-products.js @@ -16,7 +16,7 @@ import { getAdminLink } from '@woocommerce/navigation'; * Internal dependencies */ import { numberFormat } from 'lib/number'; -import Leaderboard from 'dashboard/leaderboard'; +import Leaderboard from 'analytics/components/leaderboard'; export class TopSellingProducts extends Component { constructor( props ) { @@ -88,10 +88,11 @@ export class TopSellingProducts extends Component { } render() { + const { query, totalRows } = this.props; const tableQuery = { orderby: 'items_sold', order: 'desc', - per_page: 5, //TODO replace with user configured leaderboard per page value. + per_page: totalRows, extended_info: true, }; @@ -100,7 +101,7 @@ export class TopSellingProducts extends Component { endpoint="products" getHeadersContent={ this.getHeadersContent } getRowsContent={ this.getRowsContent } - query={ this.props.query } + query={ query } tableQuery={ tableQuery } title={ __( 'Top Selling Products', 'wc-admin' ) } /> diff --git a/plugins/woocommerce-admin/client/dashboard/style.scss b/plugins/woocommerce-admin/client/dashboard/style.scss index b55c4093a3d..1a2701f8ac1 100644 --- a/plugins/woocommerce-admin/client/dashboard/style.scss +++ b/plugins/woocommerce-admin/client/dashboard/style.scss @@ -2,11 +2,11 @@ .woocommerce-dashboard__columns { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: calc(50% - #{$gap-large/2}) calc(50% - #{$gap-large/2}); grid-column-gap: $gap-large; @include breakpoint( '<960px' ) { - grid-template-columns: 1fr; + grid-template-columns: 100%; } } diff --git a/plugins/woocommerce-admin/client/header/activity-panel/activity-card/index.js b/plugins/woocommerce-admin/client/header/activity-panel/activity-card/index.js index 010652b2463..47857ecc956 100644 --- a/plugins/woocommerce-admin/client/header/activity-panel/activity-card/index.js +++ b/plugins/woocommerce-admin/client/header/activity-panel/activity-card/index.js @@ -5,7 +5,7 @@ import classnames from 'classnames'; import { cloneElement, Component } from '@wordpress/element'; import Gridicon from 'gridicons'; -import { moment } from '@wordpress/date'; +import moment from 'moment'; import PropTypes from 'prop-types'; /** diff --git a/plugins/woocommerce-admin/client/header/activity-panel/activity-card/test/index.js b/plugins/woocommerce-admin/client/header/activity-panel/activity-card/test/index.js index b643a346fd5..72acb7c6f1d 100644 --- a/plugins/woocommerce-admin/client/header/activity-panel/activity-card/test/index.js +++ b/plugins/woocommerce-admin/client/header/activity-panel/activity-card/test/index.js @@ -5,6 +5,7 @@ import { Button } from '@wordpress/components'; import Gridicon from 'gridicons'; import { shallow } from 'enzyme'; +import moment from 'moment'; /** * Internal dependencies @@ -54,8 +55,7 @@ describe( 'ActivityCard', () => { test( 'should render a timestamp on a card', () => { // We're generating this via moment to ensure it's always "3 days ago". - const threeDaysAgo = wp.date - .moment() + const threeDaysAgo = moment() .subtract( 3, 'days' ) .format(); const card = shallow( diff --git a/plugins/woocommerce-admin/client/store/reports/test/utils.js b/plugins/woocommerce-admin/client/store/reports/test/utils.js index 90e0b604c3c..94c83b46ce9 100644 --- a/plugins/woocommerce-admin/client/store/reports/test/utils.js +++ b/plugins/woocommerce-admin/client/store/reports/test/utils.js @@ -327,7 +327,7 @@ describe( 'getSummaryNumbers()', () => { } ); setGetReportStats( ( endpoint, _query ) => { - if ( '2018-10-10T00:00:00' === _query.after ) { + if ( '2018-10-10T00:00:00+00:00' === _query.after ) { return { data: { totals: totals.primary, diff --git a/plugins/woocommerce-admin/client/store/reports/utils.js b/plugins/woocommerce-admin/client/store/reports/utils.js index a81eb662921..0cb56aecd1e 100644 --- a/plugins/woocommerce-admin/client/store/reports/utils.js +++ b/plugins/woocommerce-admin/client/store/reports/utils.js @@ -4,6 +4,7 @@ * External dependencies */ import { find, forEach, isNull } from 'lodash'; +import moment from 'moment'; /** * WooCommerce dependencies @@ -18,19 +19,21 @@ import { formatCurrency } from '@woocommerce/currency'; import { MAX_PER_PAGE, QUERY_DEFAULTS } from 'store/constants'; import * as categoriesConfig from 'analytics/report/categories/config'; import * as couponsConfig from 'analytics/report/coupons/config'; +import * as customersConfig from 'analytics/report/customers/config'; +import * as downloadsConfig from 'analytics/report/downloads/config'; import * as ordersConfig from 'analytics/report/orders/config'; import * as productsConfig from 'analytics/report/products/config'; import * as taxesConfig from 'analytics/report/taxes/config'; -import * as customersConfig from 'analytics/report/customers/config'; import * as reportsUtils from './utils'; const reportConfigs = { categories: categoriesConfig, coupons: couponsConfig, + customers: customersConfig, + downloads: downloadsConfig, orders: ordersConfig, products: productsConfig, taxes: taxesConfig, - customers: customersConfig, }; export function getFilterQuery( endpoint, query ) { @@ -119,15 +122,18 @@ export function isReportDataEmpty( report ) { * @returns {Object} data request query parameters. */ function getRequestQuery( endpoint, dataType, query ) { - const datesFromQuery = getCurrentDates( query, 'YYYY-MM-DDTHH:00:00' ); + const datesFromQuery = getCurrentDates( query ); const interval = getIntervalForQuery( query ); const filterQuery = getFilterQuery( endpoint, query ); + const end = datesFromQuery[ dataType ].before; + const endingTimeOfDay = end.isSame( moment(), 'day' ) ? 'now' : 'end'; + return { order: 'asc', interval, per_page: MAX_PER_PAGE, - after: datesFromQuery[ dataType ].after, - before: datesFromQuery[ dataType ].before, + after: appendTimestamp( datesFromQuery[ dataType ].after, 'start' ), + before: appendTimestamp( end, endingTimeOfDay ), ...filterQuery, }; } diff --git a/plugins/woocommerce-admin/client/stylesheets/abstracts/_variables.scss b/plugins/woocommerce-admin/client/stylesheets/abstracts/_variables.scss index 5a534b51442..bd882140fbf 100644 --- a/plugins/woocommerce-admin/client/stylesheets/abstracts/_variables.scss +++ b/plugins/woocommerce-admin/client/stylesheets/abstracts/_variables.scss @@ -17,6 +17,7 @@ $gap-smallest: 4px; $spacing: 16px; // Gutenberg Button variables. These are temporary until Gutenberg's variables are exposed. +$border-width: 1px; $default-font-size: 13px; $blue-medium-200: #bfe7f3; $light-gray-500: $core-grey-light-500; diff --git a/plugins/woocommerce-admin/client/wc-api/reports/items/operations.js b/plugins/woocommerce-admin/client/wc-api/reports/items/operations.js index 9ec07af3caa..a850a119c60 100644 --- a/plugins/woocommerce-admin/client/wc-api/reports/items/operations.js +++ b/plugins/woocommerce-admin/client/wc-api/reports/items/operations.js @@ -14,10 +14,6 @@ import { stringifyQuery } from '@woocommerce/navigation'; */ import { getResourceIdentifier, getResourcePrefix } from '../../utils'; import { NAMESPACE } from '../../constants'; -import { SWAGGERNAMESPACE } from 'store/constants'; - -// TODO: Remove once swagger endpoints are phased out. -const swaggerEndpoints = [ 'customers', 'downloads' ]; const typeEndpointMap = { 'report-items-query-orders': 'orders', @@ -42,17 +38,11 @@ function read( resourceNames, fetch = apiFetch ) { const prefix = getResourcePrefix( resourceName ); const endpoint = typeEndpointMap[ prefix ]; const query = getResourceIdentifier( resourceName ); - const fetchArgs = { parse: false, + path: NAMESPACE + '/reports/' + endpoint + stringifyQuery( query ), }; - if ( swaggerEndpoints.indexOf( endpoint ) >= 0 ) { - fetchArgs.url = SWAGGERNAMESPACE + 'reports/' + endpoint + stringifyQuery( query ); - } else { - fetchArgs.path = NAMESPACE + '/reports/' + endpoint + stringifyQuery( query ); - } - try { const response = await fetch( fetchArgs ); const report = await response.json(); diff --git a/plugins/woocommerce-admin/client/wc-api/reports/stats/operations.js b/plugins/woocommerce-admin/client/wc-api/reports/stats/operations.js index 133fff60eac..5d0d8876be3 100644 --- a/plugins/woocommerce-admin/client/wc-api/reports/stats/operations.js +++ b/plugins/woocommerce-admin/client/wc-api/reports/stats/operations.js @@ -16,9 +16,9 @@ import { getResourceIdentifier, getResourcePrefix } from '../../utils'; import { NAMESPACE } from '../../constants'; import { SWAGGERNAMESPACE } from 'store/constants'; -const statEndpoints = [ 'orders', 'revenue', 'products', 'taxes', 'coupons' ]; +const statEndpoints = [ 'coupons', 'downloads', 'orders', 'products', 'revenue', 'taxes' ]; // TODO: Remove once swagger endpoints are phased out. -const swaggerEndpoints = [ 'categories', 'downloads' ]; +const swaggerEndpoints = [ 'categories' ]; const typeEndpointMap = { 'report-stats-query-orders': 'orders', diff --git a/plugins/woocommerce-admin/client/wc-api/user/operations.js b/plugins/woocommerce-admin/client/wc-api/user/operations.js index 1c12e9765c0..2c036b1fadd 100644 --- a/plugins/woocommerce-admin/client/wc-api/user/operations.js +++ b/plugins/woocommerce-admin/client/wc-api/user/operations.js @@ -40,6 +40,11 @@ function updateCurrentUserData( resourceNames, data, fetch ) { 'revenue_report_columns', 'taxes_report_columns', 'variations_report_columns', + 'dashboard_charts', + 'dashboard_chart_type', + 'dashboard_chart_interval', + 'dashboard_leaderboards', + 'dashboard_leaderboard_rows', ]; if ( resourceNames.includes( resourceName ) ) { diff --git a/plugins/woocommerce-admin/docs/components/analytics/report-chart.md b/plugins/woocommerce-admin/docs/components/analytics/report-chart.md index d9b113c7438..efd3381b486 100644 --- a/plugins/woocommerce-admin/docs/components/analytics/report-chart.md +++ b/plugins/woocommerce-admin/docs/components/analytics/report-chart.md @@ -20,6 +20,14 @@ Filters available for that report. Label describing the legend items. +### `mode` + +- Type: String +- Default: null + +`items-comparison` (default) or `time-comparison`, this is used to generate correct +ARIA properties. + ### `path` - **Required** diff --git a/plugins/woocommerce-admin/docs/components/analytics/report-summary.md b/plugins/woocommerce-admin/docs/components/analytics/report-summary.md index 69ac949a780..970d86a5ab8 100644 --- a/plugins/woocommerce-admin/docs/components/analytics/report-summary.md +++ b/plugins/woocommerce-admin/docs/components/analytics/report-summary.md @@ -20,7 +20,10 @@ Properties of all the charts available for that report. - Type: String - Default: null -The endpoint to use in API calls. +The endpoint to use in API calls to populate the Summary Numbers. +For example, if `taxes` is provided, data will be fetched from the report +`taxes` endpoint (ie: `/wc/v3/reports/taxes/stats`). If the provided endpoint +doesn't exist, an error will be shown to the user with `ReportError`. ### `query` diff --git a/plugins/woocommerce-admin/docs/components/analytics/report-table.md b/plugins/woocommerce-admin/docs/components/analytics/report-table.md index d8fd3744cff..6fde1e1e69e 100644 --- a/plugins/woocommerce-admin/docs/components/analytics/report-table.md +++ b/plugins/woocommerce-admin/docs/components/analytics/report-table.md @@ -18,7 +18,11 @@ The key for user preferences settings for column visibility. - Type: String - Default: null -The endpoint to use in API calls. +The endpoint to use in API calls to populate the table rows and summary. +For example, if `taxes` is provided, data will be fetched from the report +`taxes` endpoint (ie: `/wc/v3/reports/taxes` and `/wc/v3/reports/taxes/stats`). +If the provided endpoint doesn't exist, an error will be shown to the user +with `ReportError`. ### `extendItemsMethodNames` diff --git a/plugins/woocommerce-admin/docs/components/packages/chart.md b/plugins/woocommerce-admin/docs/components/packages/chart.md index e1993644997..0b699b2f7c0 100644 --- a/plugins/woocommerce-admin/docs/components/packages/chart.md +++ b/plugins/woocommerce-admin/docs/components/packages/chart.md @@ -85,7 +85,7 @@ A number formatting string, passed to d3Format. ### `mode` -- Type: One of: 'item-comparison', 'time-comparison' +- Type: One of: 'block', 'item-comparison', 'time-comparison' - Default: `'time-comparison'` `item-comparison` (default) or `time-comparison`, this is used to generate correct diff --git a/plugins/woocommerce-admin/docs/components/packages/search.md b/plugins/woocommerce-admin/docs/components/packages/search.md index 0408f6b2c8b..45c13c3fa4c 100644 --- a/plugins/woocommerce-admin/docs/components/packages/search.md +++ b/plugins/woocommerce-admin/docs/components/packages/search.md @@ -24,7 +24,7 @@ Function called when selected results change, passed result list. ### `type` - **Required** -- Type: One of: 'countries', 'coupons', 'customers', 'emails', 'orders', 'products', 'product_cats', 'taxes', 'usernames', 'variations' +- Type: One of: 'categories', 'countries', 'coupons', 'customers', 'downloadIps', 'emails', 'orders', 'products', 'taxes', 'usernames', 'variations' - Default: null The object type to be used in searching. diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php index 645ff5e3b04..83c9628168f 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-controller.php @@ -73,6 +73,10 @@ class WC_Admin_REST_Reports_Controller extends WC_REST_Reports_Controller { public function get_items( $request ) { $data = array(); $reports = array( + array( + 'slug' => 'performance-indicators', + 'description' => __( 'Batch endpoint for getting specific performance indicators from `stats` endpoints.', 'wc-admin' ), + ), array( 'slug' => 'revenue/stats', 'description' => __( 'Stats about revenue.', 'wc-admin' ), diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-controller.php index c387061707d..fb6e6c05e7f 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-coupons-controller.php @@ -262,6 +262,7 @@ class WC_Admin_REST_Reports_Coupons_Controller extends WC_REST_Reports_Controlle 'default' => 'coupon_id', 'enum' => array( 'coupon_id', + 'code', 'amount', 'orders_count', ), diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-customers-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-customers-controller.php index 6165b3a97f6..523fba26387 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-customers-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-customers-controller.php @@ -38,26 +38,33 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control * @return array */ protected function prepare_reports_query( $request ) { - $args = array(); - $args['before'] = $request['before']; - $args['after'] = $request['after']; - $args['page'] = $request['page']; - $args['per_page'] = $request['per_page']; - $args['name'] = $request['name']; - $args['username'] = $request['username']; - $args['email'] = $request['email']; - $args['country'] = $request['country']; - $args['last_active_before'] = $request['last_active_before']; - $args['last_active_after'] = $request['last_active_after']; - $args['order_count_min'] = $request['order_count_min']; - $args['order_count_max'] = $request['order_count_max']; - $args['order_count_between'] = $request['order_count_between']; - $args['total_spend_min'] = $request['total_spend_min']; - $args['total_spend_max'] = $request['total_spend_max']; - $args['total_spend_between'] = $request['total_spend_between']; - $args['avg_order_value_min'] = $request['avg_order_value_min']; - $args['avg_order_value_max'] = $request['avg_order_value_max']; - $args['avg_order_value_between'] = $request['avg_order_value_between']; + $args = array(); + $args['registered_before'] = $request['registered_before']; + $args['registered_after'] = $request['registered_after']; + $args['page'] = $request['page']; + $args['per_page'] = $request['per_page']; + $args['order'] = $request['order']; + $args['orderby'] = $request['orderby']; + $args['match'] = $request['match']; + $args['name'] = $request['name']; + $args['username'] = $request['username']; + $args['email'] = $request['email']; + $args['country'] = $request['country']; + $args['last_active_before'] = $request['last_active_before']; + $args['last_active_after'] = $request['last_active_after']; + $args['orders_count_min'] = $request['orders_count_min']; + $args['orders_count_max'] = $request['orders_count_max']; + $args['total_spend_min'] = $request['total_spend_min']; + $args['total_spend_max'] = $request['total_spend_max']; + $args['avg_order_value_min'] = $request['avg_order_value_min']; + $args['avg_order_value_max'] = $request['avg_order_value_max']; + $args['last_order_before'] = $request['last_order_before']; + $args['last_order_after'] = $request['last_order_after']; + + $between_params = array( 'orders_count', 'total_spend', 'avg_order_value' ); + $normalized = WC_Admin_Reports_Interval::normalize_between_params( $request, $between_params ); + $args = array_merge( $args, $normalized ); + return $args; } @@ -69,19 +76,20 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control */ public function get_items( $request ) { $query_args = $this->prepare_reports_query( $request ); - $customers_query = new WC_Reports_Orders_Stats_Query( $query_args ); // @todo change to correct class. + $customers_query = new WC_Admin_Reports_Customers_Query( $query_args ); $report_data = $customers_query->get_data(); - $out_data = array( - 'totals' => get_object_vars( $report_data->totals ), - 'customers' => array(), - ); - foreach ( $report_data->customers as $customer_data ) { - $item_data = $this->prepare_item_for_response( (object) $customer_data, $request ); - $out_data['customers'][] = $item_data; + + $data = array(); + + foreach ( $report_data->data as $customer_data ) { + $item = $this->prepare_item_for_response( $customer_data, $request ); + $data[] = $this->prepare_response_for_collection( $item ); } - $response = rest_ensure_response( $out_data ); + + $response = rest_ensure_response( $data ); $response->header( 'X-WP-Total', (int) $report_data->total ); $response->header( 'X-WP-TotalPages', (int) $report_data->pages ); + $page = $report_data->page_no; $max_pages = $report_data->pages; $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) ); @@ -98,21 +106,26 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } + return $response; } /** * Prepare a report object for serialization. * - * @param stdClass $report Report data. + * @param array $report Report data. * @param WP_REST_Request $request Request object. * @return WP_REST_Response */ public function prepare_item_for_response( $report, $request ) { - $data = get_object_vars( $report ); - $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; - $data = $this->add_additional_fields_to_object( $data, $request ); - $data = $this->filter_response_by_context( $data, $context ); + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $report, $request ); + $data['date_registered_gmt'] = wc_rest_prepare_date_response( $data['date_registered'] ); + $data['date_registered'] = wc_rest_prepare_date_response( $data['date_registered'], false ); + $data['date_last_active_gmt'] = wc_rest_prepare_date_response( $data['date_last_active'] ); + $data['date_last_active'] = wc_rest_prepare_date_response( $data['date_last_active'], false ); + $data = $this->filter_response_by_context( $data, $context ); + // Wrap the data in a response object. $response = rest_ensure_response( $data ); $response->add_links( $this->prepare_links( $report ) ); @@ -131,16 +144,19 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control /** * Prepare links for the request. * - * @param WC_Reports_Query $object Object data. + * @param array $object Object data. * @return array */ protected function prepare_links( $object ) { - $links = array( + if ( empty( $object['user_id'] ) ) { + return array(); + } + + return array( 'customer' => array( - 'href' => rest_url( sprintf( '/%s/customers/%d', $this->namespace, $object->customer_id ) ), + 'href' => rest_url( sprintf( '/%s/customers/%d', $this->namespace, $object['user_id'] ) ), ), ); - return $links; } /** @@ -154,18 +170,60 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control 'title' => 'report_customers', 'type' => 'object', 'properties' => array( - 'id' => array( - 'description' => __( 'ID.', 'wc-admin' ), + 'customer_id' => array( + 'description' => __( 'Customer ID.', 'wc-admin' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'customer_id' => array( - 'description' => __( 'Customer ID.', 'wc-admin' ), + 'user_id' => array( + 'description' => __( 'User ID.', 'wc-admin' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), + 'name' => array( + 'description' => __( 'Name.', 'wc-admin' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'username' => array( + 'description' => __( 'Username.', 'wc-admin' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'country' => array( + 'description' => __( 'Country.', 'wc-admin' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'city' => array( + 'description' => __( 'City.', 'wc-admin' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'postcode' => array( + 'description' => __( 'Postal code.', 'wc-admin' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_registered' => array( + 'description' => __( 'Date registered.', 'wc-admin' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_registered_gmt' => array( + 'description' => __( 'Date registered GMT.', 'wc-admin' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), 'date_last_active' => array( 'description' => __( 'Date last active.', 'wc-admin' ), 'type' => 'date-time', @@ -209,14 +267,14 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control public function get_collection_params() { $params = array(); $params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); - $params['before'] = array( - 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'wc-admin' ), + $params['registered_before'] = array( + 'description' => __( 'Limit response to objects registered before (or at) a given ISO8601 compliant datetime.', 'wc-admin' ), 'type' => 'string', 'format' => 'date-time', 'validate_callback' => 'rest_validate_request_arg', ); - $params['after'] = array( - 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'wc-admin' ), + $params['registered_after'] = array( + 'description' => __( 'Limit response to objects registered after (or at) a given ISO8601 compliant datetime.', 'wc-admin' ), 'type' => 'string', 'format' => 'date-time', 'validate_callback' => 'rest_validate_request_arg', @@ -238,7 +296,42 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); - $params['name'] = array( + $params['order'] = array( + 'description' => __( 'Order sort attribute ascending or descending.', 'wc-admin' ), + 'type' => 'string', + 'default' => 'desc', + 'enum' => array( 'asc', 'desc' ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['orderby'] = array( + 'description' => __( 'Sort collection by object attribute.', 'wc-admin' ), + 'type' => 'string', + 'default' => 'date_registered', + 'enum' => array( + 'username', + 'name', + 'country', + 'city', + 'postcode', + 'date_registered', + 'date_last_active', + 'orders_count', + 'total_spend', + 'avg_order_value', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['match'] = array( + 'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: status_is, status_is_not, product_includes, product_excludes, coupon_includes, coupon_excludes, customer, categories', 'wc-admin' ), + 'type' => 'string', + 'default' => 'all', + 'enum' => array( + 'all', + 'any', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['name'] = array( 'description' => __( 'Limit response to objects with a specfic customer name.', 'wc-admin' ), 'type' => 'string', 'validate_callback' => 'rest_validate_request_arg', @@ -270,22 +363,34 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control 'format' => 'date-time', 'validate_callback' => 'rest_validate_request_arg', ); - $params['order_count_min'] = array( + $params['registered_before'] = array( + 'description' => __( 'Limit response to objects registered before (or at) a given ISO8601 compliant datetime.', 'wc-admin' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['registered_after'] = array( + 'description' => __( 'Limit response to objects registered after (or at) a given ISO8601 compliant datetime.', 'wc-admin' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['orders_count_min'] = array( 'description' => __( 'Limit response to objects with an order count greater than or equal to given integer.', 'wc-admin' ), 'type' => 'integer', 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); - $params['order_count_max'] = array( + $params['orders_count_max'] = array( 'description' => __( 'Limit response to objects with an order count less than or equal to given integer.', 'wc-admin' ), 'type' => 'integer', 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); - $params['order_count_between'] = array( + $params['orders_count_between'] = array( 'description' => __( 'Limit response to objects with an order count between two given integers.', 'wc-admin' ), 'type' => 'array', - 'validate_callback' => 'rest_validate_request_arg', + 'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_arg' ), ); $params['total_spend_min'] = array( 'description' => __( 'Limit response to objects with a total order spend greater than or equal to given number.', 'wc-admin' ), @@ -300,7 +405,7 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control $params['total_spend_between'] = array( 'description' => __( 'Limit response to objects with a total order spend between two given numbers.', 'wc-admin' ), 'type' => 'array', - 'validate_callback' => 'rest_validate_request_arg', + 'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_arg' ), ); $params['avg_order_value_min'] = array( 'description' => __( 'Limit response to objects with an average order spend greater than or equal to given number.', 'wc-admin' ), @@ -315,6 +420,18 @@ class WC_Admin_REST_Reports_Customers_Controller extends WC_REST_Reports_Control $params['avg_order_value_between'] = array( 'description' => __( 'Limit response to objects with an average order spend between two given numbers.', 'wc-admin' ), 'type' => 'array', + 'validate_callback' => array( 'WC_Admin_Reports_Interval', 'rest_validate_between_arg' ), + ); + $params['last_order_before'] = array( + 'description' => __( 'Limit response to objects with last order before (or at) a given ISO8601 compliant datetime.', 'wc-admin' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['last_order_after'] = array( + 'description' => __( 'Limit response to objects with last order after (or at) a given ISO8601 compliant datetime.', 'wc-admin' ), + 'type' => 'string', + 'format' => 'date-time', 'validate_callback' => 'rest_validate_request_arg', ); return $params; diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-controller.php index 3dc6aaf8bf9..0265cc21a76 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-controller.php @@ -107,8 +107,10 @@ class WC_Admin_REST_Reports_Downloads_Controller extends WC_REST_Reports_Control $product_id = intval( $data['product_id'] ); $_product = wc_get_product( $product_id ); $file_path = $_product->get_file_download_path( $data['download_id'] ); + $filename = basename( $file_path ); $response->data['file_name'] = apply_filters( 'woocommerce_file_download_filename', $filename, $product_id ); + $response->data['file_path'] = $file_path; /** * Filter a report returned from the API. @@ -190,6 +192,12 @@ class WC_Admin_REST_Reports_Downloads_Controller extends WC_REST_Reports_Control 'context' => array( 'view', 'edit' ), 'description' => __( 'File name.', 'wc-admin' ), ), + 'file_path' => array( + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit' ), + 'description' => __( 'File URL.', 'wc-admin' ), + ), 'product_id' => array( 'type' => 'integer', 'readonly' => true, @@ -270,6 +278,7 @@ class WC_Admin_REST_Reports_Downloads_Controller extends WC_REST_Reports_Control 'default' => 'date', 'enum' => array( 'date', + 'product', ), 'validate_callback' => 'rest_validate_request_arg', ); @@ -292,7 +301,6 @@ class WC_Admin_REST_Reports_Downloads_Controller extends WC_REST_Reports_Control 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', - ); $params['product_excludes'] = array( 'description' => __( 'Limit result set to items that don\'t have the specified product(s) assigned.', 'wc-admin' ), diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php index a1339e34806..8726c677f4f 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php @@ -30,4 +30,340 @@ class WC_Admin_REST_Reports_Downloads_Stats_Controller extends WC_REST_Reports_C * @var string */ protected $rest_base = 'reports/downloads/stats'; + + /** + * Maps query arguments from the REST request. + * + * @param array $request Request array. + * @return array + */ + protected function prepare_reports_query( $request ) { + $args = array(); + $args['before'] = $request['before']; + $args['after'] = $request['after']; + $args['interval'] = $request['interval']; + $args['page'] = $request['page']; + $args['per_page'] = $request['per_page']; + $args['orderby'] = $request['orderby']; + $args['order'] = $request['order']; + $args['match'] = $request['match']; + $args['product_includes'] = (array) $request['product_includes']; + $args['product_excludes'] = (array) $request['product_excludes']; + $args['order_includes'] = (array) $request['order_includes']; + $args['order_excludes'] = (array) $request['order_excludes']; + $args['ip_address_includes'] = (array) $request['ip_address_includes']; + $args['ip_address_excludes'] = (array) $request['ip_address_excludes']; + + return $args; + } + + /** + * Get all reports. + * + * @param WP_REST_Request $request Request data. + * @return array|WP_Error + */ + public function get_items( $request ) { + $query_args = $this->prepare_reports_query( $request ); + $downloads_query = new WC_Admin_Reports_Downloads_Stats_Query( $query_args ); + $report_data = $downloads_query->get_data(); + + $out_data = array( + 'totals' => get_object_vars( $report_data->totals ), + 'intervals' => array(), + ); + + foreach ( $report_data->intervals as $interval_data ) { + $item = $this->prepare_item_for_response( $interval_data, $request ); + $out_data['intervals'][] = $this->prepare_response_for_collection( $item ); + } + + $response = rest_ensure_response( $out_data ); + $response->header( 'X-WP-Total', (int) $report_data->total ); + $response->header( 'X-WP-TotalPages', (int) $report_data->pages ); + + $page = $report_data->page_no; + $max_pages = $report_data->pages; + $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) ); + if ( $page > 1 ) { + $prev_page = $page - 1; + if ( $prev_page > $max_pages ) { + $prev_page = $max_pages; + } + $prev_link = add_query_arg( 'page', $prev_page, $base ); + $response->link_header( 'prev', $prev_link ); + } + if ( $max_pages > $page ) { + $next_page = $page + 1; + $next_link = add_query_arg( 'page', $next_page, $base ); + $response->link_header( 'next', $next_link ); + } + + return $response; + } + + /** + * Prepare a report object for serialization. + * + * @param Array $report Report data. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response + */ + public function prepare_item_for_response( $report, $request ) { + $data = $report; + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + // Wrap the data in a response object. + $response = rest_ensure_response( $data ); + + /** + * Filter a report returned from the API. + * + * Allows modification of the report data right before it is returned. + * + * @param WP_REST_Response $response The response object. + * @param object $report The original report object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'woocommerce_rest_prepare_report_downloads_stats', $response, $report, $request ); + } + + /** + * Get the Report's schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $totals = array( + 'download_count' => array( + 'description' => __( 'Number of downloads.', 'wc-admin' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ); + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'report_orders_stats', + 'type' => 'object', + 'properties' => array( + 'totals' => array( + 'description' => __( 'Totals data.', 'wc-admin' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'properties' => $totals, + ), + 'intervals' => array( + 'description' => __( 'Reports data grouped by intervals.', 'wc-admin' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'interval' => array( + 'description' => __( 'Type of interval.', 'wc-admin' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'enum' => array( 'day', 'week', 'month', 'year' ), + ), + 'date_start' => array( + 'description' => __( "The date the report start, in the site's timezone.", 'wc-admin' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_start_gmt' => array( + 'description' => __( 'The date the report start, as GMT.', 'wc-admin' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_end' => array( + 'description' => __( "The date the report end, in the site's timezone.", 'wc-admin' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_end_gmt' => array( + 'description' => __( 'The date the report end, as GMT.', 'wc-admin' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'subtotals' => array( + 'description' => __( 'Interval subtotals.', 'wc-admin' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'properties' => $totals, + ), + ), + ), + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } + + /** + * Get the query params for collections. + * + * @return array + */ + public function get_collection_params() { + $params = array(); + $params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); + $params['page'] = array( + 'description' => __( 'Current page of the collection.', 'wc-admin' ), + 'type' => 'integer', + 'default' => 1, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + 'minimum' => 1, + ); + $params['per_page'] = array( + 'description' => __( 'Maximum number of items to be returned in result set.', 'wc-admin' ), + 'type' => 'integer', + 'default' => 10, + 'minimum' => 1, + 'maximum' => 100, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['after'] = array( + 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'wc-admin' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['before'] = array( + 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'wc-admin' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['order'] = array( + 'description' => __( 'Order sort attribute ascending or descending.', 'wc-admin' ), + 'type' => 'string', + 'default' => 'desc', + 'enum' => array( 'asc', 'desc' ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['orderby'] = array( + 'description' => __( 'Sort collection by object attribute.', 'wc-admin' ), + 'type' => 'string', + 'default' => 'date', + 'enum' => array( + 'date', + 'download_count', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['interval'] = array( + 'description' => __( 'Time interval to use for buckets in the returned data.', 'wc-admin' ), + 'type' => 'string', + 'default' => 'week', + 'enum' => array( + 'hour', + 'day', + 'week', + 'month', + 'quarter', + 'year', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['match'] = array( + 'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: status_is, status_is_not, product_includes, product_excludes, coupon_includes, coupon_excludes, customer, categories', 'wc-admin' ), + 'type' => 'string', + 'default' => 'all', + 'enum' => array( + 'all', + 'any', + ), + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['product_includes'] = array( + 'description' => __( 'Limit result set to items that have the specified product(s) assigned.', 'wc-admin' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + 'sanitize_callback' => 'wp_parse_id_list', + + ); + $params['product_excludes'] = array( + 'description' => __( 'Limit result set to items that don\'t have the specified product(s) assigned.', 'wc-admin' ), + 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + 'sanitize_callback' => 'wp_parse_id_list', + ); + $params['order_includes'] = array( + 'description' => __( 'Limit result set to items that have the specified order ids.', 'wc-admin' ), + 'type' => 'array', + 'sanitize_callback' => 'wp_parse_id_list', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'integer', + ), + ); + $params['order_excludes'] = array( + 'description' => __( 'Limit result set to items that don\'t have the specified order ids.', 'wc-admin' ), + 'type' => 'array', + 'sanitize_callback' => 'wp_parse_id_list', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'integer', + ), + ); + $params['user_includes'] = array( + 'description' => __( 'Limit response to objects that have the specified user ids.', 'wc-admin' ), + 'type' => 'array', + 'sanitize_callback' => 'wp_parse_id_list', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'integer', + ), + ); + $params['user_excludes'] = array( + 'description' => __( 'Limit response to objects that don\'t have the specified user ids.', 'wc-admin' ), + 'type' => 'array', + 'sanitize_callback' => 'wp_parse_id_list', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'integer', + ), + ); + $params['ip_address_includes'] = array( + 'description' => __( 'Limit response to objects that have a specified ip address.', 'wc-admin' ), + 'type' => 'array', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'string', + ), + ); + + $params['ip_address_excludes'] = array( + 'description' => __( 'Limit response to objects that don\'t have a specified ip address.', 'wc-admin' ), + 'type' => 'array', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'string', + ), + ); + + return $params; + } } diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-performance-indicators-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-performance-indicators-controller.php new file mode 100644 index 00000000000..d3e59938381 --- /dev/null +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-performance-indicators-controller.php @@ -0,0 +1,287 @@ +get_data(); + $allowed_stats = array(); + if ( 200 !== $response->get_status() ) { + return new WP_Error( 'woocommerce_reports_performance_indicators_result_failed', __( 'Sorry, fetching performance indicators failed.', 'wc-admin' ) ); + } + + foreach ( $endpoints as $endpoint ) { + if ( '/stats' === substr( $endpoint['slug'], -6 ) ) { + $request = new WP_REST_Request( 'OPTIONS', '/wc/v3/reports/' . $endpoint['slug'] ); + $response = rest_do_request( $request ); + $data = $response->get_data(); + $prefix = substr( $endpoint['slug'], 0, -6 ); + + if ( empty( $data['schema']['properties']['totals']['properties'] ) ) { + continue; + } + + foreach ( $data['schema']['properties']['totals']['properties'] as $property_key => $schema_info ) { + $allowed_stats[] = $prefix . '/' . $property_key; + } + } + } + + /** + * Filter the list of allowed stats that can be returned via the performance indiciator endpoint. + * + * @param array $allowed_stats The list of allowed stats. + */ + return apply_filters( 'woocommerce_admin_performance_indicators_allowed_stats', $allowed_stats ); + } + + /** + * Get all reports. + * + * @param WP_REST_Request $request Request data. + * @return array|WP_Error + */ + public function get_items( $request ) { + $allowed_stats = $this->get_allowed_stats(); + + if ( is_wp_error( $allowed_stats ) ) { + return $allowed_stats; + } + + $query_args = $this->prepare_reports_query( $request ); + if ( empty( $query_args['stats'] ) ) { + return new WP_Error( 'woocommerce_reports_performance_indicators_empty_query', __( 'A list of stats to query must be provided.', 'wc-admin' ), 400 ); + } + + $stats = array(); + foreach ( $query_args['stats'] as $stat_request ) { + $is_error = false; + + $pieces = explode( '/', $stat_request ); + $endpoint = $pieces[0]; + $stat = $pieces[1]; + + if ( ! in_array( $stat_request, $allowed_stats ) ) { + continue; + } + + /** + * Filter the list of allowed endpoints, so that data can be loaded from extensions rather than core. + * These should be in the format of slug => path. Example: `bookings` => `/wc-bookings/v1/reports/bookings/stats`. + * + * @param array $endpoints The list of allowed endpoints. + */ + $stats_endpoints = apply_filters( 'woocommerce_admin_performance_indicators_stats_endpoints', array() ); + if ( ! empty( $stats_endpoints [ $endpoint ] ) ) { + $request_url = $stats_endpoints [ $endpoint ]; + } else { + $request_url = '/wc/v3/reports/' . $endpoint . '/stats'; + } + + $request = new WP_REST_Request( 'GET', $request_url ); + $request->set_param( 'before', $query_args['before'] ); + $request->set_param( 'after', $query_args['after'] ); + + $response = rest_do_request( $request ); + $data = $response->get_data(); + + if ( 200 !== $response->get_status() || empty( $data['totals'][ $stat ] ) ) { + $stats[] = (object) array( + 'stat' => $stat_request, + 'value' => null, + ); + continue; + } + + $stats[] = (object) array( + 'stat' => $stat_request, + 'value' => $data['totals'][ $stat ], + ); + } + + $objects = array(); + foreach ( $stats as $stat ) { + $data = $this->prepare_item_for_response( $stat, $request ); + $objects[] = $this->prepare_response_for_collection( $data ); + } + + $response = rest_ensure_response( $objects ); + $response->header( 'X-WP-Total', count( $stats ) ); + $response->header( 'X-WP-TotalPages', 1 ); + + $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) ); + + return $response; + } + + /** + * Prepare a report object for serialization. + * + * @param stdClass $stat_data Report data. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response + */ + public function prepare_item_for_response( $stat_data, $request ) { + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $stat_data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + // Wrap the data in a response object. + $response = rest_ensure_response( $data ); + + $response->add_links( $this->prepare_links( $data ) ); + + /** + * Filter a report returned from the API. + * + * Allows modification of the report data right before it is returned. + * + * @param WP_REST_Response $response The response object. + * @param object $report The original report object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'woocommerce_rest_prepare_report_performance_indicators', $response, $stat_data, $request ); + } + + /** + * Prepare links for the request. + * + * @param WC_Admin_Reports_Query $object Object data. + * @return array + */ + protected function prepare_links( $object ) { + $pieces = explode( '/', $object->stat ); + $endpoint = $pieces[0]; + $stat = $pieces[1]; + + $links = array( + 'report' => array( + 'href' => rest_url( sprintf( '/%s/reports/%s/stats', $this->namespace, $endpoint ) ), + ), + ); + + return $links; + } + + /** + * Get the Report's schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'report_performance_indicator', + 'type' => 'object', + 'properties' => array( + 'stat' => array( + 'description' => __( 'Unique identifier for the resource.', 'wc-admin' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'value' => array( + 'description' => __( 'Value of the stat. Returns null if the stat does not exist or cannot be loaded.', 'wc-admin' ), + 'type' => 'number', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } + + /** + * Get the query params for collections. + * + * @return array + */ + public function get_collection_params() { + $allowed_stats = $this->get_allowed_stats(); + if ( is_wp_error( $allowed_stats ) ) { + $allowed_stats = __( 'There was an issue loading the report endpoints', 'wc-admin' ); + } else { + $allowed_stats = implode( ', ', $allowed_stats ); + } + + $params = array(); + $params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); + $params['stats'] = array( + 'description' => sprintf( + /* translators: Allowed values is a list of stat endpoints. */ + __( 'Limit response to specific report stats. Allowed values: %s.', 'wc-admin' ), + $allowed_stats + ), + 'type' => 'array', + 'validate_callback' => 'rest_validate_request_arg', + 'items' => array( + 'type' => 'string', + ), + ); + $params['after'] = array( + 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.', 'wc-admin' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['before'] = array( + 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.', 'wc-admin' ), + 'type' => 'string', + 'format' => 'date-time', + 'validate_callback' => 'rest_validate_request_arg', + ); + return $params; + } +} diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php index 4308becd3c2..5ce54e41933 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php @@ -291,6 +291,7 @@ class WC_Admin_REST_Reports_Products_Controller extends WC_REST_Reports_Controll 'orders_count', 'items_sold', 'product_name', + 'sku', ), 'validate_callback' => 'rest_validate_request_arg', ); diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-stock-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-stock-controller.php index 4262c1d7ebc..9ba7c31a655 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-stock-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-stock-controller.php @@ -58,8 +58,9 @@ class WC_Admin_REST_Reports_Stock_Controller extends WC_REST_Reports_Controller $args['orderby'] = 'post__in'; } elseif ( 'id' === $args['orderby'] ) { $args['orderby'] = 'ID'; // ID must be capitalized. - } elseif ( 'slug' === $args['orderby'] ) { - $args['orderby'] = 'name'; + } elseif ( 'sku' === $args['orderby'] ) { + $args['meta_key'] = '_sku'; // WPCS: slow query ok. + $args['orderby'] = 'meta_value'; } $args['post_type'] = array( 'product', 'product_variation' ); @@ -359,7 +360,7 @@ class WC_Admin_REST_Reports_Stock_Controller extends WC_REST_Reports_Controller 'id', 'include', 'title', - 'slug', + 'sku', ), 'validate_callback' => 'rest_validate_request_arg', ); diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php index 82ebad4ba7a..0761df4c61c 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-api-init.php @@ -29,6 +29,10 @@ class WC_Admin_Api_Init { // Initialize Orders data store class's static vars. add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'orders_data_store_init' ), 20 ); + // Initialize Customers Report data store sync hooks. + // Note: we need to hook into 'wp' before `wc_current_user_is_active`. + // See: https://github.com/woocommerce/woocommerce/blob/942615101ba00c939c107c3a4820c3d466864872/includes/wc-user-functions.php#L749. + add_action( 'wp', array( 'WC_Admin_Api_Init', 'customers_report_data_store_init' ), 9 ); } /** @@ -54,6 +58,8 @@ class WC_Admin_Api_Init { require_once dirname( __FILE__ ) . '/class-wc-admin-reports-coupons-query.php'; require_once dirname( __FILE__ ) . '/class-wc-admin-reports-coupons-stats-query.php'; require_once dirname( __FILE__ ) . '/class-wc-admin-reports-downloads-query.php'; + require_once dirname( __FILE__ ) . '/class-wc-admin-reports-downloads-stats-query.php'; + require_once dirname( __FILE__ ) . '/class-wc-admin-reports-customers-query.php'; // Data stores. require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-data-store.php'; @@ -67,6 +73,8 @@ class WC_Admin_Api_Init { require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-coupons-data-store.php'; require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-coupons-stats-data-store.php'; require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-downloads-data-store.php'; + require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-downloads-stats-data-store.php'; + require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-reports-customers-data-store.php'; // Data triggers. require_once dirname( __FILE__ ) . '/data-stores/class-wc-admin-notes-data-store.php'; @@ -100,36 +108,44 @@ class WC_Admin_Api_Init { require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-products-controller.php'; require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-variations-controller.php'; require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-products-stats-controller.php'; + require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-performance-indicators-controller.php'; require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-revenue-stats-controller.php'; require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-controller.php'; require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-taxes-stats-controller.php'; require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-stock-controller.php'; - require_once dirname( __FILE__ ) . '/api/class-wc-admin-rest-reports-downloads-controller.php'; - $controllers = array( - 'WC_Admin_REST_Admin_Notes_Controller', - 'WC_Admin_REST_Customers_Controller', - 'WC_Admin_REST_Data_Controller', - 'WC_Admin_REST_Data_Download_Ips_Controller', - 'WC_Admin_REST_Orders_Controller', - 'WC_Admin_REST_Products_Controller', - 'WC_Admin_REST_Product_Reviews_Controller', - 'WC_Admin_REST_Reports_Controller', - 'WC_Admin_REST_System_Status_Tools_Controller', - 'WC_Admin_REST_Reports_Products_Controller', - 'WC_Admin_REST_Reports_Variations_Controller', - 'WC_Admin_REST_Reports_Products_Stats_Controller', - 'WC_Admin_REST_Reports_Revenue_Stats_Controller', - 'WC_Admin_REST_Reports_Orders_Stats_Controller', - 'WC_Admin_REST_Reports_Categories_Controller', - 'WC_Admin_REST_Reports_Taxes_Controller', - 'WC_Admin_REST_Reports_Taxes_Stats_Controller', - 'WC_Admin_REST_Reports_Coupons_Controller', - 'WC_Admin_REST_Reports_Coupons_Stats_Controller', - 'WC_Admin_REST_Reports_Stock_Controller', - 'WC_Admin_REST_Reports_Downloads_Controller', + $controllers = apply_filters( + 'woocommerce_admin_rest_controllers', + array( + 'WC_Admin_REST_Admin_Notes_Controller', + 'WC_Admin_REST_Customers_Controller', + 'WC_Admin_REST_Data_Controller', + 'WC_Admin_REST_Data_Download_Ips_Controller', + 'WC_Admin_REST_Orders_Controller', + 'WC_Admin_REST_Products_Controller', + 'WC_Admin_REST_Product_Reviews_Controller', + 'WC_Admin_REST_Reports_Controller', + 'WC_Admin_REST_System_Status_Tools_Controller', + 'WC_Admin_REST_Reports_Products_Controller', + 'WC_Admin_REST_Reports_Variations_Controller', + 'WC_Admin_REST_Reports_Products_Stats_Controller', + 'WC_Admin_REST_Reports_Revenue_Stats_Controller', + 'WC_Admin_REST_Reports_Orders_Stats_Controller', + 'WC_Admin_REST_Reports_Categories_Controller', + 'WC_Admin_REST_Reports_Taxes_Controller', + 'WC_Admin_REST_Reports_Taxes_Stats_Controller', + 'WC_Admin_REST_Reports_Coupons_Controller', + 'WC_Admin_REST_Reports_Coupons_Stats_Controller', + 'WC_Admin_REST_Reports_Stock_Controller', + 'WC_Admin_REST_Reports_Downloads_Controller', + 'WC_Admin_REST_Reports_Downloads_Stats_Controller', + 'WC_Admin_REST_Reports_Customers_Controller', + ) ); + // The performance indicators controller must be registered last, after other /stats endpoints have been registered. + $controllers[] = 'WC_Admin_REST_Reports_Performance_Indicators_Controller'; + foreach ( $controllers as $controller ) { $this->$controller = new $controller(); $this->$controller->register_routes(); @@ -258,6 +274,9 @@ class WC_Admin_Api_Init { * Regenerate data for reports. */ public static function regenerate_report_data() { + // Add registered customers to the lookup table before updating order stats + // so that the orders can be associated with the `customer_id` column. + self::customer_lookup_store_init(); WC_Admin_Reports_Orders_Data_Store::queue_order_stats_repopulate_database(); self::order_product_lookup_store_init(); } @@ -328,6 +347,59 @@ class WC_Admin_Api_Init { return true; } + /** + * Init customers report data store. + */ + public static function customers_report_data_store_init() { + WC_Admin_Reports_Customers_Data_Store::init(); + } + + /** + * Init customer lookup store. + * + * @param WC_Background_Updater|null $updater Updater instance. + * @return bool + */ + public static function customer_lookup_store_init( $updater = null ) { + // TODO: this needs to be updated a bit, as it no longer runs as a part of WC_Install, there is no bg updater. + global $wpdb; + + // Backfill customer lookup table with registered customers. + $customer_ids = get_transient( 'wc_update_350_all_customers' ); + + if ( false === $customer_ids ) { + $customer_query = new WP_User_Query( + array( + 'fields' => 'ID', + 'role' => 'customer', + 'number' => -1, + ) + ); + + $customer_ids = $customer_query->get_results(); + + set_transient( 'wc_update_350_all_customers', $customer_ids, DAY_IN_SECONDS ); + } + + // Process customers until close to running out of memory timeouts on large sites then requeue. + foreach ( $customer_ids as $customer_id ) { + $result = WC_Admin_Reports_Customers_Data_Store::update_registered_customer( $customer_id ); + + if ( $result ) { + // Pop the customer ID from the array for updating the transient later should we near memory exhaustion. + unset( $customer_ids[ $customer_id ] ); + } + + if ( $updater instanceof WC_Background_Updater && $updater->is_memory_exceeded() ) { + // Update the transient for the next run to avoid processing the same orders again. + set_transient( 'wc_update_350_all_customers', $customer_ids, DAY_IN_SECONDS ); + return true; + } + } + + return true; + } + /** * Adds data stores. * @@ -338,18 +410,20 @@ class WC_Admin_Api_Init { return array_merge( $data_stores, array( - 'report-revenue-stats' => 'WC_Admin_Reports_Orders_Data_Store', - 'report-orders-stats' => 'WC_Admin_Reports_Orders_Data_Store', - 'report-products' => 'WC_Admin_Reports_Products_Data_Store', - 'report-variations' => 'WC_Admin_Reports_Variations_Data_Store', - 'report-products-stats' => 'WC_Admin_Reports_Products_Stats_Data_Store', - 'report-categories' => 'WC_Admin_Reports_Categories_Data_Store', - 'report-taxes' => 'WC_Admin_Reports_Taxes_Data_Store', - 'report-taxes-stats' => 'WC_Admin_Reports_Taxes_Stats_Data_Store', - 'report-coupons' => 'WC_Admin_Reports_Coupons_Data_Store', - 'report-coupons-stats' => 'WC_Admin_Reports_Coupons_Stats_Data_Store', - 'report-downloads' => 'WC_Admin_Reports_Downloads_Data_Store', - 'admin-note' => 'WC_Admin_Notes_Data_Store', + 'report-revenue-stats' => 'WC_Admin_Reports_Orders_Data_Store', + 'report-orders-stats' => 'WC_Admin_Reports_Orders_Data_Store', + 'report-products' => 'WC_Admin_Reports_Products_Data_Store', + 'report-variations' => 'WC_Admin_Reports_Variations_Data_Store', + 'report-products-stats' => 'WC_Admin_Reports_Products_Stats_Data_Store', + 'report-categories' => 'WC_Admin_Reports_Categories_Data_Store', + 'report-taxes' => 'WC_Admin_Reports_Taxes_Data_Store', + 'report-taxes-stats' => 'WC_Admin_Reports_Taxes_Stats_Data_Store', + 'report-coupons' => 'WC_Admin_Reports_Coupons_Data_Store', + 'report-coupons-stats' => 'WC_Admin_Reports_Coupons_Stats_Data_Store', + 'report-downloads' => 'WC_Admin_Reports_Downloads_Data_Store', + 'report-downloads-stats' => 'WC_Admin_Reports_Downloads_Stats_Data_Store', + 'admin-note' => 'WC_Admin_Notes_Data_Store', + 'report-customers' => 'WC_Admin_Reports_Customers_Data_Store', ) ); } @@ -373,6 +447,7 @@ class WC_Admin_Api_Init { "{$wpdb->prefix}wc_order_coupon_lookup", "{$wpdb->prefix}woocommerce_admin_notes", "{$wpdb->prefix}woocommerce_admin_note_actions", + "{$wpdb->prefix}wc_customer_lookup", ) ); } @@ -401,6 +476,8 @@ class WC_Admin_Api_Init { shipping_total double DEFAULT 0 NOT NULL, net_total double DEFAULT 0 NOT NULL, returning_customer boolean DEFAULT 0 NOT NULL, + status varchar(200) NOT NULL, + customer_id BIGINT UNSIGNED NOT NULL, PRIMARY KEY (order_id), KEY date_created (date_created) ) $collate; @@ -469,6 +546,22 @@ class WC_Admin_Api_Init { PRIMARY KEY (action_id), KEY note_id (note_id) ) $collate; + CREATE TABLE {$wpdb->prefix}wc_customer_lookup ( + customer_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + user_id BIGINT UNSIGNED DEFAULT NULL, + username varchar(60) DEFAULT '' NOT NULL, + first_name varchar(255) NOT NULL, + last_name varchar(255) NOT NULL, + email varchar(100) NOT NULL, + date_last_active timestamp DEFAULT '0000-00-00 00:00:00' NOT NULL, + date_registered timestamp NULL default null, + country char(2) DEFAULT '' NOT NULL, + postcode varchar(20) DEFAULT '' NOT NULL, + city varchar(100) DEFAULT '' NOT NULL, + PRIMARY KEY (customer_id), + UNIQUE KEY user_id (user_id), + KEY email (email) + ) $collate; "; return $tables; @@ -492,17 +585,7 @@ class WC_Admin_Api_Init { // Initialize report tables. add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'order_product_lookup_store_init' ), 20 ); - } - - /** - * Enables the WP REST API for product categories - * - * @param array $args Default arguments for product_cat taxonomy. - * @return array - */ - public static function show_product_categories_in_rest( $args ) { - $args['show_in_rest'] = true; - return $args; + add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'customer_lookup_store_init' ), 20 ); } } diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-customers-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-customers-query.php new file mode 100644 index 00000000000..937a0bf7b42 --- /dev/null +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-customers-query.php @@ -0,0 +1,54 @@ + '2018-07-19 00:00:00', + * 'registered_after' => '2018-07-05 00:00:00', + * 'page' => 2, + * 'avg_order_value_min' => 100, + * 'country' => 'GB', + * ); + * $report = new WC_Admin_Reports_Customers_Query( $args ); + * $mydata = $report->get_data(); + * + * @package WooCommerce Admin/Classes + */ + +defined( 'ABSPATH' ) || exit; + +/** + * WC_Admin_Reports_Customers_Query + */ +class WC_Admin_Reports_Customers_Query extends WC_Admin_Reports_Query { + + /** + * Valid fields for Customers report. + * + * @return array + */ + protected function get_default_query_vars() { + return array( + 'per_page' => get_option( 'posts_per_page' ), // not sure if this should be the default. + 'page' => 1, + 'order' => 'DESC', + 'orderby' => 'date_registered', + 'fields' => '*', + ); + } + + /** + * Get product data based on the current query vars. + * + * @return array + */ + public function get_data() { + $args = apply_filters( 'woocommerce_reports_customers_query_args', $this->get_query_vars() ); + + $data_store = WC_Data_Store::load( 'report-customers' ); + $results = $data_store->get_data( $args ); + return apply_filters( 'woocommerce_reports_customers_select_query', $results, $args ); + } + +} diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-downloads-stats-query.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-downloads-stats-query.php new file mode 100644 index 00000000000..21ea14fc7c7 --- /dev/null +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-downloads-stats-query.php @@ -0,0 +1,37 @@ +get_query_vars() ); + + $data_store = WC_Data_Store::load( 'report-downloads-stats' ); + $results = $data_store->get_data( $args ); + return apply_filters( 'woocommerce_reports_downloads_stats_select_query', $results, $args ); + } + +} diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-reports-interval.php b/plugins/woocommerce-admin/includes/class-wc-admin-reports-interval.php index 936186294dc..672d1906fce 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-reports-interval.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-reports-interval.php @@ -1,6 +1,6 @@ prefix . self::TABLE_NAME; $sql_query_params = $this->get_time_period_sql_params( $query_args, $order_product_lookup_table ); - // Limit is left out here so that the grouping in code by PHP can be applied correctly. - $sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) ); // join wp_order_product_lookup_table with relationships and taxonomies // @TODO: How to handle custom product tables? $sql_query_params['from_clause'] .= " LEFT JOIN {$wpdb->prefix}term_relationships ON {$order_product_lookup_table}.product_id = {$wpdb->prefix}term_relationships.object_id"; $sql_query_params['from_clause'] .= " LEFT JOIN {$wpdb->prefix}term_taxonomy ON {$wpdb->prefix}term_relationships.term_taxonomy_id = {$wpdb->prefix}term_taxonomy.term_taxonomy_id"; + // Limit is left out here so that the grouping in code by PHP can be applied correctly. + // This also needs to be put after the term_taxonomy JOIN so that we can match the correct term name. + $sql_query_params = $this->get_order_by_params( $query_args, $sql_query_params ); + $included_categories = $this->get_included_categories( $query_args ); if ( $included_categories ) { $sql_query_params['where_clause'] .= " AND {$wpdb->prefix}term_taxonomy.term_id IN ({$included_categories})"; @@ -100,6 +102,52 @@ class WC_Admin_Reports_Categories_Data_Store extends WC_Admin_Reports_Data_Store return $sql_query_params; } + /** + * Fills ORDER BY clause of SQL request based on user supplied parameters. + * + * @param array $query_args Parameters supplied by the user. + * @param array $sql_query Current SQL query array. + * @return array + */ + protected function get_order_by_params( $query_args, $sql_query ) { + global $wpdb; + $lookup_table = $wpdb->prefix . self::TABLE_NAME; + + $sql_query['order_by_clause'] = ''; + if ( isset( $query_args['orderby'] ) ) { + $sql_query['order_by_clause'] = $this->normalize_order_by( $query_args['orderby'] ); + } + + if ( false !== strpos( $sql_query['order_by_clause'], '_terms' ) ) { + $sql_query['from_clause'] .= " JOIN {$wpdb->prefix}terms AS _terms ON {$wpdb->prefix}term_taxonomy.term_id = _terms.term_id"; + } + + if ( isset( $query_args['order'] ) ) { + $sql_query['order_by_clause'] .= ' ' . $query_args['order']; + } else { + $sql_query['order_by_clause'] .= ' DESC'; + } + + return $sql_query; + } + + /** + * Maps ordering specified by the user to columns in the database/fields in the data. + * + * @param string $order_by Sorting criterion. + * @return string + */ + protected function normalize_order_by( $order_by ) { + if ( 'date' === $order_by ) { + return 'time_interval'; + } + if ( 'category' === $order_by ) { + return '_terms.name'; + } + return $order_by; + } + + /** * Returns comma separated ids of included categories, based on query arguments from the user. * @@ -115,39 +163,6 @@ class WC_Admin_Reports_Categories_Data_Store extends WC_Admin_Reports_Data_Store return $included_categories_str; } - /** - * Compares values in 2 arrays based on criterion specified when called via sort_records. - * - * @param array $a Array 1 to compare. - * @param array $b Array 2 to compare. - * @return integer|WP_Error - */ - private function sort_callback( $a, $b ) { - if ( '' === $this->order_by || '' === $this->order ) { - return new WP_Error( 'woocommerce_reports_categories_sort_failed', __( 'Sorry, fetching categories data failed.', 'wc-admin' ) ); - } - if ( $a[ $this->order_by ] === $b[ $this->order_by ] ) { - return 0; - } elseif ( $a[ $this->order_by ] > $b[ $this->order_by ] ) { - return strtolower( $this->order ) === 'desc' ? -1 : 1; - } elseif ( $a[ $this->order_by ] < $b[ $this->order_by ] ) { - return strtolower( $this->order ) === 'desc' ? 1 : -1; - } - } - - /** - * Sorts the data based on given sorting criterion and order. - * - * @param array $data Data to sort. - * @param string $sort_by Sorting criterion, any of the column_types keys. - * @param string $direction Sorting direction: asc/desc. - */ - protected function sort_records( &$data, $sort_by, $direction ) { - $this->order_by = $this->normalize_order_by( $sort_by ); - $this->order = $direction; - usort( $data, array( $this, 'sort_callback' ) ); - } - /** * Returns the page of data according to page number and items per page. * @@ -223,7 +238,7 @@ class WC_Admin_Reports_Categories_Data_Store extends WC_Admin_Reports_Data_Store $categories_data = $wpdb->get_results( "SELECT - term_id as category_id, + {$wpdb->prefix}term_taxonomy.term_id as category_id, {$selections} FROM {$table_name} @@ -234,6 +249,8 @@ class WC_Admin_Reports_Categories_Data_Store extends WC_Admin_Reports_Data_Store {$sql_query_params['where_clause']} GROUP BY category_id + ORDER BY + {$sql_query_params['order_by_clause']} ", ARRAY_A ); // WPCS: cache ok, DB call ok, unprepared SQL ok. @@ -248,11 +265,8 @@ class WC_Admin_Reports_Categories_Data_Store extends WC_Admin_Reports_Data_Store return $data; } - $this->sort_records( $categories_data, $query_args['orderby'], $query_args['order'] ); $categories_data = $this->page_records( $categories_data, $query_args['page'], $query_args['per_page'] ); - $this->include_extended_info( $categories_data, $query_args ); - $categories_data = array_map( array( $this, 'cast_numbers' ), $categories_data ); $data = (object) array( 'data' => $categories_data, diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-coupons-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-coupons-data-store.php index 95111752886..ca6a036b4c2 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-coupons-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-coupons-data-store.php @@ -94,6 +94,52 @@ class WC_Admin_Reports_Coupons_Data_Store extends WC_Admin_Reports_Data_Store im return $sql_query_params; } + + /** + * Fills ORDER BY clause of SQL request based on user supplied parameters. + * + * @param array $query_args Parameters supplied by the user. + * @return array + */ + protected function get_order_by_sql_params( $query_args ) { + global $wpdb; + $lookup_table = $wpdb->prefix . self::TABLE_NAME; + $sql_query = array(); + $sql_query['from_clause'] = ''; + $sql_query['order_by_clause'] = ''; + if ( isset( $query_args['orderby'] ) ) { + $sql_query['order_by_clause'] = $this->normalize_order_by( $query_args['orderby'] ); + } + + if ( false !== strpos( $sql_query['order_by_clause'], '_coupons' ) ) { + $sql_query['from_clause'] .= " JOIN {$wpdb->prefix}posts AS _coupons ON {$lookup_table}.coupon_id = _coupons.ID"; + } + + if ( isset( $query_args['order'] ) ) { + $sql_query['order_by_clause'] .= ' ' . $query_args['order']; + } else { + $sql_query['order_by_clause'] .= ' DESC'; + } + + return $sql_query; + } + + /** + * Maps ordering specified by the user to columns in the database/fields in the data. + * + * @param string $order_by Sorting criterion. + * @return string + */ + protected function normalize_order_by( $order_by ) { + if ( 'date' === $order_by ) { + return 'time_interval'; + } + if ( 'code' === $order_by ) { + return '_coupons.post_title'; + } + return $order_by; + } + /** * Enriches the coupon data with extra attributes. * diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-customers-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-customers-data-store.php new file mode 100644 index 00000000000..16302ada7ef --- /dev/null +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-customers-data-store.php @@ -0,0 +1,572 @@ + 'intval', + 'user_id' => 'intval', + 'orders_count' => 'intval', + 'total_spend' => 'floatval', + 'avg_order_value' => 'floatval', + ); + + /** + * SQL columns to select in the db query and their mapping to SQL code. + * + * @var array + */ + protected $report_columns = array( + 'customer_id' => 'customer_id', + 'user_id' => 'user_id', + 'username' => 'username', + 'name' => "CONCAT_WS( ' ', first_name, last_name ) as name", // TODO: what does this mean for RTL? + 'email' => 'email', + 'country' => 'country', + 'city' => 'city', + 'postcode' => 'postcode', + 'date_registered' => 'date_registered', + 'date_last_active' => 'date_last_active', + 'orders_count' => 'COUNT( order_id ) as orders_count', + 'total_spend' => 'SUM( gross_total ) as total_spend', + 'avg_order_value' => '( SUM( gross_total ) / COUNT( order_id ) ) as avg_order_value', + ); + + /** + * Constructor. + */ + public function __construct() { + global $wpdb; + + // Initialize some report columns that need disambiguation. + $this->report_columns['customer_id'] = $wpdb->prefix . self::TABLE_NAME . '.customer_id'; + $this->report_columns['date_last_order'] = "MAX( {$wpdb->prefix}wc_order_stats.date_created ) as date_last_order"; + } + + /** + * Set up all the hooks for maintaining and populating table data. + */ + public static function init() { + add_action( 'woocommerce_new_customer', array( __CLASS__, 'update_registered_customer' ) ); + add_action( 'woocommerce_update_customer', array( __CLASS__, 'update_registered_customer' ) ); + add_action( 'edit_user_profile_update', array( __CLASS__, 'update_registered_customer' ) ); + add_action( 'updated_user_meta', array( __CLASS__, 'update_registered_customer_via_last_active' ), 10, 3 ); + } + + /** + * Trigger a customer update if their "last active" meta value was changed. + * Function expects to be hooked into the `updated_user_meta` action. + * + * @param int $meta_id ID of updated metadata entry. + * @param int $user_id ID of the user being updated. + * @param string $meta_key Meta key being updated. + */ + public static function update_registered_customer_via_last_active( $meta_id, $user_id, $meta_key ) { + if ( 'wc_last_active' === $meta_key ) { + self::update_registered_customer( $user_id ); + } + } + + /** + * Maps ordering specified by the user to columns in the database/fields in the data. + * + * @param string $order_by Sorting criterion. + * @return string + */ + protected function normalize_order_by( $order_by ) { + if ( 'name' === $order_by ) { + return "CONCAT_WS( ' ', first_name, last_name )"; + } + + return $order_by; + } + + /** + * Fills ORDER BY clause of SQL request based on user supplied parameters. + * + * @param array $query_args Parameters supplied by the user. + * @return array + */ + protected function get_order_by_sql_params( $query_args ) { + $sql_query['order_by_clause'] = ''; + + if ( isset( $query_args['orderby'] ) ) { + $sql_query['order_by_clause'] = $this->normalize_order_by( $query_args['orderby'] ); + } + + if ( isset( $query_args['order'] ) ) { + $sql_query['order_by_clause'] .= ' ' . $query_args['order']; + } else { + $sql_query['order_by_clause'] .= ' DESC'; + } + + return $sql_query; + } + + /** + * Fills WHERE clause of SQL request with date-related constraints. + * + * @param array $query_args Parameters supplied by the user. + * @param string $table_name Name of the db table relevant for the date constraint. + * @return array + */ + protected function get_time_period_sql_params( $query_args, $table_name ) { + global $wpdb; + + $sql_query = array( + 'where_time_clause' => '', + 'where_clause' => '', + 'having_clause' => '', + ); + $date_param_mapping = array( + 'registered' => array( + 'clause' => 'where', + 'column' => $table_name . '.date_registered', + ), + 'last_active' => array( + 'clause' => 'where', + 'column' => $table_name . '.date_last_active', + ), + 'last_order' => array( + 'clause' => 'having', + 'column' => "MAX( {$wpdb->prefix}wc_order_stats.date_created )", + ), + ); + $match_operator = $this->get_match_operator( $query_args ); + $where_time_clauses = array(); + $having_time_clauses = array(); + + foreach ( $date_param_mapping as $query_param => $param_info ) { + $subclauses = array(); + $before_arg = $query_param . '_before'; + $after_arg = $query_param . '_after'; + $column_name = $param_info['column']; + + if ( ! empty( $query_args[ $before_arg ] ) ) { + $datetime = new DateTime( $query_args[ $before_arg ] ); + $datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format ); + $subclauses[] = "{$column_name} <= '$datetime_str'"; + } + + if ( ! empty( $query_args[ $after_arg ] ) ) { + $datetime = new DateTime( $query_args[ $after_arg ] ); + $datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format ); + $subclauses[] = "{$column_name} >= '$datetime_str'"; + } + + if ( $subclauses && ( 'where' === $param_info['clause'] ) ) { + $where_time_clauses[] = '(' . implode( ' AND ', $subclauses ) . ')'; + } + + if ( $subclauses && ( 'having' === $param_info['clause'] ) ) { + $having_time_clauses[] = '(' . implode( ' AND ', $subclauses ) . ')'; + } + } + + if ( $where_time_clauses ) { + $sql_query['where_time_clause'] = ' AND ' . implode( " {$match_operator} ", $where_time_clauses ); + } + + if ( $having_time_clauses ) { + $sql_query['having_clause'] = ' AND ' . implode( " {$match_operator} ", $having_time_clauses ); + } + + return $sql_query; + } + + /** + * Updates the database query with parameters used for Customers report: categories and order status. + * + * @param array $query_args Query arguments supplied by the user. + * @return array Array of parameters used for SQL query. + */ + protected function get_sql_query_params( $query_args ) { + global $wpdb; + $customer_lookup_table = $wpdb->prefix . self::TABLE_NAME; + + $sql_query_params = $this->get_time_period_sql_params( $query_args, $customer_lookup_table ); + $sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) ); + $sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) ); + + $match_operator = $this->get_match_operator( $query_args ); + $where_clauses = array(); + $having_clauses = array(); + + $exact_match_params = array( + 'username', + 'email', + 'country', + ); + + foreach ( $exact_match_params as $exact_match_param ) { + if ( ! empty( $query_args[ $exact_match_param ] ) ) { + $where_clauses[] = $wpdb->prepare( + "{$customer_lookup_table}.{$exact_match_param} = %s", + $query_args[ $exact_match_param ] + ); // WPCS: unprepared SQL ok. + } + } + + if ( ! empty( $query_args['name'] ) ) { + $where_clauses[] = $wpdb->prepare( "CONCAT_WS( ' ', first_name, last_name ) = %s", $query_args['name'] ); + } + + $numeric_params = array( + 'orders_count' => array( + 'column' => 'COUNT( order_id )', + 'format' => '%d', + ), + 'total_spend' => array( + 'column' => 'SUM( gross_total )', + 'format' => '%f', + ), + 'avg_order_value' => array( + 'column' => '( SUM( gross_total ) / COUNT( order_id ) )', + 'format' => '%f', + ), + ); + + foreach ( $numeric_params as $numeric_param => $param_info ) { + $subclauses = array(); + $min_param = $numeric_param . '_min'; + $max_param = $numeric_param . '_max'; + + if ( isset( $query_args[ $min_param ] ) ) { + $subclauses[] = $wpdb->prepare( + "{$param_info['column']} >= {$param_info['format']}", + $query_args[ $min_param ] + ); // WPCS: unprepared SQL ok. + } + + if ( isset( $query_args[ $max_param ] ) ) { + $subclauses[] = $wpdb->prepare( + "{$param_info['column']} <= {$param_info['format']}", + $query_args[ $max_param ] + ); // WPCS: unprepared SQL ok. + } + + if ( $subclauses ) { + $having_clauses[] = '(' . implode( ' AND ', $subclauses ) . ')'; + } + } + + if ( $where_clauses ) { + $preceding_match = empty( $sql_query_params['where_time_clause'] ) ? ' AND ' : " {$match_operator} "; + $sql_query_params['where_clause'] = $preceding_match . implode( " {$match_operator} ", $where_clauses ); + } + + if ( $having_clauses ) { + $preceding_match = empty( $sql_query_params['having_clause'] ) ? ' AND ' : " {$match_operator} "; + $sql_query_params['having_clause'] .= $preceding_match . implode( " {$match_operator} ", $having_clauses ); + } + + return $sql_query_params; + } + + /** + * Returns the report data based on parameters supplied by the user. + * + * @param array $query_args Query parameters. + * @return stdClass|WP_Error Data. + */ + public function get_data( $query_args ) { + global $wpdb; + + $customers_table_name = $wpdb->prefix . self::TABLE_NAME; + $order_stats_table_name = $wpdb->prefix . 'wc_order_stats'; + + // These defaults are only partially applied when used via REST API, as that has its own defaults. + $defaults = array( + 'per_page' => get_option( 'posts_per_page' ), + 'page' => 1, + 'order' => 'DESC', + 'orderby' => 'date_registered', + 'fields' => '*', + ); + $query_args = wp_parse_args( $query_args, $defaults ); + + $cache_key = $this->get_cache_key( $query_args ); + $data = wp_cache_get( $cache_key, $this->cache_group ); + + if ( false === $data ) { + $data = (object) array( + 'data' => array(), + 'total' => 0, + 'pages' => 0, + 'page_no' => 0, + ); + + $selections = $this->selected_columns( $query_args ); + $sql_query_params = $this->get_sql_query_params( $query_args ); + + $db_records_count = (int) $wpdb->get_var( + "SELECT COUNT(*) FROM ( + SELECT {$customers_table_name}.customer_id + FROM + {$customers_table_name} + LEFT JOIN + {$order_stats_table_name} + ON + {$customers_table_name}.customer_id = {$order_stats_table_name}.customer_id + WHERE + 1=1 + {$sql_query_params['where_time_clause']} + {$sql_query_params['where_clause']} + GROUP BY + {$customers_table_name}.customer_id + HAVING + 1=1 + {$sql_query_params['having_clause']} + ) as tt + " + ); // WPCS: cache ok, DB call ok, unprepared SQL ok. + + $total_pages = (int) ceil( $db_records_count / $sql_query_params['per_page'] ); + if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) { + return $data; + } + + $customer_data = $wpdb->get_results( + "SELECT + {$selections} + FROM + {$customers_table_name} + LEFT JOIN + {$order_stats_table_name} + ON + {$customers_table_name}.customer_id = {$order_stats_table_name}.customer_id + WHERE + 1=1 + {$sql_query_params['where_time_clause']} + {$sql_query_params['where_clause']} + GROUP BY + {$customers_table_name}.customer_id + HAVING + 1=1 + {$sql_query_params['having_clause']} + ORDER BY + {$sql_query_params['order_by_clause']} + {$sql_query_params['limit']} + ", + ARRAY_A + ); // WPCS: cache ok, DB call ok, unprepared SQL ok. + + if ( null === $customer_data ) { + return $data; + } + + $customer_data = array_map( array( $this, 'cast_numbers' ), $customer_data ); + $data = (object) array( + 'data' => $customer_data, + 'total' => $db_records_count, + 'pages' => $total_pages, + 'page_no' => (int) $query_args['page'], + ); + + wp_cache_set( $cache_key, $data, $this->cache_group ); + } + + return $data; + } + + /** + * Gets the guest (no user_id) customer ID or creates a new one for + * the corresponding billing email in the provided WC_Order + * + * @param WC_Order $order Order to get/create guest customer data with. + * @return int|false The ID of the retrieved/created customer, or false on error. + */ + public function get_or_create_guest_customer_from_order( $order ) { + global $wpdb; + + $email = $order->get_billing_email( 'edit' ); + + if ( empty( $email ) ) { + return false; + } + + $existing_guest = $this->get_guest_by_email( $email ); + + if ( $existing_guest ) { + return $existing_guest['customer_id']; + } + + $result = $wpdb->insert( + $wpdb->prefix . self::TABLE_NAME, + array( + 'first_name' => $order->get_billing_first_name( 'edit' ), + 'last_name' => $order->get_billing_last_name( 'edit' ), + 'email' => $email, + 'city' => $order->get_billing_city( 'edit' ), + 'postcode' => $order->get_billing_postcode( 'edit' ), + 'country' => $order->get_billing_country( 'edit' ), + 'date_last_active' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ), + ), + array( + '%s', + '%s', + '%s', + '%s', + '%s', + '%s', + '%s', + ) + ); + + return $result ? $wpdb->insert_id : false; + } + + /** + * Retrieve a guest (no user_id) customer row by email. + * + * @param string $email Email address. + * @returns false|array Customer array if found, boolean false if not. + */ + public function get_guest_by_email( $email ) { + global $wpdb; + + $table_name = $wpdb->prefix . self::TABLE_NAME; + $guest_row = $wpdb->get_row( + $wpdb->prepare( + "SELECT * FROM {$table_name} WHERE email = %s AND user_id IS NULL LIMIT 1", + $email + ), + ARRAY_A + ); // WPCS: unprepared SQL ok. + + if ( $guest_row ) { + return $this->cast_numbers( $guest_row ); + } + + return false; + } + + /** + * Retrieve a registered customer row by user_id. + * + * @param string|int $user_id User ID. + * @returns false|array Customer array if found, boolean false if not. + */ + public function get_customer_by_user_id( $user_id ) { + global $wpdb; + + $table_name = $wpdb->prefix . self::TABLE_NAME; + $customer = $wpdb->get_row( + $wpdb->prepare( + "SELECT * FROM {$table_name} WHERE user_id = %d LIMIT 1", + $user_id + ), + ARRAY_A + ); // WPCS: unprepared SQL ok. + + if ( $customer ) { + return $this->cast_numbers( $customer ); + } + + return false; + } + + /** + * Retrieve a registered customer row id by user_id. + * + * @param string|int $user_id User ID. + * @returns false|int Customer ID if found, boolean false if not. + */ + public static function get_customer_id_by_user_id( $user_id ) { + global $wpdb; + + $table_name = $wpdb->prefix . self::TABLE_NAME; + $customer_id = $wpdb->get_var( + $wpdb->prepare( + "SELECT customer_id FROM {$table_name} WHERE user_id = %d LIMIT 1", + $user_id + ) + ); // WPCS: unprepared SQL ok. + + return $customer_id ? (int) $customer_id : false; + } + + /** + * Update the database with customer data. + * + * @param int $user_id WP User ID to update customer data for. + * @return int|bool|null Number or rows modified or false on failure. + */ + public static function update_registered_customer( $user_id ) { + global $wpdb; + + $customer = new WC_Customer( $user_id ); + + if ( $customer->get_id() != $user_id ) { + return false; + } + + $last_active = $customer->get_meta( 'wc_last_active', true, 'edit' ); + $data = array( + 'user_id' => $user_id, + 'username' => $customer->get_username( 'edit' ), + 'first_name' => $customer->get_first_name( 'edit' ), + 'last_name' => $customer->get_last_name( 'edit' ), + 'email' => $customer->get_email( 'edit' ), + 'city' => $customer->get_billing_city( 'edit' ), + 'postcode' => $customer->get_billing_postcode( 'edit' ), + 'country' => $customer->get_billing_country( 'edit' ), + 'date_registered' => date( 'Y-m-d H:i:s', $customer->get_date_created( 'edit' )->getTimestamp() ), + 'date_last_active' => $last_active ? date( 'Y-m-d H:i:s', $last_active ) : '', + ); + $format = array( + '%d', + '%s', + '%s', + '%s', + '%s', + '%s', + '%s', + '%s', + '%s', + '%s', + ); + + $customer_id = self::get_customer_id_by_user_id( $user_id ); + + if ( $customer_id ) { + // Preserve customer_id for existing user_id. + $data['customer_id'] = $customer_id; + $format[] = '%d'; + } + + return $wpdb->replace( $wpdb->prefix . self::TABLE_NAME, $data, $format ); + } + + /** + * Returns string to be used as cache key for the data. + * + * @param array $params Query parameters. + * @return string + */ + protected function get_cache_key( $params ) { + return 'woocommerce_' . self::TABLE_NAME . '_' . md5( wp_json_encode( $params ) ); + } + +} diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-data-store.php index 83b16a19740..d44282b5d3f 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-data-store.php @@ -180,6 +180,71 @@ class WC_Admin_Reports_Data_Store { $data->intervals = array_slice( $data->intervals, $offset, $count ); } + /** + * Returns expected number of items on the page in case of date ordering. + * + * @param int $expected_interval_count Expected number of intervals in total. + * @param int $items_per_page Number of items per page. + * @param int $page_no Page number. + * + * @return float|int + */ + protected function expected_intervals_on_page( $expected_interval_count, $items_per_page, $page_no ) { + $total_pages = (int) ceil( $expected_interval_count / $items_per_page ); + if ( $page_no < $total_pages ) { + return $items_per_page; + } elseif ( $page_no === $total_pages ) { + return $expected_interval_count - ( $page_no - 1 ) * $items_per_page; + } else { + return 0; + } + } + + /** + * Returns true if there are any intervals that need to be filled in the response. + * + * @param int $expected_interval_count Expected number of intervals in total. + * @param int $db_records Total number of records for given period in the database. + * @param int $items_per_page Number of items per page. + * @param int $page_no Page number. + * @param string $order asc or desc. + * @param string $order_by Column by which the result will be sorted. + * @param int $intervals_count Number of records for given (possibly shortened) time interval. + * + * @return bool + */ + protected function intervals_missing( $expected_interval_count, $db_records, $items_per_page, $page_no, $order, $order_by, $intervals_count ) { + if ( $expected_interval_count > $db_records ) { + if ( 'date' === $order_by ) { + $expected_intervals_on_page = $this->expected_intervals_on_page( $expected_interval_count, $items_per_page, $page_no ); + if ( $intervals_count < $expected_intervals_on_page ) { + return true; + } else { + return false; + } + } else { + if ( 'desc' === $order ) { + if ( $page_no > floor( $db_records / $items_per_page ) ) { + return true; + } else { + return false; + } + } elseif ( 'asc' === $order ) { + if ( $page_no <= ceil( ( $expected_interval_count - $db_records ) / $items_per_page ) ) { + return true; + } else { + return false; + } + } else { + // Invalid ordering. + return false; + } + } + } else { + return false; + } + } + /** * Updates the LIMIT query part for Intervals query of the report. * @@ -346,7 +411,7 @@ class WC_Admin_Reports_Data_Store { * @param string $status Order status. * @return string */ - protected function normalize_order_status( $status ) { + protected static function normalize_order_status( $status ) { $status = trim( $status ); return 'wc-' . $status; } @@ -709,14 +774,14 @@ class WC_Admin_Reports_Data_Store { if ( isset( $query_args['status_is'] ) && is_array( $query_args['status_is'] ) && count( $query_args['status_is'] ) > 0 ) { $allowed_statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['status_is'] ); if ( $allowed_statuses ) { - $subqueries[] = "{$wpdb->prefix}posts.post_status IN ( '" . implode( "','", $allowed_statuses ) . "' )"; + $subqueries[] = "{$wpdb->prefix}wc_order_stats.status IN ( '" . implode( "','", $allowed_statuses ) . "' )"; } } if ( isset( $query_args['status_is_not'] ) && is_array( $query_args['status_is_not'] ) && count( $query_args['status_is_not'] ) > 0 ) { $forbidden_statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['status_is_not'] ); if ( $forbidden_statuses ) { - $subqueries[] = "{$wpdb->prefix}posts.post_status NOT IN ( '" . implode( "','", $forbidden_statuses ) . "' )"; + $subqueries[] = "{$wpdb->prefix}wc_order_stats.status NOT IN ( '" . implode( "','", $forbidden_statuses ) . "' )"; } } diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-downloads-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-downloads-data-store.php index 86555a8b1ec..8f644b31d21 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-downloads-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-downloads-data-store.php @@ -73,7 +73,6 @@ class WC_Admin_Reports_Downloads_Data_Store extends WC_Admin_Reports_Data_Store $sql_query_params = $this->get_time_period_sql_params( $query_args, $lookup_table ); $sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) ); - $sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) ); $included_products = $this->get_included_products( $query_args ); $excluded_products = $this->get_excluded_products( $query_args ); @@ -163,6 +162,7 @@ class WC_Admin_Reports_Downloads_Data_Store extends WC_Admin_Reports_Data_Store } $sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}woocommerce_downloadable_product_permissions as product_permissions ON {$lookup_table}.permission_id = product_permissions.permission_id"; + $sql_query_params = $this->get_order_by( $query_args, $sql_query_params ); return $sql_query_params; } @@ -240,14 +240,20 @@ class WC_Admin_Reports_Downloads_Data_Store extends WC_Admin_Reports_Data_Store * Fills ORDER BY clause of SQL request based on user supplied parameters. * * @param array $query_args Parameters supplied by the user. + * @param array $sql_query Current SQL query array. * @return array */ - protected function get_order_by_sql_params( $query_args ) { + protected function get_order_by( $query_args, $sql_query ) { + global $wpdb; $sql_query['order_by_clause'] = ''; if ( isset( $query_args['orderby'] ) ) { $sql_query['order_by_clause'] = $this->normalize_order_by( $query_args['orderby'] ); } + if ( false !== strpos( $sql_query['order_by_clause'], '_products' ) ) { + $sql_query['from_clause'] .= " JOIN {$wpdb->prefix}posts AS _products ON product_permissions.product_id = _products.ID"; + } + if ( isset( $query_args['order'] ) ) { $sql_query['order_by_clause'] .= ' ' . $query_args['order']; } else { @@ -377,6 +383,10 @@ class WC_Admin_Reports_Downloads_Data_Store extends WC_Admin_Reports_Data_Store return $wpdb->prefix . 'wc_download_log.timestamp'; } + if ( 'product' === $order_by ) { + return '_products.post_title'; + } + return $order_by; } diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-downloads-stats-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-downloads-stats-data-store.php new file mode 100644 index 00000000000..c08e6bc9256 --- /dev/null +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-downloads-stats-data-store.php @@ -0,0 +1,232 @@ + 'intval', + ); + + /** + * SQL columns to select in the db query and their mapping to SQL code. + * + * @var array + */ + protected $report_columns = array( + 'download_count' => 'COUNT(DISTINCT download_log_id) as download_count', + ); + + /** + * Constructor + */ + public function __construct() { + global $wpdb; + } + + + /** + * Returns the report data based on parameters supplied by the user. + * + * @param array $query_args Query parameters. + * @return stdClass|WP_Error Data. + */ + public function get_data( $query_args ) { + global $wpdb; + + $table_name = $wpdb->prefix . self::TABLE_NAME; + $now = time(); + $week_back = $now - WEEK_IN_SECONDS; + + // These defaults are only partially applied when used via REST API, as that has its own defaults. + $defaults = array( + 'per_page' => get_option( 'posts_per_page' ), + 'page' => 1, + 'order' => 'DESC', + 'orderby' => 'date', + 'fields' => '*', + 'interval' => 'week', + ); + $query_args = wp_parse_args( $query_args, $defaults ); + + if ( empty( $query_args['before'] ) ) { + $query_args['before'] = date( WC_Admin_Reports_Interval::$iso_datetime_format, $now ); + } + if ( empty( $query_args['after'] ) ) { + $query_args['after'] = date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ); + } + + $cache_key = $this->get_cache_key( $query_args ); + $data = wp_cache_get( $cache_key, $this->cache_group ); + + if ( false === $data ) { + $selections = $this->selected_columns( $query_args ); + $sql_query_params = $this->get_sql_query_params( $query_args ); + $totals_query = array_merge( array(), $this->get_time_period_sql_params( $query_args, $table_name ) ); + $intervals_query = array_merge( array(), $this->get_intervals_sql_params( $query_args, $table_name ) ); + + $totals_query['where_clause'] .= $sql_query_params['where_clause']; + $totals_query['from_clause'] .= $sql_query_params['from_clause']; + $intervals_query['where_clause'] .= $sql_query_params['where_clause']; + $intervals_query['from_clause'] .= $sql_query_params['from_clause']; + $intervals_query['select_clause'] = str_replace( 'date_created', 'timestamp', $intervals_query['select_clause'] ); + $intervals_query['where_time_clause'] = str_replace( 'date_created', 'timestamp', $intervals_query['where_time_clause'] ); + + $db_intervals = $wpdb->get_col( + "SELECT + {$intervals_query['select_clause']} AS time_interval + FROM + {$table_name} + {$intervals_query['from_clause']} + WHERE + 1=1 + {$intervals_query['where_time_clause']} + {$intervals_query['where_clause']} + GROUP BY + time_interval" + ); // WPCS: cache ok, DB call ok, , unprepared SQL ok. + + $db_records_count = count( $db_intervals ); + + $expected_interval_count = WC_Admin_Reports_Interval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] ); + $total_pages = (int) ceil( $expected_interval_count / $intervals_query['per_page'] ); + if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) { + return array(); + } + + $this->update_intervals_sql_params( $intervals_query, $query_args, $db_records_count, $expected_interval_count ); + $intervals_query['where_time_clause'] = str_replace( 'date_created', 'timestamp', $intervals_query['where_time_clause'] ); + + $totals = $wpdb->get_results( + "SELECT + {$selections} + FROM + {$table_name} + {$totals_query['from_clause']} + WHERE + 1=1 + {$totals_query['where_time_clause']} + {$totals_query['where_clause']}", + ARRAY_A + ); // WPCS: cache ok, DB call ok, unprepared SQL ok. + + if ( null === $totals ) { + return new WP_Error( 'woocommerce_reports_downloads_stats_result_failed', __( 'Sorry, fetching downloads data failed.', 'wc-admin' ) ); + } + + if ( '' !== $selections ) { + $selections = ', ' . $selections; + } + + $intervals = $wpdb->get_results( + "SELECT + MAX(timestamp) AS datetime_anchor, + {$intervals_query['select_clause']} AS time_interval + {$selections} + FROM + {$table_name} + {$intervals_query['from_clause']} + WHERE + 1=1 + {$intervals_query['where_time_clause']} + {$intervals_query['where_clause']} + GROUP BY + time_interval + ORDER BY + {$intervals_query['order_by_clause']} + {$intervals_query['limit']}", + ARRAY_A + ); // WPCS: cache ok, DB call ok, unprepared SQL ok. + + if ( null === $intervals ) { + return new WP_Error( 'woocommerce_reports_downloads_stats_result_failed', __( 'Sorry, fetching downloads data failed.', 'wc-admin' ) ); + } + + $totals = (object) $this->cast_numbers( $totals[0] ); + $data = (object) array( + 'totals' => $totals, + 'intervals' => $intervals, + 'total' => $expected_interval_count, + 'pages' => $total_pages, + 'page_no' => (int) $query_args['page'], + ); + + if ( $this->intervals_missing( $expected_interval_count, $db_records_count, $intervals_query['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) { + $this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data ); + $this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] ); + $this->remove_extra_records( $data, $query_args['page'], $intervals_query['per_page'], $db_records_count, $expected_interval_count, $query_args['orderby'] ); + } else { + $this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals ); + } + $this->create_interval_subtotals( $data->intervals ); + + wp_cache_set( $cache_key, $data, $this->cache_group ); + } + + return $data; + } + + /** + * Returns string to be used as cache key for the data. + * + * @param array $params Query parameters. + * @return string + */ + protected function get_cache_key( $params ) { + return 'woocommerce_' . self::TABLE_NAME . '_stats_' . md5( wp_json_encode( $params ) ); + } + + /** + * Sorts intervals according to user's request. + * + * They are pre-sorted in SQL, but after adding gaps, they need to be sorted including the added ones. + * + * @param stdClass $data Data object, must contain an array under $data->intervals. + * @param string $sort_by Ordering property. + * @param string $direction DESC/ASC. + */ + protected function sort_intervals( &$data, $sort_by, $direction ) { + if ( 'date' === $sort_by ) { + $this->order_by = 'time_interval'; + } else { + $this->order_by = $sort_by; + } + + $this->order = $direction; + usort( $data->intervals, array( $this, 'interval_cmp' ) ); + } + + /** + * Compares two report data objects by pre-defined object property and ASC/DESC ordering. + * + * @param stdClass $a Object a. + * @param stdClass $b Object b. + * @return string + */ + protected function interval_cmp( $a, $b ) { + if ( '' === $this->order_by || '' === $this->order ) { + return 0; + } + if ( $a[ $this->order_by ] === $b[ $this->order_by ] ) { + return 0; + } elseif ( $a[ $this->order_by ] > $b[ $this->order_by ] ) { + return strtolower( $this->order ) === 'desc' ? -1 : 1; + } elseif ( $a[ $this->order_by ] < $b[ $this->order_by ] ) { + return strtolower( $this->order ) === 'desc' ? 1 : -1; + } + } + +} diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php index 92afec105c9..b467d8c4266 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php @@ -171,10 +171,8 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp )"; } - // TODO: move order status to wc_order_stats so that JOIN is not necessary. $order_status_filter = $this->get_status_subquery( $query_args, $operator ); if ( $order_status_filter ) { - $from_clause .= " JOIN {$wpdb->prefix}posts ON {$orders_stats_table}.order_id = {$wpdb->prefix}posts.ID"; $where_filters[] = $order_status_filter; } @@ -1268,7 +1266,7 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp return; } - $data = array( + $data = array( 'order_id' => $order->get_id(), 'date_created' => $order->get_date_created()->date( 'Y-m-d H:i:s' ), 'num_items_sold' => self::get_num_items_sold( $order ), @@ -1279,24 +1277,48 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp 'shipping_total' => $order->get_shipping_total(), 'net_total' => (float) $order->get_total() - (float) $order->get_total_tax() - (float) $order->get_shipping_total(), 'returning_customer' => self::is_returning_customer( $order ), + 'status' => self::normalize_order_status( $order->get_status() ), + ); + $format = array( + '%d', + '%s', + '%d', + '%f', + '%f', + '%f', + '%f', + '%f', + '%f', + '%d', + '%s', ); + // Ensure we're associating this order with a Customer in the lookup table. + $order_user_id = $order->get_customer_id(); + $customers_data_store = new WC_Admin_Reports_Customers_Data_Store(); + + if ( 0 === $order_user_id ) { + $email = $order->get_billing_email( 'edit' ); + + if ( $email ) { + $customer_id = $customers_data_store->get_or_create_guest_customer_from_order( $order ); + + if ( $customer_id ) { + $data['customer_id'] = $customer_id; + $format[] = '%d'; + } + } + } else { + $customer = $customers_data_store->get_customer_by_user_id( $order_user_id ); + + if ( $customer && $customer['customer_id'] ) { + $data['customer_id'] = $customer['customer_id']; + $format[] = '%d'; + } + } + // Update or add the information to the DB. - return $wpdb->replace( - $table_name, - $data, - array( - '%d', - '%s', - '%d', - '%f', - '%f', - '%f', - '%f', - '%f', - '%f', - ) - ); + return $wpdb->replace( $table_name, $data, $format ); } /** diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php index 09feb580f70..6d5e4b5cf05 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php @@ -100,6 +100,10 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i $sql_query['from_clause'] .= " JOIN {$wpdb->prefix}posts AS _products ON {$order_product_lookup_table}.product_id = _products.ID"; } + if ( 'postmeta.meta_value' === $sql_query['order_by_clause'] ) { + $sql_query['from_clause'] .= " JOIN {$wpdb->prefix}postmeta AS postmeta ON {$order_product_lookup_table}.product_id = postmeta.post_id AND postmeta.meta_key = '_sku'"; + } + if ( isset( $query_args['order'] ) ) { $sql_query['order_by_clause'] .= ' ' . $query_args['order']; } else { @@ -150,7 +154,9 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i if ( 'product_name' === $order_by ) { return '_products.post_title'; } - + if ( 'sku' === $order_by ) { + return 'postmeta.meta_value'; + } return $order_by; } diff --git a/plugins/woocommerce-admin/lib/admin.php b/plugins/woocommerce-admin/lib/admin.php index b44ac05158f..78b3995e82a 100644 --- a/plugins/woocommerce-admin/lib/admin.php +++ b/plugins/woocommerce-admin/lib/admin.php @@ -399,6 +399,11 @@ function wc_admin_get_user_data_fields() { 'revenue_report_columns', 'taxes_report_columns', 'variations_report_columns', + 'dashboard_charts', + 'dashboard_chart_type', + 'dashboard_chart_interval', + 'dashboard_leaderboards', + 'dashboard_leaderboard_rows', ); return apply_filters( 'wc_admin_get_user_data_fields', $user_data_fields ); diff --git a/plugins/woocommerce-admin/lib/client-assets.php b/plugins/woocommerce-admin/lib/client-assets.php index 8e876376ceb..c49f2209d5f 100644 --- a/plugins/woocommerce-admin/lib/client-assets.php +++ b/plugins/woocommerce-admin/lib/client-assets.php @@ -180,9 +180,6 @@ function wc_admin_print_script_settings() { 'embedBreadcrumbs' => wc_admin_get_embed_breadcrumbs(), 'siteLocale' => esc_attr( get_bloginfo( 'language' ) ), 'currency' => wc_admin_currency_settings(), - 'date' => array( - 'dow' => get_option( 'start_of_week', 0 ), - ), 'orderStatuses' => format_order_statuses( wc_get_order_statuses() ), 'stockStatuses' => wc_get_product_stock_status_options(), 'siteTitle' => get_bloginfo( 'name' ), diff --git a/plugins/woocommerce-admin/package-lock.json b/plugins/woocommerce-admin/package-lock.json index 8bafe1db940..7b3be3b282e 100644 --- a/plugins/woocommerce-admin/package-lock.json +++ b/plugins/woocommerce-admin/package-lock.json @@ -1,6 +1,6 @@ { "name": "wc-admin", - "version": "0.3.0", + "version": "0.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -71,7 +71,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -233,13 +233,13 @@ } }, "@babel/helper-replace-supers": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz", - "integrity": "sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.1.0.tgz", + "integrity": "sha512-BvcDWYZRWVuDeXTYZWxekQNO5D4kO55aArwZOTFXw6rlLQA8ZaDicJR1sO47h+HrnCiDFiww0fSPV0d713KBGQ==", "requires": { "@babel/helper-member-expression-to-functions": "^7.0.0", "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.2.3", + "@babel/traverse": "^7.1.0", "@babel/types": "^7.0.0" } }, @@ -293,9 +293,9 @@ } }, "@babel/parser": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.3.tgz", - "integrity": "sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==" + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.2.tgz", + "integrity": "sha512-UNTmQ5cSLDeBGBl+s7JeowkqIHgmFAGBnLDdIzFmUNSuS5JF0XBcN59jsh/vJO/YjfsBqMxhMjoFGmNExmf0FA==" }, "@babel/plugin-proposal-async-generator-functions": { "version": "7.2.0", @@ -648,9 +648,9 @@ } }, "@babel/preset-env": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.2.3.tgz", - "integrity": "sha512-AuHzW7a9rbv5WXmvGaPX7wADxFkZIqKlbBh1dmZUQp4iwiPpkE/Qnrji6SC4UQCQzvWY/cpHET29eUhXS9cLPw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.2.0.tgz", + "integrity": "sha512-haGR38j5vOGVeBatrQPr3l0xHbs14505DcM57cbJy48kgMFvvHHoYEhHuRV+7vi559yyAUAVbTWzbK/B/pzJng==", "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", @@ -723,15 +723,15 @@ } }, "@babel/traverse": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.3.tgz", - "integrity": "sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.2.tgz", + "integrity": "sha512-E5Bn9FSwHpSkUhthw/XEuvFZxIgrqb9M8cX8j5EUQtrUG5DQUy6bFyl7G7iQ1D1Czudor+xkmp81JbLVVM0Sjg==", "requires": { "@babel/code-frame": "^7.0.0", "@babel/generator": "^7.2.2", "@babel/helper-function-name": "^7.1.0", "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.2.3", + "@babel/parser": "^7.2.2", "@babel/types": "^7.2.2", "debug": "^4.1.0", "globals": "^11.1.0", @@ -756,20 +756,15 @@ "@babel/runtime-corejs2": "^7.1.5" } }, - "@icons/material": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", - "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" - }, "@lerna/add": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.7.2.tgz", - "integrity": "sha512-/kCuyytOEmYcqpbU8MhHc2/3bPJjEx+qq7SOdb0cCDG+QcJ/oSsDCZ3xVHxhyLRYAoRlKBch3DiBmY4BeIm0Ag==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.8.2.tgz", + "integrity": "sha512-P9U5fzvrINVcslfn2CoMoRH0k+skwNTEsBTYkqjRf+ZYaaM3PvKzXvnonMfPSLBgAK3bBMnzinfb3USJnStx2w==", "dev": true, "requires": { - "@lerna/bootstrap": "^3.7.2", - "@lerna/command": "^3.7.2", - "@lerna/filter-options": "^3.6.0", + "@lerna/bootstrap": "^3.8.2", + "@lerna/command": "^3.8.1", + "@lerna/filter-options": "^3.8.1", "@lerna/npm-conf": "^3.7.0", "@lerna/validation-error": "^3.6.0", "dedent": "^0.7.0", @@ -790,23 +785,23 @@ } }, "@lerna/bootstrap": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.7.2.tgz", - "integrity": "sha512-yVjr450UivC7gbIh3GZowJ6bzPy/xC75bduq2Zm+jdIksjM/8SA3HRXWNothaSyZWudV+WY+cy6MvwrtFe8Kbg==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.8.2.tgz", + "integrity": "sha512-/MT2krnth21mI2t3S6M1FJ4MUt+h+ycwcSfHhDMsgqfPqtb6y7awWlAFjHzgWXxf1LUOStkIOoGsJGNsxjiXWQ==", "dev": true, "requires": { "@lerna/batch-packages": "^3.6.0", - "@lerna/command": "^3.7.2", - "@lerna/filter-options": "^3.6.0", + "@lerna/command": "^3.8.1", + "@lerna/filter-options": "^3.8.1", "@lerna/has-npm-version": "^3.3.0", - "@lerna/npm-install": "^3.6.0", + "@lerna/npm-install": "^3.8.2", "@lerna/package-graph": "^3.6.0", "@lerna/pulse-till-done": "^3.7.1", "@lerna/rimraf-dir": "^3.6.0", - "@lerna/run-lifecycle": "^3.7.1", + "@lerna/run-lifecycle": "^3.8.2", "@lerna/run-parallel-batches": "^3.0.0", "@lerna/symlink-binary": "^3.7.2", - "@lerna/symlink-dependencies": "^3.7.2", + "@lerna/symlink-dependencies": "^3.8.1", "@lerna/validation-error": "^3.6.0", "dedent": "^0.7.0", "get-port": "^3.2.0", @@ -821,25 +816,25 @@ } }, "@lerna/changed": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.8.0.tgz", - "integrity": "sha512-IeOxB+nwGFpAuEgUi9FeP19hj6Abp1aNCeMjS9/KpOxrSGt3ejKlSKY83lwqDPbb6OnthQTRBlodWZpSiSPWqg==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.8.2.tgz", + "integrity": "sha512-NMxLftHcNVZH+kdL9qylD4c/ZYljQ7YOQdgrc97ds23dk11UhLVAkbbpRVU5DE/s+bqo03zROVHPEYT+ELQjWg==", "dev": true, "requires": { - "@lerna/collect-updates": "^3.6.0", - "@lerna/command": "^3.7.2", + "@lerna/collect-updates": "^3.8.1", + "@lerna/command": "^3.8.1", "@lerna/listable": "^3.6.0", "@lerna/output": "^3.6.0", - "@lerna/version": "^3.8.0" + "@lerna/version": "^3.8.2" } }, "@lerna/check-working-tree": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-3.6.0.tgz", - "integrity": "sha512-Ioy1t2aVasAwhY1Oi5kfpwbW9RDupxxVVu2t2c1EeBYYCu3jIt1A5ad34gidgsKyiG3HeBEVziI4Uaihnb96ZQ==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-3.8.1.tgz", + "integrity": "sha512-UJqyvFr3+MTDo31fVjJlV/eshmQg8u0vpQcY95Vs00B99QxzLoICSGpNdldhSBnOIpOlq6tm5H/2hflc5hANew==", "dev": true, "requires": { - "@lerna/describe-ref": "^3.6.0", + "@lerna/describe-ref": "^3.8.1", "@lerna/validation-error": "^3.6.0" } }, @@ -890,17 +885,27 @@ "requires": { "pump": "^3.0.0" } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } } } }, "@lerna/clean": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.7.2.tgz", - "integrity": "sha512-BhuPnAWQa2av6hSE8imbOhenUnveSp0VDO1X0jzC1EX+K6sBCubbowM13kYi+N0qUd2kdeatBNwmafzkBZ3LcQ==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.8.1.tgz", + "integrity": "sha512-+jxuiKdvau3tlhiMq1cQMzzdr/4b95q4Cmpt8BD5ivrEpD+X3pCJGjjMxybpi5sZzR/XWlfJn9vn9mnFpyTrTA==", "dev": true, "requires": { - "@lerna/command": "^3.7.2", - "@lerna/filter-options": "^3.6.0", + "@lerna/command": "^3.8.1", + "@lerna/filter-options": "^3.8.1", "@lerna/prompt": "^3.6.0", "@lerna/pulse-till-done": "^3.7.1", "@lerna/rimraf-dir": "^3.6.0", @@ -1044,6 +1049,16 @@ "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", "dev": true }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "yargs": { "version": "12.0.5", "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", @@ -1077,22 +1092,22 @@ } }, "@lerna/collect-updates": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-3.6.0.tgz", - "integrity": "sha512-knliEz3phY51SGnwDhhYqx6SJN6y9qh/gZrZgQ7ogqz1UgA/MyJb27gszjsyyG6jUQshimBpjsG7OMwjt8+n9A==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-3.8.1.tgz", + "integrity": "sha512-1ULd1FBX8j8XGe166CUx+PkNeBJrbauI6Ux9+NiVrpeyE5rF6hzrowRyaptE9n5jzAl0WtTTIP1MsdrVG7BvAA==", "dev": true, "requires": { "@lerna/child-process": "^3.3.0", - "@lerna/describe-ref": "^3.6.0", + "@lerna/describe-ref": "^3.8.1", "libnpm": "^2.0.1", "minimatch": "^3.0.4", "slash": "^1.0.0" } }, "@lerna/command": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/command/-/command-3.7.2.tgz", - "integrity": "sha512-WtBnlvQfzKmnc2i3g+GLazx7pUXwbzASiXHy4j1CoC0w90H42LUqhwJICro4VhnE8xi38BNhcH/+xFNiHX5ERA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/command/-/command-3.8.1.tgz", + "integrity": "sha512-AWC1ziiSCJikuviBcQHOc9qTqGS/KwG9cy4wbk65Zxmfy373/JJEt0bg0bfo48reZ2HiJu0mBXLitd3brXpdIw==", "dev": true, "requires": { "@lerna/child-process": "^3.3.0", @@ -1143,6 +1158,16 @@ "requires": { "pump": "^3.0.0" } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } } } }, @@ -1190,17 +1215,27 @@ "requires": { "graceful-fs": "^4.1.6" } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } } } }, "@lerna/create": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.7.2.tgz", - "integrity": "sha512-eE6i4mVi5CefQ8Mw4WhkX9GcgiDllfEYfMq3LDMCtBH4pdzXO9oNG2p1J7bbwKgCFqhmKB4nr5FTFhijOIMRRw==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.8.1.tgz", + "integrity": "sha512-+mUkK9MEwTa8w69+yf5WhGF7csxn7wJRmsBe99J7QklamOSZXTLt4O5AeBtaYKFdJ9Tf5D+oxENMYLd2EaJNSw==", "dev": true, "requires": { "@lerna/child-process": "^3.3.0", - "@lerna/command": "^3.7.2", + "@lerna/command": "^3.8.1", "@lerna/npm-conf": "^3.7.0", "@lerna/validation-error": "^3.6.0", "camelcase": "^4.1.0", @@ -1231,7 +1266,7 @@ }, "globby": { "version": "8.0.1", - "resolved": "http://registry.npmjs.org/globby/-/globby-8.0.1.tgz", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", "dev": true, "requires": { @@ -1306,9 +1341,9 @@ } }, "@lerna/describe-ref": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-3.6.0.tgz", - "integrity": "sha512-hVZJ2hYVbrrNiEG+dEg/Op4pYAbROkDZdiIUabAJffr0T/frcN+5es2HfmOC//4+78Cs1M9iTyQRoyC1RXS2BQ==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-3.8.1.tgz", + "integrity": "sha512-EPXFiZbWG0KiaDM+BT3RZahy5gwrTVyKDv8HmPkGhu2h4pr34tzsSMmYmJ8I0dLeu4IpP/G/OIBa6DkYWisiZw==", "dev": true, "requires": { "@lerna/child-process": "^3.3.0", @@ -1316,38 +1351,38 @@ } }, "@lerna/diff": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/diff/-/diff-3.7.2.tgz", - "integrity": "sha512-BVcceQHxwr0hIO4hZ8Udeb1Afn2opDiMXSh3dEyV7kcbYlgc66AxsviVPr4txGP/p8uRlzBUDzgHShVMplMGcg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/diff/-/diff-3.8.1.tgz", + "integrity": "sha512-FrQ2c7AULUs4eYwLE5adw6YObRdS8EgCbq5dE/sLrvnqODniM2hRnMf/zsFl4R8wf9PIs5tFXdyTdMVsQfwZGg==", "dev": true, "requires": { "@lerna/child-process": "^3.3.0", - "@lerna/command": "^3.7.2", + "@lerna/command": "^3.8.1", "@lerna/validation-error": "^3.6.0", "libnpm": "^2.0.1" } }, "@lerna/exec": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/exec/-/exec-3.7.2.tgz", - "integrity": "sha512-oEm3EbSxXeMguqC+ekXaBlRmo/aaJc2BcWPHrd+5+9evHhHo/7oOu/xXmbhJYCgZytGkJ6BrX3F9XhWnC+14wg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/exec/-/exec-3.8.1.tgz", + "integrity": "sha512-v+AAxkq19UdTfsO0aOfmAvQs9GnDpy5TyP1q4NVOZCotLOfVnFQ16QfzVCBA4hR3a6puugs3lmaOlQnMmDoDFQ==", "dev": true, "requires": { "@lerna/batch-packages": "^3.6.0", "@lerna/child-process": "^3.3.0", - "@lerna/command": "^3.7.2", - "@lerna/filter-options": "^3.6.0", + "@lerna/command": "^3.8.1", + "@lerna/filter-options": "^3.8.1", "@lerna/run-parallel-batches": "^3.0.0", "@lerna/validation-error": "^3.6.0" } }, "@lerna/filter-options": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-3.6.0.tgz", - "integrity": "sha512-6iUMZuvvXPL5EAF7Zo9azaZ6FxOq6tGbiSX8fUXgCdN+jlRjorvkzR+E0HS4bEGTWmV446lnLwdQLZuySfLcbQ==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-3.8.1.tgz", + "integrity": "sha512-ty4Pl+vZjPSc7jc4nhK/4YYGKpLOhcGHLw6Zu71D4V3DJW/ZDHm/veRIApmhddL7+6y9G+ZGnGYYh/8RFyT6hA==", "dev": true, "requires": { - "@lerna/collect-updates": "^3.6.0", + "@lerna/collect-updates": "^3.8.1", "@lerna/filter-packages": "^3.6.0", "dedent": "^0.7.0" } @@ -1452,13 +1487,13 @@ } }, "@lerna/import": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.7.2.tgz", - "integrity": "sha512-TGTYjhzDGLEqc9imWOi/fvIbZdmVxfV71OFB6AS98N9KQE68bbpttehQqCUIPATReVuzPUzxEiF3tMnKd7iEqg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.8.1.tgz", + "integrity": "sha512-4GETPwbZOd7HuSDaqiAf6UENBLzmU2ICE7RXGoe5hIyCIYODx8q+/OSBYOdpV0hHbm/4IaKDa/k0rvCrI/dUFQ==", "dev": true, "requires": { "@lerna/child-process": "^3.3.0", - "@lerna/command": "^3.7.2", + "@lerna/command": "^3.8.1", "@lerna/prompt": "^3.6.0", "@lerna/pulse-till-done": "^3.7.1", "@lerna/validation-error": "^3.6.0", @@ -1490,13 +1525,13 @@ } }, "@lerna/init": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/init/-/init-3.7.2.tgz", - "integrity": "sha512-840Az0GtyepX7/WH3QvOQDZJCEGFf4IykjjFuCLF+23+Od8Wxn3QCsp4Yn/+HKi/w7bSpsCHJ6xQG208dygfdw==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/init/-/init-3.8.1.tgz", + "integrity": "sha512-pWS5EBaRr7kNr5uQSN06vd1kN2afIRJoYA4PEiD1Cs4Z9tPJYoDMEClPnss0sdh8O8RH3IzcDkN2gjzLn7oe/Q==", "dev": true, "requires": { "@lerna/child-process": "^3.3.0", - "@lerna/command": "^3.7.2", + "@lerna/command": "^3.8.1", "fs-extra": "^7.0.0", "p-map": "^1.2.0", "write-json-file": "^2.3.0" @@ -1525,26 +1560,26 @@ } }, "@lerna/link": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/link/-/link-3.7.2.tgz", - "integrity": "sha512-iwxftHVPknb+RXtD7257/FR4DYiCxJRxqo6z/YGlojWjehYRfbK7tJe4xzRzxepIXAE8+ooQFqQ73m0/ozk6kQ==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/link/-/link-3.8.1.tgz", + "integrity": "sha512-IKdnForLXk9XrR4i02ExfK//pQe94z0Q7TF9uSbjHk1JNLnUSzxRZ31WeuI5/ZZrhdHBJV1eDb6lJg7IRiXH5A==", "dev": true, "requires": { - "@lerna/command": "^3.7.2", + "@lerna/command": "^3.8.1", "@lerna/package-graph": "^3.6.0", - "@lerna/symlink-dependencies": "^3.7.2", + "@lerna/symlink-dependencies": "^3.8.1", "p-map": "^1.2.0", "slash": "^1.0.0" } }, "@lerna/list": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/list/-/list-3.7.2.tgz", - "integrity": "sha512-yup9KivG31APzr+C96up83m1llqs62spsLuKkinwVUhL5mobhDscT6QwIWTJPRJ8Bbmi++SdXGLfGFkYmgujzQ==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/list/-/list-3.8.1.tgz", + "integrity": "sha512-2Eafq6tlbXmCMBCpKLlQRD27b2W2ygAc6X/jYJ9bZtKZ1ZzNBjp5TWIHQAvj3/vAAR1VhSrfL+rMHIaqzEAASg==", "dev": true, "requires": { - "@lerna/command": "^3.7.2", - "@lerna/filter-options": "^3.6.0", + "@lerna/command": "^3.8.1", + "@lerna/filter-options": "^3.8.1", "@lerna/listable": "^3.6.0", "@lerna/output": "^3.6.0" } @@ -1601,9 +1636,9 @@ } }, "@lerna/npm-install": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-3.6.0.tgz", - "integrity": "sha512-RKV31VdrBZKjmKfq25JG4mIHJ8NAOsLKq/aYSaBs8zP+uwXH7RU39saVfv9ReKiAzhKE2ghOG2JeMdIHtYnPNA==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-3.8.2.tgz", + "integrity": "sha512-A22IrhPy70Gm30rBYSPp/PDWjByvj6QdxqQh41HlNGOCzM5WCCSDXpfeoxzS0ow0d3WGJMraE2k1GFpOP6Hyxg==", "dev": true, "requires": { "@lerna/child-process": "^3.3.0", @@ -1637,12 +1672,12 @@ } }, "@lerna/npm-publish": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.7.1.tgz", - "integrity": "sha512-3Tv4UWD+1Wz1Eqc7/8eEvAHL5c2pTx+rOKYMEc6P5Z1glN1+TfIfPckPAX0H2xg44yTCh1KGJSSBpJQl68QqIQ==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.8.2.tgz", + "integrity": "sha512-0ZVEmD7vQs29mV35O/8q/eCvTVA4nu49Jpq5821TMMENa2CiBmQCWv/MqLaQlPCAdTx/kXHJMQ9kmhwrhks0Aw==", "dev": true, "requires": { - "@lerna/run-lifecycle": "^3.7.1", + "@lerna/run-lifecycle": "^3.8.2", "figgy-pudding": "^3.5.1", "fs-extra": "^7.0.0", "libnpm": "^2.0.1" @@ -1691,14 +1726,14 @@ } }, "@lerna/pack-directory": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/pack-directory/-/pack-directory-3.7.2.tgz", - "integrity": "sha512-yAZNSdAsBD26as+Il1l5R0fQaI6vTJqyNeK181V2vf34+KC0NX9TVaM+/Ht28QpK+3SaD2tvVP1T7OP2w0g2qg==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@lerna/pack-directory/-/pack-directory-3.8.2.tgz", + "integrity": "sha512-sYXioixVw3L5r9O4bt+hnfPQ8jLn/PVOJVivQdpSlup+ZUiI5mmtjzAO3NOK/k8DZf8YrUx0Lm1+vzgnqu1tKw==", "dev": true, "requires": { "@lerna/get-packed": "^3.7.0", "@lerna/package": "^3.7.2", - "@lerna/run-lifecycle": "^3.7.1", + "@lerna/run-lifecycle": "^3.8.2", "figgy-pudding": "^3.5.1", "libnpm": "^2.0.1", "npm-packlist": "^1.1.12", @@ -1819,7 +1854,7 @@ }, "globby": { "version": "8.0.1", - "resolved": "http://registry.npmjs.org/globby/-/globby-8.0.1.tgz", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", "dev": true, "requires": { @@ -1955,33 +1990,32 @@ } }, "@lerna/publish": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.8.0.tgz", - "integrity": "sha512-EJDF6oPySIHQRre9KMMqtltrPReuBT7Po72W6OQxCUmCjqDyUd6884lhqFHOgbtOl1axrVVaSOpxCU1m+SLNgA==", + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.8.4.tgz", + "integrity": "sha512-Bbz3Ne0UHiBqLrHAhB2+zitzYjgjIiydLmhLiuzhwbPgah7kP7nv9/HrXv927kjEY7EHd/+zqs/NuviJvKKb0g==", "dev": true, "requires": { "@lerna/batch-packages": "^3.6.0", - "@lerna/check-working-tree": "^3.6.0", + "@lerna/check-working-tree": "^3.8.1", "@lerna/child-process": "^3.3.0", - "@lerna/collect-updates": "^3.6.0", - "@lerna/command": "^3.7.2", - "@lerna/describe-ref": "^3.6.0", + "@lerna/collect-updates": "^3.8.1", + "@lerna/command": "^3.8.1", + "@lerna/describe-ref": "^3.8.1", "@lerna/log-packed": "^3.6.0", "@lerna/npm-conf": "^3.7.0", "@lerna/npm-dist-tag": "^3.7.1", - "@lerna/npm-publish": "^3.7.1", + "@lerna/npm-publish": "^3.8.2", "@lerna/output": "^3.6.0", - "@lerna/pack-directory": "^3.7.2", + "@lerna/pack-directory": "^3.8.2", "@lerna/prompt": "^3.6.0", "@lerna/pulse-till-done": "^3.7.1", - "@lerna/run-lifecycle": "^3.7.1", + "@lerna/run-lifecycle": "^3.8.2", "@lerna/run-parallel-batches": "^3.0.0", "@lerna/validation-error": "^3.6.0", - "@lerna/version": "^3.8.0", + "@lerna/version": "^3.8.2", "figgy-pudding": "^3.5.1", "fs-extra": "^7.0.0", "libnpm": "^2.0.1", - "npm-registry-fetch": "^3.8.0", "p-finally": "^1.0.0", "p-map": "^1.2.0", "p-pipe": "^1.2.0", @@ -2066,14 +2100,14 @@ } }, "@lerna/run": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/run/-/run-3.7.2.tgz", - "integrity": "sha512-FwBjcrtYSFyvY2YXJ8GoI9VNv2UElUbVra5+iTF1DgQh37RmK0ZCODkfXp6PYyUszHkgCRuJqhK0+yMWRJo61w==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/run/-/run-3.8.1.tgz", + "integrity": "sha512-EKJsdfuZYLr6QrEqOt7qteBIR/hZ/Fdk526fdVBtryaL5t1UESwaF741M65WWQMJnFOtJZI6n2tna5CX0n7xVQ==", "dev": true, "requires": { "@lerna/batch-packages": "^3.6.0", - "@lerna/command": "^3.7.2", - "@lerna/filter-options": "^3.6.0", + "@lerna/command": "^3.8.1", + "@lerna/filter-options": "^3.8.1", "@lerna/npm-run-script": "^3.6.0", "@lerna/output": "^3.6.0", "@lerna/run-parallel-batches": "^3.0.0", @@ -2083,9 +2117,9 @@ } }, "@lerna/run-lifecycle": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.7.1.tgz", - "integrity": "sha512-kE6w8d8Qde+ewZaDNIz4zhwde8s/i8vbbOsGDlR/Vw/9nqlmtj2YBZaS262NtWj83N04dtdYr4FVj51thciGQw==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.8.2.tgz", + "integrity": "sha512-V80nIoHpp0IL9WEaK8wz47SeqLSlI8DyNqVqUoG1DCeCGr6rdvRGD0FgHSKgRnZxk/SgP1Az/YBBtc5qnA+CzA==", "dev": true, "requires": { "@lerna/npm-conf": "^3.7.0", @@ -2138,9 +2172,9 @@ } }, "@lerna/symlink-dependencies": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-3.7.2.tgz", - "integrity": "sha512-53fZUGQ+QLr5P7I9/pqFmCizLo4Q/Jz5ETd1NURO2+eABGdYuTnuvtqyGku+eOr9A4gYDaVmg50KEpsOXq9TWg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-3.8.1.tgz", + "integrity": "sha512-MlXRTpB3Go/ubexxySngzg8PnItpPIxa0ydHMxvvw7s06g7ZsOOMOAx+F7AUPPr7bssdZ+gg5bfSq+J1HoINrg==", "dev": true, "requires": { "@lerna/create-symlink": "^3.6.0", @@ -2190,20 +2224,20 @@ } }, "@lerna/version": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.8.0.tgz", - "integrity": "sha512-c+TNPzlyv0dgDpgMu87CPauk8R2jZwwftgQarHOCGbEZ0ClXqLFTEAKxvLpzprlt+kH3goIWYNQrZiJflpMOCA==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.8.2.tgz", + "integrity": "sha512-TANvavzkyTcSVElsh8+zTZmsjzF6B0xuE9ldRdFKQaYAqmkkWNmf73BwLDZGisqEh45wnveiR9GeflSw4XI4iw==", "dev": true, "requires": { "@lerna/batch-packages": "^3.6.0", - "@lerna/check-working-tree": "^3.6.0", + "@lerna/check-working-tree": "^3.8.1", "@lerna/child-process": "^3.3.0", - "@lerna/collect-updates": "^3.6.0", - "@lerna/command": "^3.7.2", + "@lerna/collect-updates": "^3.8.1", + "@lerna/command": "^3.8.1", "@lerna/conventional-commits": "^3.6.0", "@lerna/output": "^3.6.0", "@lerna/prompt": "^3.6.0", - "@lerna/run-lifecycle": "^3.7.1", + "@lerna/run-lifecycle": "^3.8.2", "@lerna/validation-error": "^3.6.0", "chalk": "^2.3.1", "dedent": "^0.7.0", @@ -2272,9 +2306,9 @@ "integrity": "sha512-59/mWwU7sXHfoU2kI3RcWRki2Jjbz5nEVJNBN4MUyIhPjXTebAcZqgsQACvlk+sjKVOTMEMHcrFrKQbaxz/1Dw==" }, "@types/node": { - "version": "10.12.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", - "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" + "version": "10.12.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz", + "integrity": "sha512-9kROxduaN98QghwwHmxXO2Xz3MaWf+I1sLVAA6KJDF5xix+IyXVhds0MAfdNwtcpSrzhaTsNB0/jnL86fgUhqA==" }, "@webassemblyjs/ast": { "version": "1.7.11", @@ -2458,14 +2492,14 @@ } }, "@wordpress/api-fetch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-2.2.2.tgz", - "integrity": "sha512-yqJjHR+EjbMZ9BO3DWJqydvlRZoS9M1NvdwXouzkmxCCdVF4JfmWdydfakeXreYH8+T01V/JjP2rZYcQDn36/Q==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-2.2.6.tgz", + "integrity": "sha512-cZfJrkfv4+w8rWEMzuh6Q5QoaT1MxD+K3MuT+PHP/SZiSS0AuP733LsWlYJDo28EatNUHU2XPR3IpeKzbiHlvQ==", "requires": { "@babel/runtime": "^7.0.0", "@wordpress/hooks": "^2.0.3", - "@wordpress/i18n": "^3.0.1", - "@wordpress/url": "^2.2.0" + "@wordpress/i18n": "^3.1.0", + "@wordpress/url": "^2.3.2" }, "dependencies": { "@wordpress/i18n": { @@ -2504,9 +2538,9 @@ } }, "@wordpress/babel-preset-default": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-2.1.0.tgz", - "integrity": "sha512-0Fb6FS4oGQ8AWJ2fMvkX23Y/fJWFwe5PP5ASdTu3qbgajiMmesxGcnpk2Sx59CL74S+LtOUaUkG4eusEkJP4Mg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-3.0.1.tgz", + "integrity": "sha512-5pbmYh0b4flwyAbuEMTOgTZJ1jqn5iucXhoAESWHZyHCn6D+s36ws0RkFrefz1mYQv4KgIoVpqshDXdN9wR/Bg==", "dev": true, "requires": { "@babel/plugin-proposal-async-generator-functions": "^7.0.0", @@ -2525,71 +2559,107 @@ "integrity": "sha512-RZ9XeDeXTc/l3RdSnfYYwcsylFPouV+2ZpQQaAgALSXthMWJT2wU61zD4mH9aMI5Oo6Z8OUVI2vOZM/7HObPxw==" }, "@wordpress/components": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-3.0.0.tgz", - "integrity": "sha512-W0ca62EGf2as+wintOzZP1W4DzKBJmuhSsrbuKLUXMCZPh9x0KIuedlTOrRYh5bkK0mWtQ1DQRwbnh01dYBN2A==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-7.0.5.tgz", + "integrity": "sha512-Csu2z4r8g3PwwLWWRMDeA//vQhHh05J9hyogE15dMS/6jrSKyl8lonGWvbnO2PSrMzKIHPeoXYzutUeNBjY1pg==", "requires": { "@babel/runtime": "^7.0.0", - "@wordpress/a11y": "^2.0.0", - "@wordpress/api-fetch": "^2.0.0", - "@wordpress/compose": "^2.0.0", - "@wordpress/deprecated": "^2.0.0", - "@wordpress/dom": "^2.0.0", - "@wordpress/element": "^2.0.0", - "@wordpress/hooks": "^2.0.0", - "@wordpress/i18n": "^2.0.0", + "@wordpress/a11y": "^2.0.2", + "@wordpress/api-fetch": "^2.2.7", + "@wordpress/compose": "^3.0.0", + "@wordpress/dom": "^2.0.8", + "@wordpress/element": "^2.1.8", + "@wordpress/hooks": "^2.0.4", + "@wordpress/i18n": "^3.1.0", "@wordpress/is-shallow-equal": "^1.1.4", - "@wordpress/keycodes": "^2.0.0", - "@wordpress/url": "^2.0.0", + "@wordpress/keycodes": "^2.0.5", + "@wordpress/rich-text": "^3.0.4", + "@wordpress/url": "^2.3.3", "classnames": "^2.2.5", - "clipboard": "^1.7.1", + "clipboard": "^2.0.1", + "diff": "^3.5.0", "dom-scroll-into-view": "^1.2.1", - "element-closest": "^2.0.2", "lodash": "^4.17.10", "memize": "^1.0.5", "moment": "^2.22.1", "mousetrap": "^1.6.2", - "react-click-outside": "^2.3.1", - "react-color": "^2.13.4", - "react-datepicker": "^1.4.1", + "re-resizable": "^4.7.1", + "react-click-outside": "^3.0.0", + "react-dates": "^17.1.1", "rememo": "^3.0.0", - "uuid": "^3.1.0" + "tinycolor2": "^1.4.1", + "uuid": "^3.3.2" }, "dependencies": { - "react-click-outside": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/react-click-outside/-/react-click-outside-2.3.1.tgz", - "integrity": "sha1-MYc3698IGko7zUaCVmNnTL6YNus=", + "@wordpress/api-fetch": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-2.2.7.tgz", + "integrity": "sha512-7Bl4hfLLw5rWeQ7LIa3U/rananC2Tk6uZ55acetC+hag9gJpDpddbXOlW3Li8Zk1cJvvcpgfElVPwPFnS0e/qg==", "requires": { - "hoist-non-react-statics": "^1.2.0" + "@babel/runtime": "^7.0.0", + "@wordpress/hooks": "^2.0.4", + "@wordpress/i18n": "^3.1.0", + "@wordpress/url": "^2.3.3" + } + }, + "@wordpress/hooks": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-2.0.4.tgz", + "integrity": "sha512-9Z+zymxRI1vNVPnAhGOYjp05Do/rNWdj/0lrx0oC8vluQFyaU9OWS7+il1eL8kfr+27nzcBnoU0e2WfvoXVhyg==", + "requires": { + "@babel/runtime": "^7.0.0" + } + }, + "@wordpress/url": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-2.3.3.tgz", + "integrity": "sha512-WGqQjOyu02E7bJ77G8385GGjUYpvF8vDqZXXHW06/WRSb4nW6fmMIM65UWdBaYY5XecAkpglCqwd8DNbquLucQ==", + "requires": { + "@babel/runtime": "^7.0.0", + "qs": "^6.5.2" + } + }, + "clipboard": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", + "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, + "react-dates": { + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/react-dates/-/react-dates-17.2.0.tgz", + "integrity": "sha512-RDlerU8DdRRrlYS0MQ7Z9igPWABGLDwz6+ykBNff67RM3Sset2TDqeuOr+R5o00Ggn5U47GeLsGcSDxlZd9cHw==", + "requires": { + "airbnb-prop-types": "^2.10.0", + "consolidated-events": "^1.1.1 || ^2.0.0", + "is-touch-device": "^1.0.1", + "lodash": "^4.1.1", + "object.assign": "^4.1.0", + "object.values": "^1.0.4", + "prop-types": "^15.6.1", + "react-addons-shallow-compare": "^15.6.2", + "react-moment-proptypes": "^1.6.0", + "react-outside-click-handler": "^1.2.0", + "react-portal": "^4.1.5", + "react-with-styles": "^3.2.0", + "react-with-styles-interface-css": "^4.0.2" } } } }, "@wordpress/compose": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-2.1.2.tgz", - "integrity": "sha512-jr1bWRYx8vt4bCWB4hqR9Ve9rk/TCJ5mFRvkwEd+xTDyXG6ZMmps3HszGA6aCH7E+TCHH+bWw2qZo9sAeHVK/w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-3.0.0.tgz", + "integrity": "sha512-jghgcLLKYQiIxjKp1q9FGcLlbeTKmYUIbYcru2AX7VF1uqp85oeRcuWsowrQUvomWHADcf09psBfDo2Gz/OH8A==", "requires": { "@babel/runtime": "^7.0.0", - "@wordpress/deprecated": "^2.0.3", - "@wordpress/element": "^2.1.7", + "@wordpress/element": "^2.1.8", "@wordpress/is-shallow-equal": "^1.1.4", "lodash": "^4.17.10" - }, - "dependencies": { - "@wordpress/element": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-2.1.8.tgz", - "integrity": "sha512-hPbNWcxGQCpTeXoTdwr0Bu3kNJMSSKAnIb5B8P/2lTQ9mJ6w8l1Vc/0L11Yy8+uElaLwGq4Lja9ljgTlWbXUkA==", - "requires": { - "@babel/runtime": "^7.0.0", - "@wordpress/escape-html": "^1.0.1", - "lodash": "^4.17.10", - "react": "^16.6.3", - "react-dom": "^16.6.3" - } - } } }, "@wordpress/custom-templated-path-webpack-plugin": { @@ -2603,14 +2673,13 @@ } }, "@wordpress/data": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-3.1.0.tgz", - "integrity": "sha512-XQNSOy7xSHVi4KGxOOd6gJJ3+Qq4XWZMK5hMBSEwVCiyIcSbcP6I1AT1NnnPuJeH94BmFCPNADElHB4gazg8xA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-4.2.0.tgz", + "integrity": "sha512-9cdm5xBWgLJJ45nQCbUQFF26vGoG2PzEJrinNXLr45XNAr3XSzN/JsrmNCaHwI/NzyFIwFmFjv6ONQKxxkErrg==", "requires": { "@babel/runtime": "^7.0.0", - "@wordpress/compose": "^2.1.0", - "@wordpress/deprecated": "^2.0.3", - "@wordpress/element": "^2.1.5", + "@wordpress/compose": "^3.0.0", + "@wordpress/element": "^2.1.8", "@wordpress/is-shallow-equal": "^1.1.4", "@wordpress/redux-routine": "^3.0.3", "equivalent-key-map": "^0.2.2", @@ -2621,28 +2690,19 @@ } }, "@wordpress/date": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-2.1.0.tgz", - "integrity": "sha512-MMDU2/N0unnwKTmaeCYy4fW/CuyCdM5t+/ANXVigBxY/IiOGeRXnmiU4VHK1BshEnsWRJz687MmEGEkxVpwd8w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-3.0.1.tgz", + "integrity": "sha512-LOOwZM0A5OeElWgdyuR3LJQ7sJJZ5oHdXnNTs3LEB5GH7FUoozF6B6KY5Qm13pizzWX018C8vggsHrsltuLo3A==", "requires": { "@babel/runtime": "^7.0.0", "moment": "^2.22.1", "moment-timezone": "^0.5.16" } }, - "@wordpress/deprecated": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-2.0.3.tgz", - "integrity": "sha512-5v8h6BJ9xQFTho7ucitshpIahD+rVnAhgc/4juYmPLb9/GJzwY1J91Ve5mcjcjgWhdtjBKO0TCq/S4PCfS812w==", - "requires": { - "@babel/runtime": "^7.0.0", - "@wordpress/hooks": "^2.0.3" - } - }, "@wordpress/dom": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-2.0.7.tgz", - "integrity": "sha512-vjOdGSpW3WdHH5oOoamfzdoyF4BbUJOWNNT7bBb2y15GII8rN1cGyGxqVDiiajMDe51p3lyWWCpUeY4ppxj/UA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-2.0.8.tgz", + "integrity": "sha512-Nz1k1tB/NXcfpAWUL+mTtEzxC6Dp6UAavIzJVQgAq8gsdayh7F9lgkyyL5MWLirAKkGuhztwMrSle9s5HzrTlw==", "requires": { "@babel/runtime": "^7.0.0", "lodash": "^4.17.10" @@ -2657,15 +2717,15 @@ } }, "@wordpress/element": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-2.1.5.tgz", - "integrity": "sha512-y567y5SZv3VXDrUw/qpO7ElrTc8/BhumAivVlpAItychfn/PdTxd2l5V1Cc91Uc2/JDoAhPCRIO23UFWX+Ehtw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-2.1.8.tgz", + "integrity": "sha512-hPbNWcxGQCpTeXoTdwr0Bu3kNJMSSKAnIb5B8P/2lTQ9mJ6w8l1Vc/0L11Yy8+uElaLwGq4Lja9ljgTlWbXUkA==", "requires": { "@babel/runtime": "^7.0.0", "@wordpress/escape-html": "^1.0.1", "lodash": "^4.17.10", - "react": "^16.4.1", - "react-dom": "^16.4.1" + "react": "^16.6.3", + "react-dom": "^16.6.3" } }, "@wordpress/escape-html": { @@ -2685,23 +2745,24 @@ } }, "@wordpress/html-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-2.0.2.tgz", - "integrity": "sha512-cxG7YjH9EMfZyeLJAd/Vc1nFJxitMSzybv71iMPP3Dqqgz3jixX6oSe4ukTqfoOKBaF7pY7LzS6eTKu7KAmyZw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-2.0.4.tgz", + "integrity": "sha512-waT+n+sLLzoI7dUovWGwTUB25iNoRyktRYroc4NVgAbDkKuN5Dsi9IOmJnNNitdQ17HEAOn++ZO6X5/mbBkvBA==", "requires": { "@babel/runtime": "^7.0.0" } }, "@wordpress/i18n": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-2.0.0.tgz", - "integrity": "sha512-q8rY7RIkHRmtZpkodk+1WJMwr8r/NIr7oFK67/1+ek1ervp+psSCAPF543m6DzYxCDxDj/m/Q55d31ImpvU/xg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-3.1.0.tgz", + "integrity": "sha512-zHqLRuKrDV3FYh8PYDs4ABO/csiEAy1EfTffMtMS/8GAz4BcWrcqDjyH42GJF8iwWdG5+DdsllP5oerAQMHnng==", "requires": { "@babel/runtime": "^7.0.0", "gettext-parser": "^1.3.1", - "jed": "^1.1.1", "lodash": "^4.17.10", - "memize": "^1.0.5" + "memize": "^1.0.5", + "sprintf-js": "^1.1.1", + "tannin": "^1.0.1" } }, "@wordpress/is-shallow-equal": { @@ -2723,25 +2784,41 @@ } }, "@wordpress/jest-preset-default": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-2.0.6.tgz", - "integrity": "sha512-H+/2wYrGIwZ/MLzaev0TZvyxHWCH/OVpl0MjeSfiobza0+yDOqv6S5DNlcLpIHw9s324awl3pjBfjvyNr1C9tA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-3.0.3.tgz", + "integrity": "sha512-CjUmasTBL1n8jdnp+yvrZ3hEcZtC5e20aelHB594JeY4l2Lk0f96nl4PaNi/Tpd/QsAiZeTTW0DhTT97FKgdtg==", "dev": true, "requires": { - "@wordpress/jest-console": "^2.0.6", - "babel-jest": "^23.4.2", - "enzyme": "^3.3.0", - "enzyme-adapter-react-16": "^1.1.1", + "@wordpress/jest-console": "^2.0.7", + "babel-jest": "^23.6.0", + "enzyme": "^3.7.0", + "enzyme-adapter-react-16": "^1.6.0", "jest-enzyme": "^6.0.2" } }, "@wordpress/keycodes": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-2.0.3.tgz", - "integrity": "sha512-cg4IHxz9BjRHBIhs/e0NwO/snImNqMQLZfduPf6Y+GGlq28BbQuxnucDj0ktYw/4X9jxf6IHzWUq9bkX65QGiA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-2.0.5.tgz", + "integrity": "sha512-uEnLRbEe+6FkXKTdQordwR9fBExIngnsa6FmAJ2ODzEI872g271jM5W61m33WzsBHfbFHQKqUi+ZaFAzu7XUcg==", "requires": { "@babel/runtime": "^7.0.0", + "@wordpress/i18n": "^3.1.0", "lodash": "^4.17.10" + }, + "dependencies": { + "@wordpress/i18n": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-3.1.0.tgz", + "integrity": "sha512-zHqLRuKrDV3FYh8PYDs4ABO/csiEAy1EfTffMtMS/8GAz4BcWrcqDjyH42GJF8iwWdG5+DdsllP5oerAQMHnng==", + "requires": { + "@babel/runtime": "^7.0.0", + "gettext-parser": "^1.3.1", + "lodash": "^4.17.10", + "memize": "^1.0.5", + "sprintf-js": "^1.1.1", + "tannin": "^1.0.1" + } + } } }, "@wordpress/npm-package-json-lint-config": { @@ -2769,19 +2846,32 @@ "rungen": "^0.3.2" } }, + "@wordpress/rich-text": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-3.0.4.tgz", + "integrity": "sha512-dv14T0wk0YPZtDNdoJKuduf3McCdQpcUHczhMoYg3WssBkyOPY3Vak8RH6t/uEnEwZhgJIolkcwrTedIFXiC4Q==", + "requires": { + "@babel/runtime": "^7.0.0", + "@wordpress/compose": "^3.0.0", + "@wordpress/data": "^4.2.0", + "@wordpress/escape-html": "^1.0.1", + "lodash": "^4.17.10", + "rememo": "^3.0.0" + } + }, "@wordpress/scripts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-2.4.1.tgz", - "integrity": "sha512-FXr451wUvVSve7am1lk7CthGJqjiqFMyNBYykQdZQ5OBWhXbvO8zMu5MCkISWOHIXXpJ3hedvrujoBp/psx+Fg==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-2.4.4.tgz", + "integrity": "sha512-cMjtC8enrY+JXXm8toIz7kTOaTjJ8k8+8eRghRks+D3s51ylVOSsfdu8UFh3O0c4Z+nOheTYZXKoWkAn6HmNVw==", "requires": { "@wordpress/babel-preset-default": "^3.0.1", - "@wordpress/jest-preset-default": "^3.0.0", + "@wordpress/jest-preset-default": "^3.0.3", "@wordpress/npm-package-json-lint-config": "^1.1.5", "babel-eslint": "8.0.3", "chalk": "^2.4.1", "cross-spawn": "^5.1.0", "eslint": "^4.19.1", - "jest": "^23.4.2", + "jest": "^23.6.0", "npm-package-json-lint": "^3.3.1", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0" @@ -2916,7 +3006,7 @@ }, "eslint": { "version": "4.19.1", - "resolved": "http://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", "requires": { "ajv": "^5.3.0", @@ -2968,7 +3058,7 @@ }, "fast-deep-equal": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" }, "globals": { @@ -3124,7 +3214,7 @@ }, "acorn-jsx": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", "requires": { "acorn": "^3.0.4" @@ -3132,7 +3222,7 @@ "dependencies": { "acorn": { "version": "3.3.0", - "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" } } @@ -3215,7 +3305,7 @@ }, "ansi-escapes": { "version": "3.1.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==" }, "ansi-regex": { @@ -3317,7 +3407,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "requires": { "kind-of": "^3.0.2" @@ -3335,7 +3425,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "requires": { "kind-of": "^3.0.2" @@ -3529,7 +3619,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -3544,7 +3634,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -3563,7 +3653,7 @@ "dependencies": { "sprintf-js": { "version": "1.0.3", - "resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" } } @@ -3604,7 +3694,7 @@ }, "array-equal": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" }, "array-find-index": { @@ -3713,7 +3803,7 @@ }, "util": { "version": "0.10.3", - "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -3853,7 +3943,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { "ansi-styles": "^2.2.1", @@ -3870,7 +3960,7 @@ }, "supports-color": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" } } @@ -3923,7 +4013,7 @@ "dependencies": { "jsesc": { "version": "1.3.0", - "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" } } @@ -3991,7 +4081,7 @@ }, "babel-plugin-istanbul": { "version": "4.1.6", - "resolved": "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", "requires": { "babel-plugin-syntax-object-rest-spread": "^6.13.0", @@ -4007,13 +4097,13 @@ }, "babel-plugin-syntax-class-properties": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", "dev": true }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=" }, "babel-plugin-transform-class-properties": { @@ -4293,9 +4383,9 @@ } }, "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", "dev": true }, "bin-links": { @@ -4319,7 +4409,7 @@ }, "bindings": { "version": "1.2.1", - "resolved": "http://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=", "dev": true }, @@ -4414,14 +4504,14 @@ "dependencies": { "resolve": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" } } }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -4458,7 +4548,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -4523,14 +4613,14 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } }, "buffer": { "version": "4.9.1", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -4581,7 +4671,7 @@ }, "cacache": { "version": "10.0.4", - "resolved": "http://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", "dev": true, "requires": { @@ -4656,14 +4746,14 @@ "dependencies": { "callsites": { "version": "0.2.0", - "resolved": "http://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=" } } }, "callsites": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" }, "camelcase": { @@ -4682,9 +4772,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000925", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000925.tgz", - "integrity": "sha512-zcYupoUxtW46rOikuDF7vfL9N1Qe9ZuUBTz3n3q8fFsoJIs/h9UN6Vg/0QpjsmvImXw9mVc3g+ZBfqvUz/iALA==" + "version": "1.0.30000921", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000921.tgz", + "integrity": "sha512-Bu09ciy0lMWLgpYC77I0YGuI8eFRBPPzaSOYJK1jTI64txCphYCqnWbxJYjHABYVt/TYX/p3jNjLBR87u1Bfpw==" }, "capture-exit": { "version": "1.2.0", @@ -4982,6 +5072,7 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz", "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=", + "optional": true, "requires": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -5096,7 +5187,7 @@ }, "color": { "version": "0.11.4", - "resolved": "http://registry.npmjs.org/color/-/color-0.11.4.tgz", + "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", "dev": true, "requires": { @@ -5120,7 +5211,7 @@ }, "color-string": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", "dev": true, "requires": { @@ -5128,10 +5219,9 @@ } }, "colors": { - "version": "1.1.2", - "resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", + "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=" }, "columnify": { "version": "1.5.4", @@ -5152,9 +5242,9 @@ } }, "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" }, "commondir": { "version": "1.0.1", @@ -5205,7 +5295,7 @@ }, "concat-stream": { "version": "1.6.2", - "resolved": "http://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "requires": { "buffer-from": "^1.0.0", @@ -5216,7 +5306,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -5230,7 +5320,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -5275,13 +5365,13 @@ } }, "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "dev": true, "requires": { "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", + "get-stream": "^3.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -5298,15 +5388,6 @@ "locate-path": "^3.0.0" } }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", @@ -5350,20 +5431,20 @@ } }, "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", + "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", "dev": true, "requires": { - "execa": "^1.0.0", + "execa": "^0.10.0", "lcid": "^2.0.0", "mem": "^4.0.0" } }, "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -5506,9 +5587,9 @@ } }, "connect-livereload": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.1.tgz", - "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.0.tgz", + "integrity": "sha1-+fAJh0rWg3GDr7FwtMTjhXodfOs=", "dev": true }, "console-browserify": { @@ -5701,7 +5782,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -5815,7 +5896,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -5920,7 +6001,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -6106,7 +6187,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -6119,7 +6200,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -6131,15 +6212,6 @@ "sha.js": "^2.4.8" } }, - "create-react-context": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.2.2.tgz", - "integrity": "sha512-KkpaLARMhsTsgp0d2NA/R94F/eDLbhXERdIq3LvX2biCAXcDvHYoOqHfWCHf1+OLj+HKBotLG3KqaOOf+C1C+A==", - "requires": { - "fbjs": "^0.8.0", - "gud": "^1.0.0" - } - }, "cross-env": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz", @@ -6247,6 +6319,38 @@ "schema-utils": "^1.0.0" }, "dependencies": { + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, "postcss": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", @@ -6268,7 +6372,7 @@ }, "css-select": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "requires": { "boolbase": "~1.0.0", @@ -6290,13 +6394,13 @@ "dependencies": { "jsesc": { "version": "0.5.0", - "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true }, "regexpu-core": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", "dev": true, "requires": { @@ -6307,13 +6411,13 @@ }, "regjsgen": { "version": "0.2.0", - "resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", "dev": true }, "regjsparser": { "version": "0.1.5", - "resolved": "http://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, "requires": { @@ -6361,9 +6465,9 @@ "dev": true }, "d3-array": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.0.3.tgz", - "integrity": "sha512-C7g4aCOoJa+/K5hPVqZLG8wjYHsTUROTk7Z1Ep9F4P5l+WVrvV0+6nAZ1wKTRLMhFWpGbozxUpyjIPZYAaLi+g==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.0.2.tgz", + "integrity": "sha512-dbjbKhMdnTW6ToWsopRs4/+ZPOsqQE1QeeoUEvzIqhdkT29zzAUcUh8HMb92tTITVlbW/G0UBz4ryBqC2RTUIA==" }, "d3-axis": { "version": "1.0.12", @@ -6478,7 +6582,7 @@ }, "dashify": { "version": "0.2.2", - "resolved": "http://registry.npmjs.org/dashify/-/dashify-0.2.2.tgz", + "resolved": "https://registry.npmjs.org/dashify/-/dashify-0.2.2.tgz", "integrity": "sha1-agdBWgHJH69KMuONnfunH2HLIP4=", "dev": true }, @@ -6534,7 +6638,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { @@ -6559,7 +6663,7 @@ }, "meow": { "version": "3.7.0", - "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { @@ -6577,7 +6681,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -6619,9 +6723,9 @@ } }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", "requires": { "ms": "^2.1.1" } @@ -6845,7 +6949,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -6903,6 +7007,14 @@ "prismjs": "^1.15.0", "tinydate": "^1.0.0", "tweezer.js": "^1.4.0" + }, + "dependencies": { + "marked": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.5.2.tgz", + "integrity": "sha512-fdZvBa7/vSQIZCi4uuwo2N3q+7jJURpMVCcbaX0S1Mg65WZ5ilXvC67MviJAsdjqqgD+CEq4RKo5AYGgINkVAA==", + "dev": true + } } }, "docsify-cli": { @@ -6942,7 +7054,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -6975,7 +7087,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -6984,7 +7096,7 @@ }, "string-width": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { @@ -6995,7 +7107,7 @@ }, "supports-color": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, @@ -7107,7 +7219,7 @@ "dependencies": { "domelementtype": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" } } @@ -7164,7 +7276,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -7188,7 +7300,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -7203,7 +7315,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -7236,7 +7348,7 @@ "dependencies": { "lru-cache": { "version": "3.2.0", - "resolved": "http://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz", "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=", "dev": true, "requires": { @@ -7258,14 +7370,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.96", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.96.tgz", - "integrity": "sha512-ZUXBUyGLeoJxp4Nt6G/GjBRLnyz8IKQGexZ2ndWaoegThgMGFO1tdDYID5gBV32/1S83osjJHyfzvanE/8HY4Q==" - }, - "element-closest": { - "version": "2.0.2", - "resolved": "http://registry.npmjs.org/element-closest/-/element-closest-2.0.2.tgz", - "integrity": "sha1-cqdAoQdFM4LijfnOXbtajfD5Zuw=" + "version": "1.3.94", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.94.tgz", + "integrity": "sha512-miQqXALb6eBD3OetCtg3UM5XTLMwHISux0l6mh14iiV5SE+qvftgOCXT9Vvp53fWaCLET4sfA/SmIMYHXkaNmw==" }, "elliptic": { "version": "6.4.1", @@ -7459,7 +7566,7 @@ }, "es6-promisify": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { @@ -7543,9 +7650,9 @@ }, "dependencies": { "acorn": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", - "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.5.tgz", + "integrity": "sha512-i33Zgp3XWtmZBMNvCr4azvOFeWVw1Rk6p3hfi3LUDvIFraOMywb1kAtrbi+med14m4Xfpqm3zRZMT+c0FNE7kg==", "dev": true }, "acorn-jsx": { @@ -7784,7 +7891,7 @@ }, "espree": { "version": "3.5.4", - "resolved": "http://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", "requires": { "acorn": "^5.5.0", @@ -7830,13 +7937,13 @@ }, "eventemitter2": { "version": "0.4.14", - "resolved": "http://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", "dev": true }, "events": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", "dev": true }, @@ -7896,7 +8003,7 @@ }, "expand-range": { "version": "1.8.2", - "resolved": "http://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "requires": { "fill-range": "^2.1.0" @@ -7964,7 +8071,7 @@ }, "external-editor": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "requires": { "chardet": "^0.4.0", @@ -8119,7 +8226,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -8139,7 +8246,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -8410,7 +8517,7 @@ "dependencies": { "core-js": { "version": "1.2.7", - "resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" } } @@ -8544,13 +8651,13 @@ "dependencies": { "colors": { "version": "0.6.2", - "resolved": "http://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", "dev": true }, "commander": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/commander/-/commander-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz", "integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E=", "dev": true } @@ -8558,7 +8665,7 @@ }, "findup-sync": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", "dev": true, "requires": { @@ -8615,7 +8722,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -8630,7 +8737,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -8699,7 +8806,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -8714,7 +8821,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -8773,7 +8880,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -8788,7 +8895,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -8814,21 +8921,25 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "optional": true }, "are-we-there-yet": { "version": "1.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "optional": true, "requires": { "delegates": "^1.0.0", @@ -8837,13 +8948,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8851,32 +8962,35 @@ }, "chownr": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "optional": true }, "debug": { "version": "2.6.9", - "bundled": true, + "resolved": false, + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "optional": true, "requires": { "ms": "2.0.0" @@ -8884,22 +8998,26 @@ }, "deep-extend": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -8907,12 +9025,14 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { "aproba": "^1.0.3", @@ -8927,7 +9047,8 @@ }, "glob": { "version": "7.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -8940,12 +9061,14 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, "iconv-lite": { "version": "0.4.21", - "bundled": true, + "resolved": false, + "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", "optional": true, "requires": { "safer-buffer": "^2.1.0" @@ -8953,7 +9076,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "optional": true, "requires": { "minimatch": "^3.0.4" @@ -8961,7 +9085,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { "once": "^1.3.0", @@ -8970,44 +9095,46 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" } }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { "version": "2.2.4", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -9015,7 +9142,8 @@ }, "minizlib": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -9023,20 +9151,22 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "optional": true }, "needle": { "version": "2.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", "optional": true, "requires": { "debug": "^2.1.2", @@ -9046,7 +9176,8 @@ }, "node-pre-gyp": { "version": "0.10.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", "optional": true, "requires": { "detect-libc": "^1.0.2", @@ -9063,7 +9194,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { "abbrev": "1", @@ -9072,12 +9204,14 @@ }, "npm-bundled": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", "optional": true }, "npm-packlist": { "version": "1.1.10", - "bundled": true, + "resolved": false, + "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -9086,7 +9220,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -9097,35 +9232,39 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "optional": true, "requires": { "os-homedir": "^1.0.0", @@ -9134,17 +9273,20 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "optional": true }, "rc": { "version": "1.2.7", - "bundled": true, + "resolved": false, + "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", "optional": true, "requires": { "deep-extend": "^0.5.1", @@ -9155,14 +9297,16 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "optional": true } } }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -9176,7 +9320,8 @@ }, "rimraf": { "version": "2.6.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "optional": true, "requires": { "glob": "^7.0.5" @@ -9184,37 +9329,43 @@ }, "safe-buffer": { "version": "5.1.1", - "bundled": true + "resolved": false, + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, "semver": { "version": "5.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, - "optional": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9223,7 +9374,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -9231,19 +9383,22 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, "tar": { "version": "4.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", "optional": true, "requires": { "chownr": "^1.0.1", @@ -9257,12 +9412,14 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "optional": true }, "wide-align": { "version": "1.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "optional": true, "requires": { "string-width": "^1.0.2" @@ -9270,11 +9427,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "yallist": { "version": "3.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" } } }, @@ -9337,7 +9496,7 @@ }, "string-width": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { @@ -9405,7 +9564,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { @@ -9430,7 +9589,7 @@ }, "meow": { "version": "3.7.0", - "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { @@ -9448,7 +9607,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -9493,7 +9652,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" }, "get-value": { @@ -9517,7 +9676,7 @@ }, "gettext-parser": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/gettext-parser/-/gettext-parser-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-1.4.0.tgz", "integrity": "sha512-sedZYLHlHeBop/gZ1jdg59hlUEcpcZJofLq2JFwJT1zTqAU3l2wFv6IsuwFHGqbiT9DWzMUW4/em2+hspnmMMA==", "requires": { "encoding": "^0.1.12", @@ -9581,7 +9740,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -9690,7 +9849,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -9896,7 +10055,7 @@ "dependencies": { "minimist": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz", "integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=", "dev": true } @@ -9912,7 +10071,7 @@ }, "got": { "version": "6.7.1", - "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "dev": true, "requires": { @@ -10003,7 +10162,7 @@ }, "grunt-cli": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", "dev": true, "requires": { @@ -10025,7 +10184,7 @@ }, "resolve": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true } @@ -10049,7 +10208,7 @@ }, "chalk": { "version": "0.2.1", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-0.2.1.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.2.1.tgz", "integrity": "sha1-dhPhV1FFshOGSD9/SFql/6jL0Qw=", "dev": true, "requires": { @@ -10075,6 +10234,14 @@ "grunt-legacy-log-utils": "~2.0.0", "hooker": "~0.2.3", "lodash": "~4.17.5" + }, + "dependencies": { + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + } } }, "grunt-legacy-log-utils": { @@ -10104,7 +10271,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true } @@ -10309,11 +10476,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "hoist-non-react-statics": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", - "integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs=" - }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -10390,7 +10552,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -10482,16 +10644,16 @@ } }, "husky": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-1.2.1.tgz", - "integrity": "sha512-4Ylal3HWhnDvIszuiyLoVrSGI7QLg/ogkNCoHE34c+yZYzb9kBZNrlTOsdw92cGi3cJT8pPb6CdVfxFkLnc8Dg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/husky/-/husky-1.3.1.tgz", + "integrity": "sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg==", "dev": true, "requires": { "cosmiconfig": "^5.0.7", "execa": "^1.0.0", "find-up": "^3.0.0", "get-stdin": "^6.0.0", - "is-ci": "^1.2.1", + "is-ci": "^2.0.0", "pkg-dir": "^3.0.0", "please-upgrade-node": "^3.1.1", "read-pkg": "^4.0.1", @@ -10499,6 +10661,12 @@ "slash": "^2.0.0" }, "dependencies": { + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -10551,6 +10719,15 @@ "pump": "^3.0.0" } }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -10610,6 +10787,16 @@ "find-up": "^3.0.0" } }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "read-pkg": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", @@ -10871,9 +11058,9 @@ } }, "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", "dev": true }, "invariant": { @@ -10902,7 +11089,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "requires": { "kind-of": "^3.0.2" @@ -10956,7 +11143,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "requires": { "builtin-modules": "^1.0.0" @@ -10977,7 +11164,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "requires": { "kind-of": "^3.0.2" @@ -11055,7 +11242,7 @@ }, "is-generator-fn": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=" }, "is-glob": { @@ -11114,7 +11301,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, @@ -11407,11 +11594,6 @@ "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==", "dev": true }, - "jed": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/jed/-/jed-1.1.1.tgz", - "integrity": "sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ=" - }, "jest": { "version": "23.6.0", "resolved": "https://registry.npmjs.org/jest/-/jest-23.6.0.tgz", @@ -11678,7 +11860,7 @@ }, "jest-environment-jsdom": { "version": "22.4.3", - "resolved": "http://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz", "integrity": "sha512-FviwfR+VyT3Datf13+ULjIMO5CSeajlayhhYQwpzgunswoaLIPutdbrnfUHEMyJCwvqQFaVtTmn9+Y8WCt6n1w==", "requires": { "jest-mock": "^22.4.3", @@ -11746,7 +11928,7 @@ }, "jest-get-type": { "version": "22.4.3", - "resolved": "http://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==" }, "jest-haste-map": { @@ -11837,7 +12019,7 @@ }, "jest-message-util": { "version": "22.4.3", - "resolved": "http://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.3.tgz", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.3.tgz", "integrity": "sha512-iAMeKxhB3Se5xkSjU0NndLLCHtP4n+GtCqV0bISKA5dmOXQfEbdEmYiu2qpnWBDCQdEafNDDU6Q+l6oBMd/+BA==", "requires": { "@babel/code-frame": "^7.0.0-beta.35", @@ -11849,7 +12031,7 @@ }, "jest-mock": { "version": "22.4.3", - "resolved": "http://registry.npmjs.org/jest-mock/-/jest-mock-22.4.3.tgz", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-22.4.3.tgz", "integrity": "sha512-+4R6mH5M1G4NK16CKg9N1DtCaFmuxhcIqF4lQK/Q1CIotqMs/XBemfpDPeVZBFow6iyUNu6EBT9ugdNOTT5o5Q==" }, "jest-regex-util": { @@ -12085,7 +12267,7 @@ }, "jest-util": { "version": "22.4.3", - "resolved": "http://registry.npmjs.org/jest-util/-/jest-util-22.4.3.tgz", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-22.4.3.tgz", "integrity": "sha512-rfDfG8wyC5pDPNdcnAlZgwKnzHvZDu8Td2NJI/jAGKEGxJPYiE4F0ss/gSAkG4778Y23Hvbz+0GMrDJTeo7RjQ==", "requires": { "callsites": "^2.0.0", @@ -12242,12 +12424,12 @@ }, "json5": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" }, "jsonfile": { "version": "2.4.0", - "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { @@ -12322,26 +12504,26 @@ "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" }, "lerna": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.8.0.tgz", - "integrity": "sha512-OLdf7JSWjpgVecvVLyTRpeKPjTJOcQa366IvaEhorOIxFPZvR1rNIEvi4DMOAaxNINpmCB4nSm769H7H4jNQyw==", + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.8.4.tgz", + "integrity": "sha512-bmFDfYiHhgaDndFT9o0HcQ7jNHi5OVF47Y8hWZWtVOpQuVDAYiAKXwtFCNoqgzIeZxsdGXI/VoW9aZ/IPHErMw==", "dev": true, "requires": { - "@lerna/add": "^3.7.2", - "@lerna/bootstrap": "^3.7.2", - "@lerna/changed": "^3.8.0", - "@lerna/clean": "^3.7.2", + "@lerna/add": "^3.8.2", + "@lerna/bootstrap": "^3.8.2", + "@lerna/changed": "^3.8.2", + "@lerna/clean": "^3.8.1", "@lerna/cli": "^3.6.0", - "@lerna/create": "^3.7.2", - "@lerna/diff": "^3.7.2", - "@lerna/exec": "^3.7.2", - "@lerna/import": "^3.7.2", - "@lerna/init": "^3.7.2", - "@lerna/link": "^3.7.2", - "@lerna/list": "^3.7.2", - "@lerna/publish": "^3.8.0", - "@lerna/run": "^3.7.2", - "@lerna/version": "^3.8.0", + "@lerna/create": "^3.8.1", + "@lerna/diff": "^3.8.1", + "@lerna/exec": "^3.8.1", + "@lerna/import": "^3.8.1", + "@lerna/init": "^3.8.1", + "@lerna/link": "^3.8.1", + "@lerna/list": "^3.8.1", + "@lerna/publish": "^3.8.4", + "@lerna/run": "^3.8.1", + "@lerna/version": "^3.8.2", "import-local": "^1.0.0", "libnpm": "^2.0.1" } @@ -12414,6 +12596,16 @@ "requires": { "pump": "^3.0.0" } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } } } }, @@ -12499,6 +12691,16 @@ "requires": { "pump": "^3.0.0" } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } } } }, @@ -12528,6 +12730,16 @@ "requires": { "pump": "^3.0.0" } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } } } }, @@ -12563,6 +12775,16 @@ "pump": "^3.0.0" } }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", @@ -12593,6 +12815,16 @@ "requires": { "pump": "^3.0.0" } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } } } }, @@ -12622,6 +12854,16 @@ "requires": { "pump": "^3.0.0" } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } } } }, @@ -12677,7 +12919,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { "graceful-fs": "^4.1.2", @@ -12745,31 +12987,14 @@ "dev": true }, "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^5.2.2", + "big.js": "^3.1.3", "emojis-list": "^2.0.0", - "json5": "^1.0.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } + "json5": "^0.5.0" } }, "locate-path": { @@ -13068,6 +13293,16 @@ "through2": "^2.0.0" } }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", @@ -13139,14 +13374,9 @@ "dev": true }, "marked": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.5.2.tgz", - "integrity": "sha512-fdZvBa7/vSQIZCi4uuwo2N3q+7jJURpMVCcbaX0S1Mg65WZ5ilXvC67MviJAsdjqqgD+CEq4RKo5AYGgINkVAA==" - }, - "material-colors": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", - "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.0.tgz", + "integrity": "sha512-HduzIW2xApSXKXJSpCipSxKyvMbwRRa/TwMbepmlZziKdH8548WSoDP4SxzulEKjlo8BE39l+2fwJZuRKOln6g==" }, "math-random": { "version": "1.0.1", @@ -13181,7 +13411,7 @@ }, "medium-zoom": { "version": "0.4.0", - "resolved": "http://registry.npmjs.org/medium-zoom/-/medium-zoom-0.4.0.tgz", + "resolved": "https://registry.npmjs.org/medium-zoom/-/medium-zoom-0.4.0.tgz", "integrity": "sha512-0z7yMfd6I1BTCAa8QaR4cp5AqDkQD571GzhHIbbfefKEssGLSvs+4Xai/itOAncm4FBlF5gUoMQ22yW9/f8Sig==", "dev": true }, @@ -13210,7 +13440,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -13225,7 +13455,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -13332,7 +13562,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -13346,7 +13576,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -13436,7 +13666,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minimist-options": { @@ -13491,18 +13721,6 @@ "pumpify": "^1.3.3", "stream-each": "^1.1.0", "through2": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } } }, "mixin-deep": { @@ -13544,7 +13762,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -13600,7 +13818,7 @@ }, "multimatch": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", "dev": true, "requires": { @@ -13612,13 +13830,13 @@ }, "mute-stream": { "version": "0.0.7", - "resolved": "http://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" }, "nan": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", - "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==" + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.0.tgz", + "integrity": "sha512-zT5nC0JhbljmyEf+Z456nvm7iO7XgRV2hYxoBtPpnyp+0Q4aCoP6uWNn76v/I6k2kCYNLWqWbwBWQcjsNI/bjw==" }, "nanomatch": { "version": "1.2.13", @@ -13661,12 +13879,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" }, "nearley": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.16.0.tgz", - "integrity": "sha512-Tr9XD3Vt/EujXbZBv6UAHYoLUSMQAxSsTnm9K3koXzjzNWY195NqALeyrzLZBKzAkL3gl92BcSogqrHjD8QuUg==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.15.1.tgz", + "integrity": "sha512-8IUY/rUrKz2mIynUGh8k+tul1awMKEjeHHC5G3FHvvyAW6oq4mQfNp2c0BMea+sYZJvYcrrM6GmZVIle/GRXGw==", "requires": { - "commander": "^2.19.0", "moo": "^0.4.3", + "nomnom": "~1.6.2", "railroad-diagrams": "^1.0.0", "randexp": "0.4.6", "semver": "^5.4.1" @@ -13747,7 +13965,7 @@ "dependencies": { "semver": { "version": "5.3.0", - "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "dev": true } @@ -13797,7 +14015,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -13812,7 +14030,7 @@ "dependencies": { "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -13835,9 +14053,9 @@ } }, "node-releases": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.3.tgz", - "integrity": "sha512-6VrvH7z6jqqNFY200kdB6HdzkgM96Oaj9v3dqGfgp6mF+cHmU4wyQKZ2/WPDRVoR0Jz9KqbamaBN0ZhdUaysUQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.1.tgz", + "integrity": "sha512-2UXrBr6gvaebo5TNF84C66qyJJ6r0kxBObgZIDX3D3/mt1ADKiHux3NJPWisq0wxvJJdkjECH+9IIKYViKj71Q==", "requires": { "semver": "^5.3.0" } @@ -13883,7 +14101,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { @@ -13893,7 +14111,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -13931,7 +14149,7 @@ }, "meow": { "version": "3.7.0", - "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { @@ -13949,7 +14167,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -13974,7 +14192,7 @@ }, "supports-color": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, @@ -14014,12 +14232,21 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } } }, + "nomnom": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz", + "integrity": "sha1-hKZqJgF0QI/Ft3oY+IjszET7aXE=", + "requires": { + "colors": "0.5.x", + "underscore": "~1.4.4" + } + }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -14402,7 +14629,7 @@ "dependencies": { "ansi-escapes": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", "dev": true }, @@ -14414,7 +14641,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -14448,13 +14675,13 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, "node-fetch": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz", "integrity": "sha1-3CNO3WSJmC1Y6PDbT2lQKavNjAQ=", "dev": true, "requires": { @@ -14464,7 +14691,7 @@ }, "opn": { "version": "4.0.2", - "resolved": "http://registry.npmjs.org/opn/-/opn-4.0.2.tgz", + "resolved": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz", "integrity": "sha1-erwi5kTf9jsKltWrfyeQwPAavJU=", "dev": true, "requires": { @@ -14474,7 +14701,7 @@ }, "supports-color": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true } @@ -14538,7 +14765,7 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { @@ -14553,7 +14780,7 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { @@ -14590,7 +14817,7 @@ }, "p-is-promise": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", "dev": true }, @@ -14756,6 +14983,16 @@ "through2": "^2.0.0" } }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", @@ -14813,7 +15050,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -14828,7 +15065,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -14845,7 +15082,7 @@ }, "parse-asn1": { "version": "5.1.1", - "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "dev": true, "requires": { @@ -14922,7 +15159,7 @@ }, "path-browserify": { "version": "0.0.0", - "resolved": "http://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", "dev": true }, @@ -14939,7 +15176,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { @@ -15017,7 +15254,7 @@ }, "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" }, "pinkie": { @@ -15068,11 +15305,6 @@ "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" }, - "popper.js": { - "version": "1.14.6", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz", - "integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA==" - }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -15145,7 +15377,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -15158,7 +15390,7 @@ "dependencies": { "supports-color": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true } @@ -15397,15 +15629,15 @@ } }, "postcss-reporter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz", - "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.0.tgz", + "integrity": "sha512-5xQXm1UPWuFObjbtyQzWvQaupru8yFcFi4HUlm6OPo1o2bUszYASuqRJ7bVArb3svGCdbYtqdMBKrqR1Aoy+tw==", "dev": true, "requires": { - "chalk": "^2.4.1", - "lodash": "^4.17.11", - "log-symbols": "^2.2.0", - "postcss": "^7.0.7" + "chalk": "^2.0.1", + "lodash": "^4.17.4", + "log-symbols": "^2.0.0", + "postcss": "^7.0.2" }, "dependencies": { "postcss": { @@ -15649,7 +15881,7 @@ }, "globby": { "version": "6.1.0", - "resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { @@ -15707,7 +15939,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -15905,9 +16137,9 @@ } }, "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -15923,18 +16155,6 @@ "duplexify": "^3.6.0", "inherits": "^2.0.3", "pump": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } } }, "punycode": { @@ -16068,21 +16288,26 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } } }, + "re-resizable": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-4.11.0.tgz", + "integrity": "sha512-dye+7rERqNf/6mDT1iwps+4Gf42420xuZgygF33uX178DxffqcyeuHbBuJ382FIcB5iP6mMZOhfW7kI0uXwb/Q==" + }, "react": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.7.0.tgz", - "integrity": "sha512-StCz3QY8lxTb5cl2HJxjwLFOXPIFQp+p+hxQfc8WE0QiLfCtIlKj8/+5tjjKm8uSTlAW+fCPaavGFS06V9Ar3A==", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz", + "integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.12.0" + "scheduler": "^0.11.2" } }, "react-addons-create-fragment": { @@ -16119,30 +16344,6 @@ } } }, - "react-color": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.17.0.tgz", - "integrity": "sha512-kJfE5tSaFe6GzalXOHksVjqwCPAsTl+nzS9/BWfP7j3EXbQ4IiLAF9sZGNzk3uq7HfofGYgjmcUgh0JP7xAQ0w==", - "requires": { - "@icons/material": "^0.2.4", - "lodash": ">4.17.4", - "material-colors": "^1.2.1", - "prop-types": "^15.5.10", - "reactcss": "^1.2.0", - "tinycolor2": "^1.4.1" - } - }, - "react-datepicker": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-1.8.0.tgz", - "integrity": "sha512-N4LdVTtqJCsZyKXBQ/AqSEcH6FyhgsY1gD07zECNu60nGt5s4ngRlhYdHoE34VNFO+ZY+pvljZRLwSC8LS9RxQ==", - "requires": { - "classnames": "^2.2.5", - "prop-types": "^15.6.0", - "react-onclickoutside": "^6.7.1", - "react-popper": "^1.0.2" - } - }, "react-dates": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dates/-/react-dates-18.3.1.tgz", @@ -16187,7 +16388,7 @@ }, "babylon": { "version": "5.8.38", - "resolved": "http://registry.npmjs.org/babylon/-/babylon-5.8.38.tgz", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-5.8.38.tgz", "integrity": "sha1-7JsSCxG/bM1Bc6GL8hfmC3mFn/0=", "dev": true }, @@ -16219,20 +16420,20 @@ } }, "react-dom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.7.0.tgz", - "integrity": "sha512-D0Ufv1ExCAmF38P2Uh1lwpminZFRXEINJe53zRAbm4KPwSyd6DY/uDoS0Blj9jvPpn1+wivKpZYc8aAAN/nAkg==", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", + "integrity": "sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.12.0" + "scheduler": "^0.11.2" } }, "react-is": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.7.0.tgz", - "integrity": "sha512-Z0VRQdF4NPDoI0tsXVMLkJLiwEBa+RP66g0xDHxgxysxSoCUccSten4RTF/UFvZF1dZvZ9Zu1sx+MDXwcOR34g==" + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.3.tgz", + "integrity": "sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA==" }, "react-lifecycles-compat": { "version": "3.0.4", @@ -16280,11 +16481,6 @@ "moment": ">=1.6.0" } }, - "react-onclickoutside": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.7.1.tgz", - "integrity": "sha512-p84kBqGaMoa7VYT0vZ/aOYRfJB+gw34yjpda1Z5KeLflg70HipZOT+MXQenEhdkPAABuE2Astq4zEPdMqUQxcg==" - }, "react-outside-click-handler": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/react-outside-click-handler/-/react-outside-click-handler-1.2.2.tgz", @@ -16296,19 +16492,6 @@ "prop-types": "^15.6.1" } }, - "react-popper": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.2.tgz", - "integrity": "sha512-UbFWj55Yt9uqvy0oZ+vULDL2Bw1oxeZF9/JzGyxQ5ypgauRH/XlarA5+HLZWro/Zss6Ht2kqpegtb6sYL8GUGw==", - "requires": { - "@babel/runtime": "^7.1.2", - "create-react-context": "<=0.2.2", - "popper.js": "^1.14.4", - "prop-types": "^15.6.1", - "typed-styles": "^0.0.7", - "warning": "^4.0.2" - } - }, "react-portal": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/react-portal/-/react-portal-4.2.0.tgz", @@ -16357,20 +16540,20 @@ "integrity": "sha512-jZzE+vbYAblPXSPFlir+aL5ljxwB0dJ62O2pR74OS/TUVDTW95msrdszJKLNp4lxzNcoHnCRIzLT6crBgTolGg==" }, "react-test-renderer": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.7.0.tgz", - "integrity": "sha512-tFbhSjknSQ6+ttzmuGdv+SjQfmvGcq3PFKyPItohwhhOBmRoTf1We3Mlt3rJtIn85mjPXOkKV+TaKK4irvk9Yg==", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.6.3.tgz", + "integrity": "sha512-B5bCer+qymrQz/wN03lT0LppbZUDRq6AMfzMKrovzkGzfO81a9T+PWQW6MzkWknbwODQH/qpJno/yFQLX5IWrQ==", "requires": { "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "react-is": "^16.7.0", - "scheduler": "^0.12.0" + "react-is": "^16.6.3", + "scheduler": "^0.11.2" } }, "react-transition-group": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.2.tgz", - "integrity": "sha512-vwHP++S+f6KL7rg8V1mfs62+MBKtbMeZDR8KiNmD7v98Gs3UPGsDZDahPJH2PVprFW5YHJfh6cbNim3zPndaSQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.1.tgz", + "integrity": "sha512-8x/CxUL9SjYFmUdzsBPTgtKeCxt7QArjNSte0wwiLtF/Ix/o1nWNJooNy5o9XbHIKS31pz7J5VF2l41TwlvbHQ==", "requires": { "dom-helpers": "^3.3.1", "loose-envify": "^1.4.0", @@ -16436,14 +16619,6 @@ "world-countries": "^2.0.0" } }, - "reactcss": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", - "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", - "requires": { - "lodash": "^4.0.1" - } - }, "read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -16527,9 +16702,9 @@ } }, "readable-stream": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", - "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.0.tgz", + "integrity": "sha512-vpydAvIJvPODZNagCPuHG87O9JNPtvFEtjHHRVwNVsVVRBqemvPJkc2SYbxJsiZXawJdtZNmkmnsPuE3IgsG0A==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -16644,7 +16819,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -16664,7 +16839,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -16850,7 +17025,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -16865,7 +17040,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -16988,7 +17163,7 @@ }, "regexpp": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==" }, "regexpu-core": { @@ -17038,7 +17213,7 @@ "dependencies": { "jsesc": { "version": "0.5.0", - "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" } } @@ -17199,13 +17374,13 @@ } }, "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "dev": true, "requires": { "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", + "get-stream": "^3.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -17222,15 +17397,6 @@ "locate-path": "^3.0.0" } }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "invert-kv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", @@ -17268,20 +17434,20 @@ } }, "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", + "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", "dev": true, "requires": { - "execa": "^1.0.0", + "execa": "^0.10.0", "lcid": "^2.0.0", "mem": "^4.0.0" } }, "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -17424,7 +17590,7 @@ }, "require-uncached": { "version": "1.0.3", - "resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "requires": { "caller-path": "^0.1.0", @@ -17627,7 +17793,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "requires": { "ret": "~0.1.10" @@ -17731,7 +17897,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "requires": { "kind-of": "^3.0.2" @@ -17749,7 +17915,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "requires": { "kind-of": "^3.0.2" @@ -17912,7 +18078,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "ms": { @@ -17962,7 +18128,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -17971,7 +18137,7 @@ }, "string-width": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { @@ -18046,9 +18212,9 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "scheduler": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.12.0.tgz", - "integrity": "sha512-t7MBR28Akcp4Jm+QoR63XgAi9YgCUmgvDHqf5otgAj4QvdoBE4ImCX0ffehefePPG+aitiYHp0g/mW6s4Tp+dw==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.3.tgz", + "integrity": "sha512-i9X9VRRVZDd3xZw10NY5Z2cVMbdYg6gqFecfj79USv1CFN+YrJ3gIPRKf1qlY+Sxly4djoKdfx1T+m9dnRB8kQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -18085,7 +18251,7 @@ "dependencies": { "source-map": { "version": "0.4.4", - "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { @@ -18164,9 +18330,9 @@ } }, "serialize-javascript": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.6.1.tgz", - "integrity": "sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", + "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==", "dev": true }, "serve-static": { @@ -18220,7 +18386,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -18516,9 +18682,9 @@ } }, "spdx-license-ids": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", - "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz", + "integrity": "sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg==" }, "specificity": { "version": "0.4.1", @@ -18558,9 +18724,9 @@ "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" }, "sshpk": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.0.tgz", - "integrity": "sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -18629,7 +18795,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -18644,7 +18810,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -18660,7 +18826,7 @@ }, "stream-browserify": { "version": "2.0.1", - "resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "dev": true, "requires": { @@ -18670,7 +18836,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -18685,7 +18851,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -18719,7 +18885,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -18734,7 +18900,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -18825,7 +18991,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -18848,7 +19014,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-indent": { @@ -18874,7 +19040,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -19037,7 +19203,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -19057,7 +19223,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -19161,7 +19327,7 @@ }, "globby": { "version": "8.0.1", - "resolved": "http://registry.npmjs.org/globby/-/globby-8.0.1.tgz", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", "dev": true, "requires": { @@ -19410,31 +19576,31 @@ } }, "stylelint-scss": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.4.4.tgz", - "integrity": "sha512-GquwsRegF2gsVRePaUN93cYf9aJDygr03X/QRiwk9O5lOe7QZHlM4Vzrb8JAu+pZ0xodPRpN6W259yA6ApM3WA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.4.1.tgz", + "integrity": "sha512-ENYTE25wd9ndSkwxMksr8hrXVdWYu+RfDKM1ef2MHEqxk4cU362WQv7mgJK3HZqdCZFxL21sFegQO9Kz2vzwGw==", "dev": true, "requires": { "lodash": "^4.17.11", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^5.0.0", + "postcss-selector-parser": "^4.0.0", "postcss-value-parser": "^3.3.1" }, "dependencies": { "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-1.0.1.tgz", + "integrity": "sha512-S2hzrpWvE6G/rW7i7IxJfWBYn27QWfOIncUW++8Rbo1VB5zsJDSVPcnI+Q8z7rhxT6/yZeLOCja4cZnghJrNGA==", "dev": true }, "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-4.0.0.tgz", + "integrity": "sha512-5h+MvEjnzu1qy6MabjuoPatsGAjjDV9B24e7Cktjl+ClNtjVjmvAXjOFQr1u7RlWULKNGYaYVE4s+DIIQ4bOGA==", "dev": true, "requires": { - "cssesc": "^2.0.0", + "cssesc": "^1.0.1", "indexes-of": "^1.0.1", "uniq": "^1.0.1" } @@ -19524,7 +19690,7 @@ }, "fast-deep-equal": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" }, "json-schema-traverse": { @@ -19550,7 +19716,7 @@ }, "tar": { "version": "2.2.1", - "resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "dev": true, "requires": { @@ -19597,9 +19763,9 @@ } }, "terser": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-3.13.1.tgz", - "integrity": "sha512-ogyZye4DFqOtMzT92Y3Nxxw8OvXmL39HOALro4fc+EUYFFF9G/kk0znkvwMz6PPYgBtdKAodh3FPR70eugdaQA==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.14.0.tgz", + "integrity": "sha512-KQC1QNKbC/K1ZUjLIWsezW7wkTJuB4v9ptQQUNOzAPVHuVf2LrwEcB0I9t2HTEYUwAFVGiiS6wc+P4ClLDc5FQ==", "dev": true, "requires": { "commander": "~2.17.1", @@ -19607,12 +19773,6 @@ "source-map-support": "~0.5.6" }, "dependencies": { - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -19759,6 +19919,16 @@ "find-up": "^3.0.0" } }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -19818,7 +19988,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { @@ -19833,7 +20003,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -19848,7 +20018,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -20024,7 +20194,7 @@ }, "tty-browserify": { "version": "0.0.0", - "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, @@ -20060,11 +20230,6 @@ "prelude-ls": "~1.1.2" } }, - "typed-styles": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", - "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" - }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -20109,12 +20274,6 @@ "source-map": "~0.6.1" }, "dependencies": { - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "optional": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -20141,6 +20300,11 @@ "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=", "dev": true }, + "underscore": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", + "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" + }, "underscore.string": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", @@ -20511,9 +20675,9 @@ } }, "validator": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.10.0.tgz", - "integrity": "sha512-DyZyLJlMXM3CGdVaVHE/EDzCagMRoPI3mmGdxxNQbqkGqh56+M3d1i0ZAWd69En8U21DHbPTn12aOdhO+hfm5w==" + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.9.0.tgz", + "integrity": "sha512-hZJcZSWz9poXBlAkjjcsNAdrZ6JbjD3kWlNjq/+vE7RLLS/+8PAj3qVVwrwsOz/WL8jPmZ1hYkRvtlUeZAm4ug==" }, "value-equal": { "version": "0.4.0", @@ -20564,7 +20728,7 @@ }, "vm-browserify": { "version": "0.0.4", - "resolved": "http://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", "dev": true, "requires": { @@ -20606,7 +20770,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } @@ -20637,9 +20801,9 @@ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" }, "webpack": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.28.2.tgz", - "integrity": "sha512-PK3uVg3/NuNVOjPfYleFI6JF7khO7c2kIlksH7mivQm+QDcwiqV1x6+q89dDeOioh5FNxJHr3LKbDu3oSAhl9g==", + "version": "4.28.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.28.3.tgz", + "integrity": "sha512-vLZN9k5I7Nr/XB1IDG9GbZB4yQd1sPuvufMFgJkx0b31fi2LD97KQIjwjxE7xytdruAYfu5S0FLBLjdxmwGJCg==", "dev": true, "requires": { "@webassemblyjs/ast": "1.7.11", @@ -20769,7 +20933,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -20789,7 +20953,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -21023,13 +21187,13 @@ } }, "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "dev": true, "requires": { "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", + "get-stream": "^3.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -21046,15 +21210,6 @@ "locate-path": "^3.0.0" } }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -21102,20 +21257,20 @@ } }, "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", + "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", "dev": true, "requires": { - "execa": "^1.0.0", + "execa": "^0.10.0", "lcid": "^2.0.0", "mem": "^4.0.0" } }, "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -21269,13 +21424,13 @@ } }, "world-countries": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/world-countries/-/world-countries-2.1.0.tgz", - "integrity": "sha512-g8DRgoH7UJiF5L0xjj+RP/GFH4fOVlD3J5CxkJ+PZYH1PNl0i5JrstBOX53Ub8ObTZ6lCx0V7nIA8BuCvxMoSg==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/world-countries/-/world-countries-2.0.0.tgz", + "integrity": "sha512-f/Atl3VHj/FxFEw5jVkTkKofbbD8z/WF/PCpmv3JPrUR2X/XtuApulLy8QwKcyrurlDDRO0lGPvX+m0GzjxaNQ==" }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { "string-width": "^1.0.1", @@ -21292,7 +21447,7 @@ }, "string-width": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", @@ -21429,7 +21584,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -21442,7 +21597,7 @@ }, "supports-color": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true } @@ -21450,7 +21605,7 @@ }, "yargs": { "version": "11.1.0", - "resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "requires": { "cliui": "^4.0.0", diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 4d708d6ec98..c7d60efbe68 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -1,6 +1,6 @@ { "name": "wc-admin", - "version": "0.3.0", + "version": "0.4.0", "main": "js/index.js", "author": "Automattic", "license": "GPL-2.0-or-later", @@ -39,6 +39,7 @@ "test": "wp-scripts test-unit-js --config tests/js/jest.config.json", "test:watch": "npm run test -- --watch", "test:update-snapshots": "jest --updateSnapshot --config tests/js/jest.config.json", + "jest:update": "jest -u --config=tests/js/jest.config.json", "docs": "node ./bin/generate-docs", "publish:check": "npm run build:packages && lerna updated", "publish:dev": "npm run build:packages && lerna publish from-package --npm-tag next", @@ -52,10 +53,10 @@ "@babel/runtime-corejs2": "7.2.0", "@wordpress/babel-plugin-import-jsx-pragma": "1.1.2", "@wordpress/babel-plugin-makepot": "2.1.2", - "@wordpress/babel-preset-default": "2.1.0", + "@wordpress/babel-preset-default": "3.0.1", "@wordpress/browserslist-config": "2.2.2", "@wordpress/custom-templated-path-webpack-plugin": "1.1.5", - "@wordpress/jest-preset-default": "2.0.6", + "@wordpress/jest-preset-default": "3.0.3", "@wordpress/postcss-themes": "1.0.4", "ast-types": "0.11.7", "autoprefixer": "9.4.3", @@ -83,8 +84,8 @@ "grunt": "1.0.3", "grunt-checktextdomain": "1.0.1", "grunt-wp-i18n": "1.0.3", - "husky": "1.2.1", - "lerna": "3.8.0", + "husky": "1.3.1", + "lerna": "3.8.4", "node-sass": "4.11.0", "postcss-color-function": "4.0.1", "postcss-loader": "3.0.0", @@ -101,21 +102,21 @@ "style-loader": "0.23.1", "stylelint": "9.9.0", "stylelint-config-wordpress": "13.1.0", - "webpack": "4.28.2", + "webpack": "4.28.3", "webpack-cli": "3.1.2" }, "dependencies": { "@fresh-data/framework": "^0.5.1", - "@wordpress/api-fetch": "2.2.2", - "@wordpress/components": "3.0.0", - "@wordpress/data": "3.1.0", - "@wordpress/date": "2.1.0", - "@wordpress/element": "2.1.5", + "@wordpress/api-fetch": "2.2.6", + "@wordpress/components": "7.0.5", + "@wordpress/data": "4.2.0", + "@wordpress/date": "3.0.1", + "@wordpress/element": "2.1.8", "@wordpress/hooks": "2.0.3", - "@wordpress/html-entities": "2.0.2", - "@wordpress/i18n": "2.0.0", - "@wordpress/keycodes": "2.0.3", - "@wordpress/scripts": "2.4.1", + "@wordpress/html-entities": "2.0.4", + "@wordpress/i18n": "3.1.0", + "@wordpress/keycodes": "2.0.5", + "@wordpress/scripts": "2.4.4", "@wordpress/viewport": "^2.0.7", "browser-filesaver": "^1.1.1", "classnames": "^2.2.5", @@ -135,7 +136,7 @@ "html-to-react": "1.3.4", "interpolate-components": "1.1.1", "lodash": "^4.17.11", - "marked": "0.5.2", + "marked": "0.6.0", "prismjs": "^1.15.0", "qs": "^6.5.2", "react-click-outside": "3.0.1", diff --git a/plugins/woocommerce-admin/packages/components/CHANGELOG.md b/plugins/woocommerce-admin/packages/components/CHANGELOG.md index 5774db71a64..5e51ab89fff 100644 --- a/plugins/woocommerce-admin/packages/components/CHANGELOG.md +++ b/plugins/woocommerce-admin/packages/components/CHANGELOG.md @@ -3,7 +3,11 @@ - Add order number autocompleter to search component - Add order number, username, and IP address filters to the downloads report. - Added `interactive` prop for `d3chart/legend` to signal if legend items are clickable or not. -- Fix for undefined ref in `d3chart/legend` +- Fix for undefined ref in `d3chart/legend`. +- Added three news props to ``: + - `interactiveLegend`: whether legend items are clickable or not. Defaults to true. + - `legendPosition`: can be `top`, `side` or `bottom`. If not specified, it's calculated based on `mode` and viewport width. + - `showHeaderControls`: whether the header controls must be visible. Defaults to true. # 1.3.0 diff --git a/plugins/woocommerce-admin/packages/components/package.json b/plugins/woocommerce-admin/packages/components/package.json index 4f1a39ac57c..61256041b45 100644 --- a/plugins/woocommerce-admin/packages/components/package.json +++ b/plugins/woocommerce-admin/packages/components/package.json @@ -26,13 +26,13 @@ "@woocommerce/currency": "^1.0.0", "@woocommerce/date": "^1.0.3", "@woocommerce/navigation": "^1.1.0", - "@wordpress/components": "3.0.0", + "@wordpress/components": "7.0.5", "@wordpress/compose": "3.0.0", - "@wordpress/date": "2.1.0", - "@wordpress/element": "2.1.5", - "@wordpress/html-entities": "2.0.2", - "@wordpress/i18n": "2.0.0", - "@wordpress/keycodes": "2.0.2", + "@wordpress/date": "3.0.1", + "@wordpress/element": "2.1.8", + "@wordpress/html-entities": "2.0.4", + "@wordpress/i18n": "3.1.0", + "@wordpress/keycodes": "2.0.5", "@wordpress/viewport": "^2.0.7", "classnames": "^2.2.5", "core-js": "2.6.1", diff --git a/plugins/woocommerce-admin/packages/components/src/calendar/date-range.js b/plugins/woocommerce-admin/packages/components/src/calendar/date-range.js index 9d8deb3b7e4..271a1c9039f 100644 --- a/plugins/woocommerce-admin/packages/components/src/calendar/date-range.js +++ b/plugins/woocommerce-admin/packages/components/src/calendar/date-range.js @@ -118,6 +118,7 @@ class DateRange extends Component { ), shortDateFormat ) } + onFocus={ () => this.onFocusChange( 'startDate' ) } />
{ __( 'to', 'wc-admin' ) }
this.onFocusChange( 'endDate' ) } />
@@ -150,7 +152,6 @@ class DateRange extends Component { noBorder initialVisibleMonth={ this.setTnitialVisibleMonth( isDoubleCalendar, before ) } phrases={ phrases } - firstDayOfWeek={ Number( wcSettings.date.dow ) } />
diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/chart.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/chart.js index b7144a65a43..0903fad61a8 100644 --- a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/chart.js +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/chart.js @@ -8,31 +8,31 @@ import { Component, createRef } from '@wordpress/element'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { timeFormat as d3TimeFormat, utcParse as d3UTCParse } from 'd3-time-format'; -import { select as d3Select } from 'd3-selection'; /** * Internal dependencies */ import D3Base from './d3base'; import { - drawAxis, - drawBars, - drawLines, getDateSpaces, getOrderedKeys, getLine, getLineData, - getXTicks, getUniqueKeys, getUniqueDates, + getFormatter, +} from './utils'; +import { getXScale, getXGroupScale, getXLineScale, getYMax, getYScale, getYTickOffset, - getFormatter, -} from './utils'; +} from './utils/scales'; +import { drawAxis, getXTicks } from './utils/axis'; +import { drawBars } from './utils/bar-chart'; +import { drawLines } from './utils/line-chart'; /** * A simple D3 line and bar chart component for timeseries data in React. @@ -70,23 +70,48 @@ class D3Chart extends Component { } drawChart( node ) { - const { data, margin, type } = this.props; - const params = this.getParams(); - const adjParams = Object.assign( {}, params, { - height: params.adjHeight, - width: params.adjWidth, - tooltip: d3Select( this.tooltipRef.current ), - valueType: params.valueType, + setTimeout( () => { + const { data, margin, type } = this.props; + const params = this.getParams(); + const adjParams = Object.assign( {}, params, { + height: params.adjHeight, + width: params.adjWidth, + tooltip: this.tooltipRef.current, + valueType: params.valueType, + } ); + + const g = node + .attr( 'id', 'chart' ) + .append( 'g' ) + .attr( 'transform', `translate(${ margin.left },${ margin.top })` ); + + drawAxis( g, adjParams ); + type === 'line' && drawLines( g, data, adjParams ); + type === 'bar' && drawBars( g, data, adjParams ); } ); + } - const g = node - .attr( 'id', 'chart' ) - .append( 'g' ) - .attr( 'transform', `translate(${ margin.left },${ margin.top })` ); + shouldBeCompact() { + const { data, margin, type, width } = this.props; + if ( type !== 'bar' ) { + return false; + } + const widthWithoutMargins = width - margin.left - margin.right; + const columnsPerDate = data && data.length ? Object.keys( data[ 0 ] ).length - 1 : 0; + const minimumWideWidth = data.length * ( columnsPerDate + 1 ); - drawAxis( g, adjParams ); - type === 'line' && drawLines( g, data, adjParams ); - type === 'bar' && drawBars( g, data, adjParams ); + return widthWithoutMargins < minimumWideWidth; + } + + getWidth() { + const { data, margin, type, width } = this.props; + if ( type !== 'bar' ) { + return width; + } + const columnsPerDate = data && data.length ? Object.keys( data[ 0 ] ).length - 1 : 0; + const minimumWidth = this.shouldBeCompact() ? data.length * columnsPerDate : data.length * ( columnsPerDate + 1 ); + + return Math.max( width, minimumWidth + margin.left + margin.right ); } getParams() { @@ -104,14 +129,14 @@ class D3Chart extends Component { tooltipValueFormat, tooltipTitle, type, - width, xFormat, x2Format, yFormat, valueType, } = this.props; const adjHeight = height - margin.top - margin.bottom; - const adjWidth = width - margin.left - margin.right; + const adjWidth = this.getWidth() - margin.left - margin.right; + const compact = this.shouldBeCompact(); const uniqueKeys = getUniqueKeys( data ); const newOrderedKeys = orderedKeys || getOrderedKeys( data, uniqueKeys ); const lineData = getLineData( data, newOrderedKeys ); @@ -120,7 +145,7 @@ class D3Chart extends Component { const parseDate = d3UTCParse( dateParser ); const uniqueDates = getUniqueDates( lineData, parseDate ); const xLineScale = getXLineScale( uniqueDates, adjWidth ); - const xScale = getXScale( uniqueDates, adjWidth ); + const xScale = getXScale( uniqueDates, adjWidth, compact ); const xTicks = getXTicks( uniqueDates, adjWidth, mode, interval ); return { adjHeight, @@ -143,7 +168,7 @@ class D3Chart extends Component { uniqueKeys, xFormat: getFormatter( xFormat, d3TimeFormat ), x2Format: getFormatter( x2Format, d3TimeFormat ), - xGroupScale: getXGroupScale( orderedKeys, xScale ), + xGroupScale: getXGroupScale( orderedKeys, xScale, compact ), xLineScale, xTicks, xScale, @@ -156,21 +181,24 @@ class D3Chart extends Component { } render() { - if ( isEmpty( this.props.data ) ) { + const { className, data, height } = this.props; + if ( isEmpty( data ) ) { return null; // TODO: improve messaging } + const computedWidth = this.getWidth(); return (
diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/d3base/index.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/d3base/index.js index 9f259fdfe22..6b35c157fb0 100644 --- a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/d3base/index.js +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/d3base/index.js @@ -6,9 +6,14 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; import { Component, createRef } from '@wordpress/element'; -import { isEqual } from 'lodash'; +import { isEqual, throttle } from 'lodash'; import { select as d3Select } from 'd3-selection'; +/** + * Internal dependencies + */ +import { hideTooltip } from '../utils/tooltip'; + /** * Provides foundation to use D3 within React. * @@ -25,6 +30,10 @@ export default class D3Base extends Component { super( props ); this.chartRef = createRef(); + + this.delayedScroll = throttle( () => { + hideTooltip( this.chartRef.current, props.tooltipRef.current ); + }, 300 ); } componentDidMount() { @@ -86,7 +95,7 @@ export default class D3Base extends Component { render() { return ( -
+
); } } diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/d3base/style.scss b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/d3base/style.scss index f3aa0895bd8..3feb26e2b7e 100644 --- a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/d3base/style.scss +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/d3base/style.scss @@ -2,6 +2,8 @@ .d3-base { background: transparent; + overflow-x: auto; + overflow-y: hidden; position: relative; width: 100%; height: 100%; diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/legend.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/legend.js index 9ba9c053354..48684fa0604 100644 --- a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/legend.js +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/legend.js @@ -9,7 +9,8 @@ import PropTypes from 'prop-types'; /** * Internal dependencies */ -import { getColor, getFormatter } from './utils'; +import { getFormatter } from './utils'; +import { getColor } from './utils/color'; /** * A legend specifically designed for the WooCommerce admin charts. diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/test/fixtures/dummy-hour.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/test/fixtures/dummy-hour.js deleted file mode 100644 index b310f491f07..00000000000 --- a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/test/fixtures/dummy-hour.js +++ /dev/null @@ -1,25 +0,0 @@ -/** @format */ - -// /** -// * /* eslint-disable quote-props -// * -// * @format -// */ - -export default [ - { - date: '2018-08-01T00:00:00', - 'Custom (Aug 1, 2018)': { label: '2018-08-01 00:00', value: 58929.99 }, - 'Previous Period (Jul 31, 2018)': { label: '2018-07-31 00:00', value: 160130.74000000002 }, - }, - { - date: '2018-08-01T01:00:00', - 'Custom (Aug 1, 2018)': { label: '2018-08-01 01:00', value: 3805.56 }, - 'Previous Period (Jul 31, 2018)': { label: '2018-07-31 01:00', value: 0 }, - }, - { - date: '2018-08-01T02:00:00', - 'Custom (Aug 1, 2018)': { label: '2018-08-01 02:00', value: 3805.56 }, - 'Previous Period (Jul 31, 2018)': { label: '2018-07-31 02:00', value: 0 }, - }, -]; diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils.js index b8792ec9b65..c356968de3c 100644 --- a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils.js +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils.js @@ -144,25 +144,29 @@ export const getColor = ( key, params ) => { * Describes getXScale * @param {array} uniqueDates - from `getUniqueDates` * @param {number} width - calculated width of the charting space + * @param {boolean} compact - whether the chart must be compact (without padding + between days) * @returns {function} a D3 scale of the dates */ -export const getXScale = ( uniqueDates, width ) => +export const getXScale = ( uniqueDates, width, compact = false ) => d3ScaleBand() .domain( uniqueDates ) .rangeRound( [ 0, width ] ) - .paddingInner( 0.1 ); + .paddingInner( compact ? 0 : 0.1 ); /** * Describes getXGroupScale * @param {array} orderedKeys - from `getOrderedKeys` * @param {function} xScale - from `getXScale` + * @param {boolean} compact - whether the chart must be compact (without padding + between days) * @returns {function} a D3 scale for each category within the xScale range */ -export const getXGroupScale = ( orderedKeys, xScale ) => +export const getXGroupScale = ( orderedKeys, xScale, compact = false ) => d3ScaleBand() .domain( orderedKeys.filter( d => d.visible ).map( d => d.key ) ) .rangeRound( [ 0, xScale.bandwidth() ] ) - .padding( 0.07 ); + .padding( compact ? 0 : 0.07 ); /** * Describes getXLineScale @@ -568,27 +572,30 @@ const calculateTooltipXPosition = ( elementWidthRatio, tooltipPosition ) => { - const xPosition = - elementCoords.left + elementCoords.width * elementWidthRatio + tooltipMargin - chartCoords.left; + const d3BaseCoords = d3Select( '.d3-base' ).node().getBoundingClientRect(); + const leftMargin = Math.max( d3BaseCoords.left, chartCoords.left ); if ( tooltipPosition === 'below' ) { return Math.max( tooltipMargin, Math.min( - xPosition - tooltipSize.width / 2, - chartCoords.width - tooltipSize.width - tooltipMargin + elementCoords.left + elementCoords.width * 0.5 - tooltipSize.width / 2 - leftMargin, + d3BaseCoords.width - tooltipSize.width - tooltipMargin ) ); } - if ( xPosition + tooltipSize.width + tooltipMargin > chartCoords.width ) { + const xPosition = + elementCoords.left + elementCoords.width * elementWidthRatio + tooltipMargin - leftMargin; + + if ( xPosition + tooltipSize.width + tooltipMargin > d3BaseCoords.width ) { return Math.max( tooltipMargin, elementCoords.left + elementCoords.width * ( 1 - elementWidthRatio ) - tooltipSize.width - tooltipMargin - - chartCoords.left + leftMargin ); } diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/axis.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/axis.js new file mode 100644 index 00000000000..3062b90ac12 --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/axis.js @@ -0,0 +1,266 @@ +/** @format */ + +/** + * External dependencies + */ +import { axisBottom as d3AxisBottom, axisLeft as d3AxisLeft } from 'd3-axis'; +import { smallBreak, wideBreak } from './breakpoints'; + +const dayTicksThreshold = 63; +const weekTicksThreshold = 9; +const mediumBreak = 1130; +const smallPoints = 7; +const mediumPoints = 12; +const largePoints = 16; +const mostPoints = 31; + +/** +* Describes `smallestFactor` +* @param {number} inputNum - any double or integer +* @returns {integer} smallest factor of num +*/ +const getFactors = inputNum => { + const numFactors = []; + for ( let i = 1; i <= Math.floor( Math.sqrt( inputNum ) ); i += 1 ) { + if ( inputNum % i === 0 ) { + numFactors.push( i ); + inputNum / i !== i && numFactors.push( inputNum / i ); + } + } + numFactors.sort( ( x, y ) => x - y ); // numeric sort + + return numFactors; +}; + +/** + * Calculate the maximum number of ticks allowed in the x-axis based on the width and mode of the chart + * @param {integer} width - calculated page width + * @param {string} mode - item-comparison or time-comparison + * @returns {integer} number of x-axis ticks based on width and chart mode + */ +const calculateMaxXTicks = ( width, mode ) => { + if ( width < smallBreak ) { + return smallPoints; + } else if ( width >= smallBreak && width <= mediumBreak ) { + return mediumPoints; + } else if ( width > mediumBreak && width <= wideBreak ) { + if ( mode === 'time-comparison' ) { + return largePoints; + } else if ( mode === 'item-comparison' ) { + return mediumPoints; + } + } else if ( width > wideBreak ) { + if ( mode === 'time-comparison' ) { + return mostPoints; + } else if ( mode === 'item-comparison' ) { + return largePoints; + } + } + + return largePoints; +}; + +/** + * Get x-axis ticks given the unique dates and the increment factor. + * @param {array} uniqueDates - all the unique dates from the input data for the chart + * @param {integer} incrementFactor - increment factor for the visible ticks. + * @returns {array} Ticks for the x-axis. + */ +const getXTicksFromIncrementFactor = ( uniqueDates, incrementFactor ) => { + const ticks = []; + + for ( let idx = 0; idx < uniqueDates.length; idx = idx + incrementFactor ) { + ticks.push( uniqueDates[ idx ] ); + } + + // If the first date is missing from the ticks array, add it back in. + if ( ticks[ 0 ] !== uniqueDates[ 0 ] ) { + ticks.unshift( uniqueDates[ 0 ] ); + } + + return ticks; +}; + +/** + * Calculates the increment factor between ticks so there aren't more than maxTicks. + * @param {array} uniqueDates - all the unique dates from the input data for the chart + * @param {integer} maxTicks - maximum number of ticks that can be displayed in the x-axis + * @returns {integer} x-axis ticks increment factor + */ +const calculateXTicksIncrementFactor = ( uniqueDates, maxTicks ) => { + let factors = []; + let i = 1; + // First we get all the factors of the length of the uniqueDates array + // if the number is a prime number or near prime (with 3 factors) then we + // step down by 1 integer and try again. + while ( factors.length <= 3 ) { + factors = getFactors( uniqueDates.length - i ); + i += 1; + } + + return factors.find( f => uniqueDates.length / f < maxTicks ); +}; + +/** + * Given an array of dates, returns true if the first and last one belong to the same day. + * @param {array} dates - an array of dates + * @returns {boolean} whether the first and last date are different hours from the same date. + */ +const areDatesInTheSameDay = dates => { + const firstDate = new Date( dates [ 0 ] ); + const lastDate = new Date( dates [ dates.length - 1 ] ); + return ( + firstDate.getDate() === lastDate.getDate() && + firstDate.getMonth() === lastDate.getMonth() && + firstDate.getFullYear() === lastDate.getFullYear() + ); +}; + +/** +* Filter out irrelevant dates so only the first date of each month is kept. +* @param {array} dates - string dates. +* @returns {array} Filtered dates. +*/ +const getFirstDatePerMonth = dates => { + return dates.filter( + ( date, i ) => i === 0 || new Date( date ).getMonth() !== new Date( dates[ i - 1 ] ).getMonth() + ); +}; + +/** + * Returns ticks for the x-axis. + * @param {array} uniqueDates - all the unique dates from the input data for the chart + * @param {integer} width - calculated page width + * @param {string} mode - item-comparison or time-comparison + * @param {string} interval - string of the interval used in the graph (hour, day, week...) + * @returns {integer} number of x-axis ticks based on width and chart mode + */ +export const getXTicks = ( uniqueDates, width, mode, interval ) => { + const maxTicks = calculateMaxXTicks( width, mode ); + + if ( + ( uniqueDates.length >= dayTicksThreshold && interval === 'day' ) || + ( uniqueDates.length >= weekTicksThreshold && interval === 'week' ) + ) { + uniqueDates = getFirstDatePerMonth( uniqueDates ); + } + if ( uniqueDates.length <= maxTicks || + ( interval === 'hour' && areDatesInTheSameDay( uniqueDates ) && width > smallBreak ) ) { + return uniqueDates; + } + + const incrementFactor = calculateXTicksIncrementFactor( uniqueDates, maxTicks ); + + return getXTicksFromIncrementFactor( uniqueDates, incrementFactor ); +}; + +/** +* Compares 2 strings and returns a list of words that are unique from s2 +* @param {string} s1 - base string to compare against +* @param {string} s2 - string to compare against the base string +* @param {string|Object} splitChar - character or RegExp to use to deliminate words +* @returns {array} of unique words that appear in s2 but not in s1, the base string +*/ +export const compareStrings = ( s1, s2, splitChar = new RegExp( [ ' |,' ], 'g' ) ) => { + const string1 = s1.split( splitChar ); + const string2 = s2.split( splitChar ); + const diff = new Array(); + const long = s1.length > s2.length ? string1 : string2; + for ( let x = 0; x < long.length; x++ ) { + string1[ x ] !== string2[ x ] && diff.push( string2[ x ] ); + } + return diff; +}; + +export const drawAxis = ( node, params ) => { + const xScale = params.type === 'line' ? params.xLineScale : params.xScale; + const removeDuplicateDates = ( d, i, ticks, formatter ) => { + const monthDate = d instanceof Date ? d : new Date( d ); + let prevMonth = i !== 0 ? ticks[ i - 1 ] : ticks[ i ]; + prevMonth = prevMonth instanceof Date ? prevMonth : new Date( prevMonth ); + return i === 0 + ? formatter( monthDate ) + : compareStrings( formatter( prevMonth ), formatter( monthDate ) ).join( ' ' ); + }; + + const yGrids = []; + for ( let i = 0; i < 4; i++ ) { + if ( params.yMax > 1 ) { + const roundedValue = Math.round( i / 3 * params.yMax ); + if ( yGrids[ yGrids.length - 1 ] !== roundedValue ) { + yGrids.push( roundedValue ); + } + } else { + yGrids.push( i / 3 * params.yMax ); + } + } + + const ticks = params.xTicks.map( d => ( params.type === 'line' ? new Date( d ) : d ) ); + + node + .append( 'g' ) + .attr( 'class', 'axis' ) + .attr( 'aria-hidden', 'true' ) + .attr( 'transform', `translate(0, ${ params.height })` ) + .call( + d3AxisBottom( xScale ) + .tickValues( ticks ) + .tickFormat( ( d, i ) => params.interval === 'hour' + ? params.xFormat( d ) + : removeDuplicateDates( d, i, ticks, params.xFormat ) ) + ); + + node + .append( 'g' ) + .attr( 'class', 'axis axis-month' ) + .attr( 'aria-hidden', 'true' ) + .attr( 'transform', `translate(0, ${ params.height + 20 })` ) + .call( + d3AxisBottom( xScale ) + .tickValues( ticks ) + .tickFormat( ( d, i ) => removeDuplicateDates( d, i, ticks, params.x2Format ) ) + ) + .call( g => g.select( '.domain' ).remove() ); + + node + .append( 'g' ) + .attr( 'class', 'pipes' ) + .attr( 'transform', `translate(0, ${ params.height })` ) + .call( + d3AxisBottom( xScale ) + .tickValues( ticks ) + .tickSize( 5 ) + .tickFormat( '' ) + ); + + node + .append( 'g' ) + .attr( 'class', 'grid' ) + .attr( 'transform', `translate(-${ params.margin.left },0)` ) + .call( + d3AxisLeft( params.yScale ) + .tickValues( yGrids ) + .tickSize( -params.width - params.margin.left - params.margin.right ) + .tickFormat( '' ) + ) + .call( g => g.select( '.domain' ).remove() ); + + node + .append( 'g' ) + .attr( 'class', 'axis y-axis' ) + .attr( 'aria-hidden', 'true' ) + .attr( 'transform', 'translate(-50, 0)' ) + .attr( 'text-anchor', 'start' ) + .call( + d3AxisLeft( params.yTickOffset ) + .tickValues( yGrids ) + .tickFormat( d => params.yFormat( d !== 0 ? d : 0 ) ) + ); + + node.selectAll( '.domain' ).remove(); + node + .selectAll( '.axis' ) + .selectAll( '.tick' ) + .select( 'line' ) + .remove(); +}; diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/bar-chart.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/bar-chart.js new file mode 100644 index 00000000000..3854e446702 --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/bar-chart.js @@ -0,0 +1,103 @@ +/** @format */ + +/** + * External dependencies + */ +import { get } from 'lodash'; +import { event as d3Event, select as d3Select } from 'd3-selection'; + +/** + * Internal dependencies + */ +import { getColor } from './color'; +import { calculateTooltipPosition, hideTooltip, showTooltip } from './tooltip'; + +const handleMouseOverBarChart = ( date, parentNode, node, data, params, position ) => { + d3Select( parentNode ) + .select( '.barfocus' ) + .attr( 'opacity', '0.1' ); + showTooltip( params, data.find( e => e.date === date ), position ); +}; + +export const drawBars = ( node, data, params ) => { + const barGroup = node + .append( 'g' ) + .attr( 'class', 'bars' ) + .selectAll( 'g' ) + .data( data ) + .enter() + .append( 'g' ) + .attr( 'transform', d => `translate(${ params.xScale( d.date ) },0)` ) + .attr( 'class', 'bargroup' ) + .attr( 'role', 'region' ) + .attr( + 'aria-label', + d => + params.mode === 'item-comparison' + ? params.tooltipLabelFormat( d.date instanceof Date ? d.date : new Date( d.date ) ) + : null + ); + + barGroup + .append( 'rect' ) + .attr( 'class', 'barfocus' ) + .attr( 'x', 0 ) + .attr( 'y', 0 ) + .attr( 'width', params.xGroupScale.range()[ 1 ] ) + .attr( 'height', params.height ) + .attr( 'opacity', '0' ); + + barGroup + .selectAll( '.bar' ) + .data( d => + params.orderedKeys.filter( row => row.visible ).map( row => ( { + key: row.key, + focus: row.focus, + value: get( d, [ row.key, 'value' ], 0 ), + label: get( d, [ row.key, 'label' ], '' ), + visible: row.visible, + date: d.date, + } ) ) + ) + .enter() + .append( 'rect' ) + .attr( 'class', 'bar' ) + .attr( 'x', d => params.xGroupScale( d.key ) ) + .attr( 'y', d => params.yScale( d.value ) ) + .attr( 'width', params.xGroupScale.bandwidth() ) + .attr( 'height', d => params.height - params.yScale( d.value ) ) + .attr( 'fill', d => getColor( d.key, params ) ) + .attr( 'tabindex', '0' ) + .attr( 'aria-label', d => { + const label = params.mode === 'time-comparison' && d.label ? d.label : d.key; + return `${ label } ${ params.tooltipValueFormat( d.value ) }`; + } ) + .style( 'opacity', d => { + const opacity = d.focus ? 1 : 0.1; + return d.visible ? opacity : 0; + } ) + .on( 'focus', ( d, i, nodes ) => { + const targetNode = d.value > 0 ? d3Event.target : d3Event.target.parentNode; + const position = calculateTooltipPosition( targetNode, node.node(), params.tooltipPosition ); + handleMouseOverBarChart( d.date, nodes[ i ].parentNode, node, data, params, position ); + } ) + .on( 'blur', ( d, i, nodes ) => hideTooltip( nodes[ i ].parentNode, params.tooltip ) ); + + barGroup + .append( 'rect' ) + .attr( 'class', 'barmouse' ) + .attr( 'x', 0 ) + .attr( 'y', 0 ) + .attr( 'width', params.xGroupScale.range()[ 1 ] ) + .attr( 'height', params.height ) + .attr( 'opacity', '0' ) + .on( 'mouseover', ( d, i, nodes ) => { + const position = calculateTooltipPosition( + d3Event.target, + node.node(), + params.tooltipPosition + ); + handleMouseOverBarChart( d.date, nodes[ i ].parentNode, node, data, params, position ); + } ) + .on( 'mouseout', ( d, i, nodes ) => hideTooltip( nodes[ i ].parentNode, params.tooltip ) ); +}; diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/breakpoints.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/breakpoints.js new file mode 100644 index 00000000000..19b8b559da9 --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/breakpoints.js @@ -0,0 +1,3 @@ +/** @format */ +export const smallBreak = 783; +export const wideBreak = 1365; diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/color.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/color.js new file mode 100644 index 00000000000..1e2a42c922a --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/color.js @@ -0,0 +1,25 @@ +/** @format */ + +/** + * External dependencies + */ +import { findIndex } from 'lodash'; + +export const getColor = ( key, params ) => { + const smallColorScales = [ + [], + [ 0.5 ], + [ 0.333, 0.667 ], + [ 0.2, 0.5, 0.8 ], + [ 0.12, 0.375, 0.625, 0.88 ], + ]; + let keyValue = 0; + const len = params.orderedKeys.length; + const idx = findIndex( params.orderedKeys, d => d.key === key ); + if ( len < 5 ) { + keyValue = smallColorScales[ len ][ idx ]; + } else { + keyValue = idx / ( params.orderedKeys.length - 1 ); + } + return params.colorScheme( keyValue ); +}; diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/index.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/index.js new file mode 100644 index 00000000000..8dcbb4716f0 --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/index.js @@ -0,0 +1,136 @@ +/** @format */ + +/** + * External dependencies + */ +import { find, get } from 'lodash'; +import { format as d3Format } from 'd3-format'; +import { line as d3Line } from 'd3-shape'; + +/** + * Allows an overriding formatter or defaults to d3Format or d3TimeFormat + * @param {string|function} format - either a format string for the D3 formatters or an overriding fomatting method + * @param {function} formatter - default d3Format or another formatting method, which accepts the string `format` + * @returns {function} to be used to format an input given the format and formatter + */ +export const getFormatter = ( format, formatter = d3Format ) => + typeof format === 'function' ? format : formatter( format ); + +/** + * Describes `getUniqueKeys` + * @param {array} data - The chart component's `data` prop. + * @returns {array} of unique category keys + */ +export const getUniqueKeys = data => { + return [ + ...new Set( + data.reduce( ( accum, curr ) => { + Object.keys( curr ).forEach( key => key !== 'date' && accum.push( key ) ); + return accum; + }, [] ) + ), + ]; +}; + +/** + * Describes `getOrderedKeys` + * @param {array} data - The chart component's `data` prop. + * @param {array} uniqueKeys - from `getUniqueKeys`. + * @returns {array} of unique category keys ordered by cumulative total value + */ +export const getOrderedKeys = ( data, uniqueKeys ) => + uniqueKeys + .map( key => ( { + key, + focus: true, + total: data.reduce( ( a, c ) => a + c[ key ].value, 0 ), + visible: true, + } ) ) + .sort( ( a, b ) => b.total - a.total ); + +/** + * Describes `getLineData` + * @param {array} data - The chart component's `data` prop. + * @param {array} orderedKeys - from `getOrderedKeys`. + * @returns {array} an array objects with a category `key` and an array of `values` with `date` and `value` properties + */ +export const getLineData = ( data, orderedKeys ) => + orderedKeys.map( row => ( { + key: row.key, + focus: row.focus, + visible: row.visible, + values: data.map( d => ( { + date: d.date, + focus: row.focus, + label: get( d, [ row.key, 'label' ], '' ), + value: get( d, [ row.key, 'value' ], 0 ), + visible: row.visible, + } ) ), + } ) ); + +/** + * Describes `getUniqueDates` + * @param {array} lineData - from `GetLineData` + * @param {function} parseDate - D3 time format parser + * @returns {array} an array of unique date values sorted from earliest to latest + */ +export const getUniqueDates = ( lineData, parseDate ) => { + return [ + ...new Set( + lineData.reduce( ( accum, { values } ) => { + values.forEach( ( { date } ) => accum.push( date ) ); + return accum; + }, [] ) + ), + ].sort( ( a, b ) => parseDate( a ) - parseDate( b ) ); +}; + +/** + * Describes getLine + * @param {function} xLineScale - from `getXLineScale`. + * @param {function} yScale - from `getYScale`. + * @returns {function} the D3 line function for plotting all category values + */ +export const getLine = ( xLineScale, yScale ) => + d3Line() + .x( d => xLineScale( new Date( d.date ) ) ) + .y( d => yScale( d.value ) ); + +/** + * Describes getDateSpaces + * @param {array} data - The chart component's `data` prop. + * @param {array} uniqueDates - from `getUniqueDates` + * @param {number} width - calculated width of the charting space + * @param {function} xLineScale - from `getXLineScale` + * @returns {array} that icnludes the date, start (x position) and width to mode the mouseover rectangles + */ +export const getDateSpaces = ( data, uniqueDates, width, xLineScale ) => + uniqueDates.map( ( d, i ) => { + const datapoints = find( data, { date: d } ); + const xNow = xLineScale( new Date( d ) ); + const xPrev = + i >= 1 + ? xLineScale( new Date( uniqueDates[ i - 1 ] ) ) + : xLineScale( new Date( uniqueDates[ 0 ] ) ); + const xNext = + i < uniqueDates.length - 1 + ? xLineScale( new Date( uniqueDates[ i + 1 ] ) ) + : xLineScale( new Date( uniqueDates[ uniqueDates.length - 1 ] ) ); + let xWidth = i === 0 ? xNext - xNow : xNow - xPrev; + const xStart = i === 0 ? 0 : xNow - xWidth / 2; + xWidth = i === 0 || i === uniqueDates.length - 1 ? xWidth / 2 : xWidth; + return { + date: d, + start: uniqueDates.length > 1 ? xStart : 0, + width: uniqueDates.length > 1 ? xWidth : width, + values: Object.keys( datapoints ) + .filter( key => key !== 'date' ) + .map( key => { + return { + key, + value: datapoints[ key ].value, + date: d, + }; + } ), + }; + } ); diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/line-chart.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/line-chart.js new file mode 100644 index 00000000000..b4193b5fd8b --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/line-chart.js @@ -0,0 +1,138 @@ +/** @format */ + +/** + * External dependencies + */ +import { event as d3Event, select as d3Select } from 'd3-selection'; +import { smallBreak, wideBreak } from './breakpoints'; + +/** + * Internal dependencies + */ +import { getColor } from './color'; +import { calculateTooltipPosition, hideTooltip, showTooltip } from './tooltip'; + +const handleMouseOverLineChart = ( date, parentNode, node, data, params, position ) => { + d3Select( parentNode ) + .select( '.focus-grid' ) + .attr( 'opacity', '1' ); + showTooltip( params, data.find( e => e.date === date ), position ); +}; + +export const drawLines = ( node, data, params ) => { + const series = node + .append( 'g' ) + .attr( 'class', 'lines' ) + .selectAll( '.line-g' ) + .data( params.lineData.filter( d => d.visible ).reverse() ) + .enter() + .append( 'g' ) + .attr( 'class', 'line-g' ) + .attr( 'role', 'region' ) + .attr( 'aria-label', d => d.key ); + + let lineStroke = params.width <= wideBreak || params.uniqueDates.length > 50 ? 2 : 3; + lineStroke = params.width <= smallBreak ? 1.25 : lineStroke; + const dotRadius = params.width <= wideBreak ? 4 : 6; + + series + .append( 'path' ) + .attr( 'fill', 'none' ) + .attr( 'stroke-width', lineStroke ) + .attr( 'stroke-linejoin', 'round' ) + .attr( 'stroke-linecap', 'round' ) + .attr( 'stroke', d => getColor( d.key, params ) ) + .style( 'opacity', d => { + const opacity = d.focus ? 1 : 0.1; + return d.visible ? opacity : 0; + } ) + .attr( 'd', d => params.line( d.values ) ); + + const minDataPointSpacing = 36; + + params.width / params.uniqueDates.length > minDataPointSpacing && + series + .selectAll( 'circle' ) + .data( ( d, i ) => d.values.map( row => ( { ...row, i, visible: d.visible, key: d.key } ) ) ) + .enter() + .append( 'circle' ) + .attr( 'r', dotRadius ) + .attr( 'fill', d => getColor( d.key, params ) ) + .attr( 'stroke', '#fff' ) + .attr( 'stroke-width', lineStroke + 1 ) + .style( 'opacity', d => { + const opacity = d.focus ? 1 : 0.1; + return d.visible ? opacity : 0; + } ) + .attr( 'cx', d => params.xLineScale( new Date( d.date ) ) ) + .attr( 'cy', d => params.yScale( d.value ) ) + .attr( 'tabindex', '0' ) + .attr( 'aria-label', d => { + const label = d.label + ? d.label + : params.tooltipLabelFormat( d.date instanceof Date ? d.date : new Date( d.date ) ); + return `${ label } ${ params.tooltipValueFormat( d.value ) }`; + } ) + .on( 'focus', ( d, i, nodes ) => { + const position = calculateTooltipPosition( + d3Event.target, + node.node(), + params.tooltipPosition + ); + handleMouseOverLineChart( d.date, nodes[ i ].parentNode, node, data, params, position ); + } ) + .on( 'blur', ( d, i, nodes ) => hideTooltip( nodes[ i ].parentNode, params.tooltip ) ); + + const focus = node + .append( 'g' ) + .attr( 'class', 'focusspaces' ) + .selectAll( '.focus' ) + .data( params.dateSpaces ) + .enter() + .append( 'g' ) + .attr( 'class', 'focus' ); + + const focusGrid = focus + .append( 'g' ) + .attr( 'class', 'focus-grid' ) + .attr( 'opacity', '0' ); + + focusGrid + .append( 'line' ) + .attr( 'x1', d => params.xLineScale( new Date( d.date ) ) ) + .attr( 'y1', 0 ) + .attr( 'x2', d => params.xLineScale( new Date( d.date ) ) ) + .attr( 'y2', params.height ); + + focusGrid + .selectAll( 'circle' ) + .data( d => d.values.reverse() ) + .enter() + .append( 'circle' ) + .attr( 'r', dotRadius + 2 ) + .attr( 'fill', d => getColor( d.key, params ) ) + .attr( 'stroke', '#fff' ) + .attr( 'stroke-width', lineStroke + 2 ) + .attr( 'cx', d => params.xLineScale( new Date( d.date ) ) ) + .attr( 'cy', d => params.yScale( d.value ) ); + + focus + .append( 'rect' ) + .attr( 'class', 'focus-g' ) + .attr( 'x', d => d.start ) + .attr( 'y', 0 ) + .attr( 'width', d => d.width ) + .attr( 'height', params.height ) + .attr( 'opacity', 0 ) + .on( 'mouseover', ( d, i, nodes ) => { + const elementWidthRatio = i === 0 || i === params.dateSpaces.length - 1 ? 0 : 0.5; + const position = calculateTooltipPosition( + d3Event.target, + node.node(), + params.tooltipPosition, + elementWidthRatio + ); + handleMouseOverLineChart( d.date, nodes[ i ].parentNode, node, data, params, position ); + } ) + .on( 'mouseout', ( d, i, nodes ) => hideTooltip( nodes[ i ].parentNode, params.tooltip ) ); +}; diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/scales.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/scales.js new file mode 100644 index 00000000000..5639530be73 --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/scales.js @@ -0,0 +1,83 @@ +/** @format */ + +/** + * External dependencies + */ +import { max as d3Max } from 'd3-array'; +import { + scaleBand as d3ScaleBand, + scaleLinear as d3ScaleLinear, + scaleTime as d3ScaleTime, +} from 'd3-scale'; + +/** + * Describes and rounds the maximum y value to the nearest thousand, ten-thousand, million etc. In case it is a decimal number, ceils it. + * @param {array} lineData - from `getLineData` + * @returns {number} the maximum value in the timeseries multiplied by 4/3 + */ +export const getYMax = lineData => { + const yMax = 4 / 3 * d3Max( lineData, d => d3Max( d.values.map( date => date.value ) ) ); + const pow3Y = Math.pow( 10, ( ( Math.log( yMax ) * Math.LOG10E + 1 ) | 0 ) - 2 ) * 3; + return Math.ceil( Math.ceil( yMax / pow3Y ) * pow3Y ); +}; + +/** + * Describes getXScale + * @param {array} uniqueDates - from `getUniqueDates` + * @param {number} width - calculated width of the charting space + * @param {boolean} compact - whether the chart must be compact (without padding + between days) + * @returns {function} a D3 scale of the dates + */ +export const getXScale = ( uniqueDates, width, compact = false ) => + d3ScaleBand() + .domain( uniqueDates ) + .rangeRound( [ 0, width ] ) + .paddingInner( compact ? 0 : 0.1 ); + +/** + * Describes getXGroupScale + * @param {array} orderedKeys - from `getOrderedKeys` + * @param {function} xScale - from `getXScale` + * @param {boolean} compact - whether the chart must be compact (without padding + between days) + * @returns {function} a D3 scale for each category within the xScale range + */ +export const getXGroupScale = ( orderedKeys, xScale, compact = false ) => + d3ScaleBand() + .domain( orderedKeys.filter( d => d.visible ).map( d => d.key ) ) + .rangeRound( [ 0, xScale.bandwidth() ] ) + .padding( compact ? 0 : 0.07 ); + +/** + * Describes getXLineScale + * @param {array} uniqueDates - from `getUniqueDates` + * @param {number} width - calculated width of the charting space + * @returns {function} a D3 scaletime for each date + */ +export const getXLineScale = ( uniqueDates, width ) => + d3ScaleTime() + .domain( [ new Date( uniqueDates[ 0 ] ), new Date( uniqueDates[ uniqueDates.length - 1 ] ) ] ) + .rangeRound( [ 0, width ] ); + +/** + * Describes getYScale + * @param {number} height - calculated height of the charting space + * @param {number} yMax - from `getYMax` + * @returns {function} the D3 linear scale from 0 to the value from `getYMax` + */ +export const getYScale = ( height, yMax ) => + d3ScaleLinear() + .domain( [ 0, yMax ] ) + .rangeRound( [ height, 0 ] ); + +/** + * Describes getyTickOffset + * @param {number} height - calculated height of the charting space + * @param {number} yMax - from `getYMax` + * @returns {function} the D3 linear scale from 0 to the value from `getYMax`, offset by 12 pixels down + */ +export const getYTickOffset = ( height, yMax ) => + d3ScaleLinear() + .domain( [ 0, yMax ] ) + .rangeRound( [ height + 12, 12 ] ); diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/test/utils.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/axis.js similarity index 51% rename from plugins/woocommerce-admin/packages/components/src/chart/d3chart/test/utils.js rename to plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/axis.js index d29ed740b6e..7a19250c93f 100644 --- a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/test/utils.js +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/axis.js @@ -1,162 +1,12 @@ +/** @format */ /** * External dependencies - * - * @format */ -// import { noop } from 'lodash'; -import { utcParse as d3UTCParse } from 'd3-time-format'; /** - * Internal dependencies - */ -import dummyOrders from './fixtures/dummy'; -import { - compareStrings, - getDateSpaces, - getOrderedKeys, - getLineData, - getUniqueKeys, - getUniqueDates, - getXScale, - getXGroupScale, - getXLineScale, - getXTicks, - getYMax, - getYScale, - getYTickOffset, -} from '../utils'; - -const orderedKeys = [ - { - key: 'Cap', - focus: true, - visible: true, - total: 34513697, - }, - { - key: 'T-Shirt', - focus: true, - visible: true, - total: 14762281, - }, - { - key: 'Sunglasses', - focus: true, - visible: true, - total: 12430349, - }, - { - key: 'Polo', - focus: true, - visible: true, - total: 8712807, - }, - { - key: 'Hoodie', - focus: true, - visible: true, - total: 6968764, - }, -]; -const orderedDates = [ - '2018-05-30T00:00:00', - '2018-05-31T00:00:00', - '2018-06-01T00:00:00', - '2018-06-02T00:00:00', - '2018-06-03T00:00:00', - '2018-06-04T00:00:00', -]; -const parseDate = d3UTCParse( '%Y-%m-%dT%H:%M:%S' ); -const testUniqueKeys = getUniqueKeys( dummyOrders ); -const testOrderedKeys = getOrderedKeys( dummyOrders, testUniqueKeys ); -const testLineData = getLineData( dummyOrders, testOrderedKeys ); -const testUniqueDates = getUniqueDates( testLineData, parseDate ); -const testXScale = getXScale( testUniqueDates, 100 ); -const testXLineScale = getXLineScale( testUniqueDates, 100 ); -const testYMax = getYMax( testLineData ); -const testYScale = getYScale( 100, testYMax ); - -describe( 'parseDate', () => { - it( 'correctly parse date in the expected format', () => { - const testDate = parseDate( '2018-06-30T00:00:00' ); - const expectedDate = new Date( Date.UTC( 2018, 5, 30 ) ); - expect( testDate.getTime() ).toEqual( expectedDate.getTime() ); - } ); -} ); - -describe( 'getUniqueKeys', () => { - it( 'returns an array of keys excluding date', () => { - // sort is a mutating action so we need a copy - const testUniqueKeysClone = testUniqueKeys.slice(); - const sortedAZKeys = orderedKeys.map( d => d.key ).slice(); - expect( testUniqueKeysClone.sort() ).toEqual( sortedAZKeys.sort() ); - } ); -} ); - -describe( 'getOrderedKeys', () => { - it( 'returns an array of keys order by value from largest to smallest', () => { - expect( testOrderedKeys ).toEqual( orderedKeys ); - } ); -} ); - -describe( 'getLineData', () => { - it( 'returns a sorted array of objects with category key', () => { - expect( testLineData ).toBeInstanceOf( Array ); - expect( testLineData ).toHaveLength( 5 ); - expect( testLineData.map( d => d.key ) ).toEqual( orderedKeys.map( d => d.key ) ); - } ); - - testLineData.forEach( d => { - it( 'ensure a key and that the values property is an array', () => { - expect( d ).toHaveProperty( 'key' ); - expect( d ).toHaveProperty( 'values' ); - expect( d.values ).toBeInstanceOf( Array ); - } ); - - it( 'ensure all unique dates exist in values array', () => { - const rowDates = d.values.map( row => row.date ); - expect( rowDates ).toEqual( orderedDates ); - } ); - - d.values.forEach( row => { - it( 'ensure a date property and that the values property is an array with date (parseable) and value properties', () => { - expect( row ).toHaveProperty( 'date' ); - expect( row ).toHaveProperty( 'value' ); - expect( parseDate( row.date ) ).not.toBeNull(); - expect( typeof row.date ).toBe( 'string' ); - expect( typeof row.value ).toBe( 'number' ); - } ); - } ); - } ); -} ); - -describe( 'getXScale', () => { - it( 'properly scale inputs to the provided domain and range', () => { - expect( testXScale( orderedDates[ 0 ] ) ).toEqual( 3 ); - expect( testXScale( orderedDates[ 2 ] ) ).toEqual( 35 ); - expect( testXScale( orderedDates[ orderedDates.length - 1 ] ) ).toEqual( 83 ); - } ); - it( 'properly scale inputs and test the bandwidth', () => { - expect( testXScale.bandwidth() ).toEqual( 14 ); - } ); -} ); - -describe( 'getXGroupScale', () => { - it( 'properly scale inputs based on the getXScale', () => { - const testXGroupScale = getXGroupScale( testOrderedKeys, testXScale ); - expect( testXGroupScale( orderedKeys[ 0 ].key ) ).toEqual( 2 ); - expect( testXGroupScale( orderedKeys[ 2 ].key ) ).toEqual( 6 ); - expect( testXGroupScale( orderedKeys[ orderedKeys.length - 1 ].key ) ).toEqual( 10 ); - } ); -} ); - -describe( 'getXLineScale', () => { - it( 'properly scale inputs for the line', () => { - expect( testXLineScale( new Date( orderedDates[ 0 ] ) ) ).toEqual( 0 ); - expect( testXLineScale( new Date( orderedDates[ 2 ] ) ) ).toEqual( 40 ); - expect( testXLineScale( new Date( orderedDates[ orderedDates.length - 1 ] ) ) ).toEqual( 100 ); - } ); -} ); +* Internal dependencies +*/ +import { compareStrings, getXTicks } from '../axis'; describe( 'getXTicks', () => { describe( 'interval=day', () => { @@ -380,42 +230,6 @@ describe( 'getXTicks', () => { } ); } ); -describe( 'getYMax', () => { - it( 'calculate the correct maximum y value', () => { - expect( testYMax ).toEqual( 15000000 ); - } ); -} ); - -describe( 'getYScale', () => { - it( 'properly scale the y values given the height and maximum y value', () => { - expect( testYScale( 0 ) ).toEqual( 100 ); - expect( testYScale( testYMax ) ).toEqual( 0 ); - } ); -} ); - -describe( 'getYTickOffset', () => { - it( 'properly scale the y values for the y-axis ticks given the height and maximum y value', () => { - const testYTickOffset1 = getYTickOffset( 100, testYMax ); - expect( testYTickOffset1( 0 ) ).toEqual( 112 ); - expect( testYTickOffset1( testYMax ) ).toEqual( 12 ); - } ); -} ); - -describe( 'getDateSpaces', () => { - it( 'return an array used to space out the mouseover rectangles, used for tooltips', () => { - const testDateSpaces = getDateSpaces( dummyOrders, testUniqueDates, 100, testXLineScale ); - expect( testDateSpaces[ 0 ].date ).toEqual( '2018-05-30T00:00:00' ); - expect( testDateSpaces[ 0 ].start ).toEqual( 0 ); - expect( testDateSpaces[ 0 ].width ).toEqual( 10 ); - expect( testDateSpaces[ 3 ].date ).toEqual( '2018-06-02T00:00:00' ); - expect( testDateSpaces[ 3 ].start ).toEqual( 50 ); - expect( testDateSpaces[ 3 ].width ).toEqual( 20 ); - expect( testDateSpaces[ testDateSpaces.length - 1 ].date ).toEqual( '2018-06-04T00:00:00' ); - expect( testDateSpaces[ testDateSpaces.length - 1 ].start ).toEqual( 90 ); - expect( testDateSpaces[ testDateSpaces.length - 1 ].width ).toEqual( 10 ); - } ); -} ); - describe( 'compareStrings', () => { it( 'return an array of unique words from s2 that dont appear in base string', () => { expect( compareStrings( 'Jul 2018', 'Aug 2018' ).join( ' ' ) ).toEqual( 'Aug' ); diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/fixtures/dummy-ordered-dates.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/fixtures/dummy-ordered-dates.js new file mode 100644 index 00000000000..85839f2932d --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/fixtures/dummy-ordered-dates.js @@ -0,0 +1,9 @@ +/** @format */ +export default [ + '2018-05-30T00:00:00', + '2018-05-31T00:00:00', + '2018-06-01T00:00:00', + '2018-06-02T00:00:00', + '2018-06-03T00:00:00', + '2018-06-04T00:00:00', +]; diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/fixtures/dummy-ordered-keys.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/fixtures/dummy-ordered-keys.js new file mode 100644 index 00000000000..713e7ea877f --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/fixtures/dummy-ordered-keys.js @@ -0,0 +1,33 @@ +/** @format */ +export default [ + { + key: 'Cap', + focus: true, + visible: true, + total: 34513697, + }, + { + key: 'T-Shirt', + focus: true, + visible: true, + total: 14762281, + }, + { + key: 'Sunglasses', + focus: true, + visible: true, + total: 12430349, + }, + { + key: 'Polo', + focus: true, + visible: true, + total: 8712807, + }, + { + key: 'Hoodie', + focus: true, + visible: true, + total: 6968764, + }, +]; diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/test/fixtures/dummy.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/fixtures/dummy-orders.js similarity index 95% rename from plugins/woocommerce-admin/packages/components/src/chart/d3chart/test/fixtures/dummy.js rename to plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/fixtures/dummy-orders.js index 6e1488316ed..9f825544f07 100644 --- a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/test/fixtures/dummy.js +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/fixtures/dummy-orders.js @@ -1,11 +1,4 @@ /** @format */ - -/** - * /* eslint-disable quote-props - * - * @format - */ - export default [ { date: '2018-05-30T00:00:00', diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/index.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/index.js new file mode 100644 index 00000000000..85bae47e55d --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/index.js @@ -0,0 +1,96 @@ +/** @format */ +/** + * External dependencies + */ +import { utcParse as d3UTCParse } from 'd3-time-format'; + +/** + * Internal dependencies + */ +import dummyOrders from './fixtures/dummy-orders'; +import orderedDates from './fixtures/dummy-ordered-dates'; +import orderedKeys from './fixtures/dummy-ordered-keys'; +import { + getDateSpaces, + getOrderedKeys, + getLineData, + getUniqueKeys, + getUniqueDates, +} from '../index'; +import { getXLineScale } from '../scales'; + +const parseDate = d3UTCParse( '%Y-%m-%dT%H:%M:%S' ); +const testUniqueKeys = getUniqueKeys( dummyOrders ); +const testOrderedKeys = getOrderedKeys( dummyOrders, testUniqueKeys ); +const testLineData = getLineData( dummyOrders, testOrderedKeys ); +const testUniqueDates = getUniqueDates( testLineData, parseDate ); +const testXLineScale = getXLineScale( testUniqueDates, 100 ); + +describe( 'parseDate', () => { + it( 'correctly parse date in the expected format', () => { + const testDate = parseDate( '2018-06-30T00:00:00' ); + const expectedDate = new Date( Date.UTC( 2018, 5, 30 ) ); + expect( testDate.getTime() ).toEqual( expectedDate.getTime() ); + } ); +} ); + +describe( 'getUniqueKeys', () => { + it( 'returns an array of keys excluding date', () => { + // sort is a mutating action so we need a copy + const testUniqueKeysClone = testUniqueKeys.slice(); + const sortedAZKeys = orderedKeys.map( d => d.key ).slice(); + expect( testUniqueKeysClone.sort() ).toEqual( sortedAZKeys.sort() ); + } ); +} ); + +describe( 'getOrderedKeys', () => { + it( 'returns an array of keys order by value from largest to smallest', () => { + expect( testOrderedKeys ).toEqual( orderedKeys ); + } ); +} ); + +describe( 'getLineData', () => { + it( 'returns a sorted array of objects with category key', () => { + expect( testLineData ).toBeInstanceOf( Array ); + expect( testLineData ).toHaveLength( 5 ); + expect( testLineData.map( d => d.key ) ).toEqual( orderedKeys.map( d => d.key ) ); + } ); + + testLineData.forEach( d => { + it( 'ensure a key and that the values property is an array', () => { + expect( d ).toHaveProperty( 'key' ); + expect( d ).toHaveProperty( 'values' ); + expect( d.values ).toBeInstanceOf( Array ); + } ); + + it( 'ensure all unique dates exist in values array', () => { + const rowDates = d.values.map( row => row.date ); + expect( rowDates ).toEqual( orderedDates ); + } ); + + d.values.forEach( row => { + it( 'ensure a date property and that the values property is an array with date (parseable) and value properties', () => { + expect( row ).toHaveProperty( 'date' ); + expect( row ).toHaveProperty( 'value' ); + expect( parseDate( row.date ) ).not.toBeNull(); + expect( typeof row.date ).toBe( 'string' ); + expect( typeof row.value ).toBe( 'number' ); + } ); + } ); + } ); +} ); + +describe( 'getDateSpaces', () => { + it( 'return an array used to space out the mouseover rectangles, used for tooltips', () => { + const testDateSpaces = getDateSpaces( dummyOrders, testUniqueDates, 100, testXLineScale ); + expect( testDateSpaces[ 0 ].date ).toEqual( '2018-05-30T00:00:00' ); + expect( testDateSpaces[ 0 ].start ).toEqual( 0 ); + expect( testDateSpaces[ 0 ].width ).toEqual( 10 ); + expect( testDateSpaces[ 3 ].date ).toEqual( '2018-06-02T00:00:00' ); + expect( testDateSpaces[ 3 ].start ).toEqual( 50 ); + expect( testDateSpaces[ 3 ].width ).toEqual( 20 ); + expect( testDateSpaces[ testDateSpaces.length - 1 ].date ).toEqual( '2018-06-04T00:00:00' ); + expect( testDateSpaces[ testDateSpaces.length - 1 ].start ).toEqual( 90 ); + expect( testDateSpaces[ testDateSpaces.length - 1 ].width ).toEqual( 10 ); + } ); +} ); diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/scales.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/scales.js new file mode 100644 index 00000000000..346d727c5db --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/scales.js @@ -0,0 +1,78 @@ +/** @format */ +/** + * External dependencies + */ +import { utcParse as d3UTCParse } from 'd3-time-format'; + +/** + * Internal dependencies + */ +import dummyOrders from './fixtures/dummy-orders'; +import orderedDates from './fixtures/dummy-ordered-dates'; +import orderedKeys from './fixtures/dummy-ordered-keys'; +import { + getOrderedKeys, + getLineData, + getUniqueKeys, + getUniqueDates, +} from '../index'; +import { getXGroupScale, getXScale, getXLineScale, getYMax, getYScale, getYTickOffset } from '../scales'; + +const parseDate = d3UTCParse( '%Y-%m-%dT%H:%M:%S' ); +const testUniqueKeys = getUniqueKeys( dummyOrders ); +const testOrderedKeys = getOrderedKeys( dummyOrders, testUniqueKeys ); +const testLineData = getLineData( dummyOrders, testOrderedKeys ); +const testUniqueDates = getUniqueDates( testLineData, parseDate ); +const testXScale = getXScale( testUniqueDates, 100 ); +const testXLineScale = getXLineScale( testUniqueDates, 100 ); +const testYMax = getYMax( testLineData ); +const testYScale = getYScale( 100, testYMax ); + +describe( 'getXScale', () => { + it( 'properly scale inputs to the provided domain and range', () => { + expect( testXScale( orderedDates[ 0 ] ) ).toEqual( 3 ); + expect( testXScale( orderedDates[ 2 ] ) ).toEqual( 35 ); + expect( testXScale( orderedDates[ orderedDates.length - 1 ] ) ).toEqual( 83 ); + } ); + it( 'properly scale inputs and test the bandwidth', () => { + expect( testXScale.bandwidth() ).toEqual( 14 ); + } ); +} ); + +describe( 'getXGroupScale', () => { + it( 'properly scale inputs based on the getXScale', () => { + const testXGroupScale = getXGroupScale( testOrderedKeys, testXScale ); + expect( testXGroupScale( orderedKeys[ 0 ].key ) ).toEqual( 2 ); + expect( testXGroupScale( orderedKeys[ 2 ].key ) ).toEqual( 6 ); + expect( testXGroupScale( orderedKeys[ orderedKeys.length - 1 ].key ) ).toEqual( 10 ); + } ); +} ); + +describe( 'getXLineScale', () => { + it( 'properly scale inputs for the line', () => { + expect( testXLineScale( new Date( orderedDates[ 0 ] ) ) ).toEqual( 0 ); + expect( testXLineScale( new Date( orderedDates[ 2 ] ) ) ).toEqual( 40 ); + expect( testXLineScale( new Date( orderedDates[ orderedDates.length - 1 ] ) ) ).toEqual( 100 ); + } ); +} ); + +describe( 'getYMax', () => { + it( 'calculate the correct maximum y value', () => { + expect( testYMax ).toEqual( 15000000 ); + } ); +} ); + +describe( 'getYScale', () => { + it( 'properly scale the y values given the height and maximum y value', () => { + expect( testYScale( 0 ) ).toEqual( 100 ); + expect( testYScale( testYMax ) ).toEqual( 0 ); + } ); +} ); + +describe( 'getYTickOffset', () => { + it( 'properly scale the y values for the y-axis ticks given the height and maximum y value', () => { + const testYTickOffset1 = getYTickOffset( 100, testYMax ); + expect( testYTickOffset1( 0 ) ).toEqual( 112 ); + expect( testYTickOffset1( testYMax ) ).toEqual( 12 ); + } ); +} ); diff --git a/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/tooltip.js b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/tooltip.js new file mode 100644 index 00000000000..edf38cdcd21 --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/tooltip.js @@ -0,0 +1,148 @@ +/** @format */ + +/** + * External dependencies + */ +import { select as d3Select } from 'd3-selection'; + +/** + * Internal dependencies + */ +import { getColor } from './color'; + +export const hideTooltip = ( parentNode, tooltipNode ) => { + d3Select( parentNode ) + .selectAll( '.barfocus, .focus-grid' ) + .attr( 'opacity', '0' ); + d3Select( tooltipNode ) + .style( 'visibility', 'hidden' ); +}; + +const calculateTooltipXPosition = ( + elementCoords, + chartCoords, + tooltipSize, + tooltipMargin, + elementWidthRatio, + tooltipPosition +) => { + const d3BaseCoords = d3Select( '.d3-base' ).node().getBoundingClientRect(); + const leftMargin = Math.max( d3BaseCoords.left, chartCoords.left ); + + if ( tooltipPosition === 'below' ) { + return Math.max( + tooltipMargin, + Math.min( + elementCoords.left + elementCoords.width * 0.5 - tooltipSize.width / 2 - leftMargin, + d3BaseCoords.width - tooltipSize.width - tooltipMargin + ) + ); + } + + const xPosition = + elementCoords.left + elementCoords.width * elementWidthRatio + tooltipMargin - leftMargin; + + if ( xPosition + tooltipSize.width + tooltipMargin > d3BaseCoords.width ) { + return Math.max( + tooltipMargin, + elementCoords.left + + elementCoords.width * ( 1 - elementWidthRatio ) - + tooltipSize.width - + tooltipMargin - + leftMargin + ); + } + + return xPosition; +}; + +const calculateTooltipYPosition = ( + elementCoords, + chartCoords, + tooltipSize, + tooltipMargin, + tooltipPosition +) => { + if ( tooltipPosition === 'below' ) { + return chartCoords.height; + } + + const yPosition = elementCoords.top + tooltipMargin - chartCoords.top; + if ( yPosition + tooltipSize.height + tooltipMargin > chartCoords.height ) { + return Math.max( 0, elementCoords.top - tooltipSize.height - tooltipMargin - chartCoords.top ); + } + + return yPosition; +}; + +export const calculateTooltipPosition = ( element, chart, tooltipPosition, elementWidthRatio = 1 ) => { + const elementCoords = element.getBoundingClientRect(); + const chartCoords = chart.getBoundingClientRect(); + const tooltipSize = d3Select( '.d3-chart__tooltip' ) + .node() + .getBoundingClientRect(); + const tooltipMargin = 24; + + if ( tooltipPosition === 'below' ) { + elementWidthRatio = 0; + } + + return { + x: calculateTooltipXPosition( + elementCoords, + chartCoords, + tooltipSize, + tooltipMargin, + elementWidthRatio, + tooltipPosition + ), + y: calculateTooltipYPosition( + elementCoords, + chartCoords, + tooltipSize, + tooltipMargin, + tooltipPosition + ), + }; +}; + +const getTooltipRowLabel = ( d, row, params ) => { + if ( d[ row.key ].labelDate ) { + return params.tooltipLabelFormat( + d[ row.key ].labelDate instanceof Date + ? d[ row.key ].labelDate + : new Date( d[ row.key ].labelDate ) + ); + } + return row.key; +}; + +export const showTooltip = ( params, d, position ) => { + const keys = params.orderedKeys.filter( row => row.visible ).map( + row => ` +
  • +
    + + ${ getTooltipRowLabel( d, row, params ) } +
    + ${ params.tooltipValueFormat( d[ row.key ].value ) } +
  • + ` + ); + + const tooltipTitle = params.tooltipTitle + ? params.tooltipTitle + : params.tooltipLabelFormat( d.date instanceof Date ? d.date : new Date( d.date ) ); + + d3Select( params.tooltip ) + .style( 'left', position.x + 'px' ) + .style( 'top', position.y + 'px' ) + .style( 'visibility', 'visible' ).html( ` +
    +

    ${ tooltipTitle }

    +
      + ${ keys.join( '' ) } +
    +
    + ` ); +}; diff --git a/plugins/woocommerce-admin/packages/components/src/chart/index.js b/plugins/woocommerce-admin/packages/components/src/chart/index.js index 0486d2f9eec..e45aa2e7403 100644 --- a/plugins/woocommerce-admin/packages/components/src/chart/index.js +++ b/plugins/woocommerce-admin/packages/components/src/chart/index.js @@ -113,8 +113,8 @@ class Chart extends Component { } handleLegendToggle( event ) { - const { data, mode } = this.props; - if ( mode ) { + const { data, interactiveLegend } = this.props; + if ( ! interactiveLegend ) { return; } const orderedKeys = this.state.orderedKeys.map( d => ( { @@ -211,28 +211,44 @@ class Chart extends Component { return 220; } + getLegendPosition() { + const { legendPosition, mode, isViewportWide } = this.props; + if ( legendPosition ) { + return legendPosition; + } + if ( isViewportWide && mode === 'time-comparison' ) { + return 'top'; + } + if ( isViewportWide && mode === 'item-comparison' ) { + return 'side'; + } + return 'bottom'; + } + render() { - const { orderedKeys, visibleData, width } = this.state; + const { interactiveLegend, orderedKeys, visibleData, width } = this.state; const { dateParser, + interval, + isRequesting, + isViewportLarge, itemsLabel, mode, - isViewportLarge, - isViewportWide, + showHeaderControls, title, tooltipLabelFormat, tooltipValueFormat, tooltipTitle, + type, + valueType, xFormat, x2Format, - interval, - valueType, - type, - isRequesting, } = this.props; let { yFormat } = this.props; - const legendDirection = mode === 'time-comparison' && isViewportWide ? 'row' : 'column'; - const chartDirection = ( mode === 'item-comparison' ) && isViewportWide ? 'row' : 'column'; + + const legendPosition = this.getLegendPosition(); + const legendDirection = legendPosition === 'top' ? 'row' : 'column'; + const chartDirection = legendPosition === 'side' ? 'row' : 'column'; const chartHeight = this.getChartHeight(); const legend = ( @@ -241,7 +257,7 @@ class Chart extends Component { data={ orderedKeys } handleLegendHover={ this.handleLegendHover } handleLegendToggle={ this.handleLegendToggle } - interactive={ mode !== 'block' } + interactive={ interactiveLegend } legendDirection={ legendDirection } legendValueFormat={ tooltipValueFormat } totalLabel={ sprintf( itemsLabel, orderedKeys.length ) } @@ -267,10 +283,10 @@ class Chart extends Component { } return (
    - { mode !== 'block' && + { showHeaderControls && (
    { title } - { isViewportWide && legendDirection === 'row' && legend } + { legendPosition === 'top' && legend } { this.renderIntervalSelector() }
    - } + ) }
    - { isViewportWide && legendDirection === 'column' && mode !== 'block' && legend } + { legendPosition === 'side' && legend } { isRequesting && ( @@ -328,7 +344,7 @@ class Chart extends Component { height={ chartHeight } interval={ interval } margin={ margin } - mode={ mode === 'block' ? 'item-comparison' : mode } + mode={ mode } orderedKeys={ orderedKeys } tooltipLabelFormat={ tooltipLabelFormat } tooltipValueFormat={ tooltipValueFormat } @@ -343,7 +359,9 @@ class Chart extends Component { /> ) }
    - { ( ! isViewportWide || mode === 'block' ) &&
    { legend }
    } + { ( legendPosition === 'bottom' ) && ( +
    { legend }
    + ) }
    ); @@ -351,6 +369,10 @@ class Chart extends Component { } Chart.propTypes = { + /** + * Allowed intervals to show in a dropdown. + */ + allowedIntervals: PropTypes.array, /** * An array of data. */ @@ -363,6 +385,11 @@ Chart.propTypes = { * Label describing the legend items. */ itemsLabel: PropTypes.string, + /** + * `item-comparison` (default) or `time-comparison`, this is used to generate correct + * ARIA properties. + */ + mode: PropTypes.oneOf( [ 'item-comparison', 'time-comparison' ] ), /** * Current path */ @@ -371,6 +398,35 @@ Chart.propTypes = { * The query string represented in object form */ query: PropTypes.object, + /** + * Whether the legend items can be activated/deactivated. + */ + interactiveLegend: PropTypes.bool, + /** + * Interval specification (hourly, daily, weekly etc). + */ + interval: PropTypes.oneOf( [ 'hour', 'day', 'week', 'month', 'quarter', 'year' ] ), + /** + * Information about the currently selected interval, and set of allowed intervals for the chart. See `getIntervalsForQuery`. + */ + intervalData: PropTypes.object, + /** + * Render a chart placeholder to signify an in-flight data request. + */ + isRequesting: PropTypes.bool, + /** + * Position the legend must be displayed in. If it's not defined, it's calculated + * depending on the viewport width and the mode. + */ + legendPosition: PropTypes.oneOf( [ 'bottom', 'side', 'top' ] ), + /** + * Wether header UI controls must be displayed. + */ + showHeaderControls: PropTypes.bool, + /** + * A title describing this chart. + */ + title: PropTypes.string, /** * A datetime formatting string or overriding function to format the tooltip label. */ @@ -383,6 +439,14 @@ Chart.propTypes = { * A string to use as a title for the tooltip. Takes preference over `tooltipLabelFormat`. */ tooltipTitle: PropTypes.string, + /** + * Chart type of either `line` or `bar`. + */ + type: PropTypes.oneOf( [ 'bar', 'line' ] ), + /** + * What type of data is to be displayed? Number, Average, String? + */ + valueType: PropTypes.string, /** * A datetime formatting string, passed to d3TimeFormat. */ @@ -395,53 +459,22 @@ Chart.propTypes = { * A number formatting string, passed to d3Format. */ yFormat: PropTypes.string, - /** - * `item-comparison` (default) or `time-comparison`, this is used to generate correct - * ARIA properties. - */ - mode: PropTypes.oneOf( [ 'block', 'item-comparison', 'time-comparison' ] ), - /** - * A title describing this chart. - */ - title: PropTypes.string, - /** - * Chart type of either `line` or `bar`. - */ - type: PropTypes.oneOf( [ 'bar', 'line' ] ), - /** - * Information about the currently selected interval, and set of allowed intervals for the chart. See `getIntervalsForQuery`. - */ - intervalData: PropTypes.object, - /** - * Interval specification (hourly, daily, weekly etc). - */ - interval: PropTypes.oneOf( [ 'hour', 'day', 'week', 'month', 'quarter', 'year' ] ), - /** - * Allowed intervals to show in a dropdown. - */ - allowedIntervals: PropTypes.array, - /** - * What type of data is to be displayed? Number, Average, String? - */ - valueType: PropTypes.string, - /** - * Render a chart placeholder to signify an in-flight data request. - */ - isRequesting: PropTypes.bool, }; Chart.defaultProps = { data: [], dateParser: '%Y-%m-%dT%H:%M:%S', + interactiveLegend: true, + interval: 'day', + isRequesting: false, + mode: 'time-comparison', + showHeaderControls: true, tooltipLabelFormat: '%B %d, %Y', tooltipValueFormat: ',', + type: 'line', xFormat: '%d', x2Format: '%b %Y', yFormat: '$.3s', - mode: 'time-comparison', - type: 'line', - interval: 'day', - isRequesting: false, }; export default withViewportMatch( { diff --git a/plugins/woocommerce-admin/packages/components/src/chart/placeholder.js b/plugins/woocommerce-admin/packages/components/src/chart/placeholder.js index 2fb3ced50e8..0eb374380c0 100644 --- a/plugins/woocommerce-admin/packages/components/src/chart/placeholder.js +++ b/plugins/woocommerce-admin/packages/components/src/chart/placeholder.js @@ -4,6 +4,7 @@ */ import { Component } from '@wordpress/element'; import PropTypes from 'prop-types'; +import { Spinner } from '@wordpress/components'; /** * `ChartPlaceholder` displays a large loading indiciator for use in place of a `Chart` while data is loading. @@ -13,7 +14,9 @@ class ChartPlaceholder extends Component { const { height } = this.props; return ( -