UI: fix undefined csv filename (#26485)

* fix undefined file name start date

* add test coverage!

* small copy changes

* one last test!

* add changelog;
This commit is contained in:
claire bontempo
2024-04-18 11:11:23 -07:00
committed by GitHub
parent 79d9bf1572
commit dd939d9a7e
4 changed files with 116 additions and 18 deletions

3
changelog/26485.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:bug
ui: fixes undefined start time in filename for downloaded client count attribution csv
```

View File

@@ -99,13 +99,17 @@
</p>
<p class="has-bottom-margin-s is-subtitle-gray">SELECTED DATE {{if this.formattedEndDate " RANGE"}}</p>
<p class="has-bottom-margin-s" data-test-export-date-range>
{{this.parseAPITimestamp @startTimestamp "MMMM yyyy"}}
{{this.formattedStartDate}}
{{if this.formattedEndDate "-"}}
{{this.formattedEndDate}}</p>
</M.Body>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button @text="Export" {{on "click" (fn this.exportChartData this.formattedCsvFileName)}} />
<Hds::Button
@text="Export"
{{on "click" (fn this.exportChartData this.formattedCsvFileName)}}
data-test-confirm-button
/>
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
</Hds::ButtonSet>
{{#if @upgradesDuringActivity}}

View File

@@ -43,11 +43,8 @@ import { format, isSameMonth } from 'date-fns';
export default class Attribution extends Component {
@service download;
@tracked showCSVDownloadModal = false;
parseAPITimestamp = (time, format) => parseAPITimestamp(time, format);
get attributionLegend() {
const attributionLegend = [
{ key: 'entity_clients', label: 'entity clients' },
@@ -60,6 +57,11 @@ export default class Attribution extends Component {
return attributionLegend;
}
get formattedStartDate() {
if (!this.args.startTimestamp) return null;
return parseAPITimestamp(this.args.startTimestamp, 'MMMM yyyy');
}
get formattedEndDate() {
if (!this.args.startTimestamp && !this.args.endTimestamp) return null;
// displays on CSV export modal, no need to display duplicate months and years
@@ -73,9 +75,6 @@ export default class Attribution extends Component {
}
get isSingleNamespace() {
if (!this.args.totalClientAttribution) {
return 'no data';
}
// if a namespace is selected, then we're viewing top 10 auth methods (mounts)
return !!this.args.selectedNamespace;
}
@@ -100,6 +99,9 @@ export default class Attribution extends Component {
}
get chartText() {
if (!this.args.totalClientAttribution) {
return { description: 'There is a problem gathering data' };
}
const dateText = this.formattedEndDate ? 'date range' : 'month';
switch (this.isSingleNamespace) {
case true:
@@ -121,10 +123,6 @@ export default class Attribution extends Component {
}`,
totalCopy: `The total clients in the namespace for this ${dateText}. This number is useful for identifying overall usage volume.`,
};
case 'no data':
return {
description: 'There is a problem gathering data',
};
default:
return '';
}
@@ -157,15 +155,15 @@ export default class Attribution extends Component {
const csvData = [];
// added to clarify that the row of namespace totals without an auth method (blank) are not additional clients
// but indicate the total clients for that ns, including its auth methods
const upgrade = this.args.upgradesDuringActivity.length
const upgrade = this.args.upgradesDuringActivity?.length
? `\n **data contains an upgrade, mount summation may not equal namespace totals`
: '';
const descriptionOfBlanks = this.isSingleNamespace
? ''
: `\n *namespace totals, inclusive of mount clients ${upgrade}`;
: `\n *namespace totals, inclusive of mount clients${upgrade}`;
const csvHeader = [
'Namespace path',
`"Mount path ${descriptionOfBlanks}"`,
`Mount path${descriptionOfBlanks}`,
'Total clients',
'Entity clients',
'Non-entity clients',
@@ -216,10 +214,10 @@ export default class Attribution extends Component {
get formattedCsvFileName() {
const endRange = this.formattedEndDate ? `-${this.formattedEndDate}` : '';
const csvDateRange = this.formattedStartDate + endRange;
const csvDateRange = this.formattedStartDate ? `_${this.formattedStartDate + endRange}` : '';
return this.isSingleNamespace
? `clients_by_mount_path_${csvDateRange}`
: `clients_by_namespace_${csvDateRange}`;
? `clients_by_mount_path${csvDateRange}`
: `clients_by_namespace${csvDateRange}`;
}
get modalExportText() {

View File

@@ -12,6 +12,7 @@ import { endOfMonth, formatRFC3339 } from 'date-fns';
import { click } from '@ember/test-helpers';
import subMonths from 'date-fns/subMonths';
import timestamp from 'core/utils/timestamp';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
module('Integration | Component | clients/attribution', function (hooks) {
setupRenderingTest(hooks);
@@ -19,7 +20,9 @@ module('Integration | Component | clients/attribution', function (hooks) {
hooks.before(function () {
sinon.stub(timestamp, 'now').callsFake(() => new Date('2018-04-03T14:15:30'));
});
hooks.beforeEach(function () {
this.csvDownloadStub = sinon.stub(this.owner.lookup('service:download'), 'csv');
const mockNow = timestamp.now();
this.mockNow = mockNow;
this.set('startTimestamp', formatRFC3339(subMonths(mockNow, 6)));
@@ -40,8 +43,10 @@ module('Integration | Component | clients/attribution', function (hooks) {
{ label: 'auth2/', clients: 2, entity_clients: 1, non_entity_clients: 1 },
]);
});
hooks.after(function () {
timestamp.now.restore();
this.csvDownloadStub.restore();
});
test('it renders empty state with no data', async function (assert) {
@@ -227,4 +232,92 @@ module('Integration | Component | clients/attribution', function (hooks) {
.hasText('Export attribution data', 'modal appears to export csv');
assert.dom('[ data-test-export-date-range]').includesText('June 2022 - December 2022');
});
test('it downloads csv data for date range', async function (assert) {
assert.expect(2);
await render(hbs`
<Clients::Attribution
@totalClientAttribution={{this.totalClientAttribution}}
@responseTimestamp={{this.timestamp}}
@startTimestamp="2022-06-01T23:00:11.050Z"
@endTimestamp="2022-12-01T23:00:11.050Z"
/>
`);
await click('[data-test-attribution-export-button]');
await click(GENERAL.confirmButton);
const [filename, content] = this.csvDownloadStub.lastCall.args;
assert.strictEqual(filename, 'clients_by_namespace_June 2022-December 2022', 'csv has expected filename');
assert.strictEqual(
content,
`Namespace path,Mount path\n *namespace totals, inclusive of mount clients,Total clients,Entity clients,Non-entity clients\nsecond,*,10,7,3\nfirst,*,5,3,2`,
'csv has expected content'
);
});
test('it downloads csv data for a single month', async function (assert) {
assert.expect(2);
await render(hbs`
<Clients::Attribution
@totalClientAttribution={{this.totalClientAttribution}}
@responseTimestamp={{this.timestamp}}
@startTimestamp="2022-06-01T23:00:11.050Z"
@endTimestamp="2022-06-21T23:00:11.050Z"
/>
`);
await click('[data-test-attribution-export-button]');
await click(GENERAL.confirmButton);
const [filename, content] = this.csvDownloadStub.lastCall.args;
assert.strictEqual(filename, 'clients_by_namespace_June 2022', 'csv has single month in filename');
assert.strictEqual(
content,
`Namespace path,Mount path\n *namespace totals, inclusive of mount clients,Total clients,Entity clients,Non-entity clients\nsecond,*,10,7,3\nfirst,*,5,3,2`,
'csv has expected content'
);
});
test('it downloads csv data when a namespace is selected', async function (assert) {
assert.expect(2);
this.selectedNamespace = 'second';
await render(hbs`
<Clients::Attribution
@totalClientAttribution={{this.namespaceMountsData}}
@selectedNamespace={{this.selectedNamespace}}
@responseTimestamp={{this.timestamp}}
@startTimestamp="2022-06-01T23:00:11.050Z"
@endTimestamp="2022-12-21T23:00:11.050Z"
/>
`);
await click('[data-test-attribution-export-button]');
await click(GENERAL.confirmButton);
const [filename, content] = this.csvDownloadStub.lastCall.args;
assert.strictEqual(
filename,
'clients_by_mount_path_June 2022-December 2022',
'csv has expected filename for a selected namespace'
);
assert.strictEqual(
content,
`Namespace path,Mount path,Total clients,Entity clients,Non-entity clients\nsecond,auth1/,3,2,1\nsecond,auth2/,2,1,1`,
'csv has expected content for a selected namespace'
);
});
test('csv filename omits date if no start/end timestamp', async function (assert) {
assert.expect(1);
await render(hbs`
<Clients::Attribution
@totalClientAttribution={{this.totalClientAttribution}}
@responseTimestamp={{this.timestamp}}
/>
`);
await click('[data-test-attribution-export-button]');
await click(GENERAL.confirmButton);
const [filename, ,] = this.csvDownloadStub.lastCall.args;
assert.strictEqual(filename, 'clients_by_namespace');
});
});