Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions client/my-sites/stats/stats-download-csv/get-stats-csv-filename.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import moment from 'moment';

/**
* Builds the CSV export filename so it matches the data selection.
*
* When a custom date range query is present (`start_date` + `date`), those dates
* are used and the period segment is omitted (query.period is forced to `day` for
* API reasons and is not meaningful in the filename). Otherwise the legacy period
* object label and bounds are used.
* @param {Object} options
* @param {string} options.siteSlug Site slug used as the filename prefix.
* @param {string} options.path Stats module path segment (e.g. "posts").
* @param {Object} options.period Period object with `period`, `startOf`, and `endOf`.
* @param {Object} [options.query] Stats query; custom ranges include `start_date` and `date`.
* @param {boolean} [options.includeDates] Pass false for exports that are not date-scoped
* (e.g. the all-time Emails summary) to omit the period and date segments.
* @returns {string} Filename ending in `.csv`.
*/
export function getStatsCsvFileName( { siteSlug, path, period, query, includeDates = true } ) {
if ( ! includeDates ) {
return [ siteSlug, path ].join( '-' ) + '.csv';
}

const dateLocale = period.startOf.locale();
const customStart = query?.start_date ? moment( query.start_date, 'YYYY-MM-DD', true ) : null;
const customEnd = query?.date ? moment( query.date, 'YYYY-MM-DD', true ) : null;
const hasCustomDateRange = Boolean( customStart?.isValid() && customEnd?.isValid() );

const startDate = hasCustomDateRange
? customStart.locale( dateLocale ).format( 'L' )
: period.startOf.format( 'L' );
const endDate = hasCustomDateRange
? customEnd.locale( dateLocale ).format( 'L' )
: period.endOf.format( 'L' );

if ( hasCustomDateRange ) {
return [ siteSlug, path, startDate, endDate ].join( '-' ) + '.csv';
}

return [ siteSlug, path, period.period, startDate, endDate ].join( '-' ) + '.csv';
}
15 changes: 5 additions & 10 deletions client/my-sites/stats/stats-download-csv/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
isRequestingSiteStatsForQuery,
} from 'calypso/state/stats/lists/selectors';
import { getSelectedSiteId } from 'calypso/state/ui/selectors';
import { getStatsCsvFileName } from './get-stats-csv-filename';

import './style.scss';

Expand All @@ -32,6 +33,7 @@ class StatsDownloadCsv extends Component {
hideIfNoData: PropTypes.bool,
headers: PropTypes.array,
rowModifierFn: PropTypes.func,
includeDates: PropTypes.bool,
};

processExportData = ( data ) => {
Expand All @@ -53,16 +55,9 @@ class StatsDownloadCsv extends Component {

downloadCsv = ( event ) => {
event.preventDefault();
const { siteSlug, path, period, data, headers } = this.props;

const fileName =
[
siteSlug,
path,
period.period,
period.startOf.format( 'L' ),
period.endOf.format( 'L' ),
].join( '-' ) + '.csv';
const { siteSlug, path, period, query, data, headers, includeDates } = this.props;

const fileName = getStatsCsvFileName( { siteSlug, path, period, query, includeDates } );

this.props.recordGoogleEvent( 'Stats', 'CSV Download ' + titlecase( path ) );

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import moment from 'moment';
import { getStatsCsvFileName } from '../get-stats-csv-filename';

describe( 'getStatsCsvFileName', () => {
const siteSlug = 'mercantile.wordpress.org';
const path = 'posts';
let previousLocale;

beforeAll( () => {
previousLocale = moment.locale();
moment.locale( 'en' );
} );

afterAll( () => {
moment.locale( previousLocale );
} );

it( 'uses the selected custom date range for multi-month exports (STATS-420)', () => {
// Period object still reflects a single month unit around the start date,
// which previously produced filenames like posts-month-01/01/2026-01/31/2026.
// query.period is forced to 'day' for custom ranges and is omitted from the filename.
const period = {
period: 'month',
startOf: moment( '2026-01-01' ),
endOf: moment( '2026-01-31' ),
};
const query = {
period: 'day',
start_date: '2026-01-01',
date: '2026-08-06',
summarize: 1,
};

expect( getStatsCsvFileName( { siteSlug, path, period, query } ) ).toBe(
'mercantile.wordpress.org-posts-01/01/2026-08/06/2026.csv'
);
} );

it( 'uses the selected custom date range for a single-month export (STATS-420)', () => {
// A day-period unit around the start date previously produced
// posts-day-01/01/2026-01/01/2026 for a Jan 1–31 selection.
const period = {
period: 'day',
startOf: moment( '2026-01-01' ),
endOf: moment( '2026-01-01' ),
};
const query = {
period: 'day',
start_date: '2026-01-01',
date: '2026-01-31',
summarize: 1,
};

expect( getStatsCsvFileName( { siteSlug, path, period, query } ) ).toBe(
'mercantile.wordpress.org-posts-01/01/2026-01/31/2026.csv'
);
} );

it( 'falls back to period bounds when no custom date range is present', () => {
const period = {
period: 'month',
startOf: moment( '2026-01-01' ),
endOf: moment( '2026-01-31' ),
};
const query = {
period: 'month',
date: '2026-01-31',
};

expect( getStatsCsvFileName( { siteSlug, path, period, query } ) ).toBe(
'mercantile.wordpress.org-posts-month-01/01/2026-01/31/2026.csv'
);
} );

it( 'falls back to period bounds when custom range dates are invalid', () => {
const period = {
period: 'month',
startOf: moment( '2026-01-01' ),
endOf: moment( '2026-01-31' ),
};
const query = {
period: 'day',
start_date: 'not-a-date',
date: '2026-08-06',
summarize: 1,
};

expect( getStatsCsvFileName( { siteSlug, path, period, query } ) ).toBe(
'mercantile.wordpress.org-posts-month-01/01/2026-01/31/2026.csv'
);
} );

it( 'omits the period and date segments when includeDates is false', () => {
const period = {
period: 'day',
startOf: moment( '2026-08-06' ),
endOf: moment( '2026-08-06' ),
};
const query = { quantity: 30 };

expect(
getStatsCsvFileName( { siteSlug, path: 'emails', period, query, includeDates: false } )
).toBe( 'mercantile.wordpress.org-emails.csv' );
} );

it( 'falls back to period bounds when query is omitted', () => {
const period = {
period: 'week',
startOf: moment( '2026-01-05' ),
endOf: moment( '2026-01-11' ),
};

expect( getStatsCsvFileName( { siteSlug, path, period } ) ).toBe(
'mercantile.wordpress.org-posts-week-01/05/2026-01/11/2026.csv'
);
} );
} );
1 change: 1 addition & 0 deletions client/my-sites/stats/stats-email-summary/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const StatsEmailSummaryInner = ( { period, query, context, breadcrumbTrail } ) =
path="emails"
query={ query }
period={ period }
includeDates={ false }
headers={ [ 'title', 'opens_rate', 'unique_clicks', 'link' ] }
rowModifierFn={ ( row, data ) => {
if ( ! Array.isArray( row ) || row.length === 0 ) {
Expand Down
Loading