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>
<p class="has-bottom-margin-s is-subtitle-gray">SELECTED DATE {{if this.formattedEndDate " RANGE"}}</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> <p class="has-bottom-margin-s" data-test-export-date-range>
{{this.parseAPITimestamp @startTimestamp "MMMM yyyy"}} {{this.formattedStartDate}}
{{if this.formattedEndDate "-"}} {{if this.formattedEndDate "-"}}
{{this.formattedEndDate}}</p> {{this.formattedEndDate}}</p>
</M.Body> </M.Body>
<M.Footer as |F|> <M.Footer as |F|>
<Hds::ButtonSet> <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::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
</Hds::ButtonSet> </Hds::ButtonSet>
{{#if @upgradesDuringActivity}} {{#if @upgradesDuringActivity}}

View File

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

View File

@@ -12,6 +12,7 @@ import { endOfMonth, formatRFC3339 } from 'date-fns';
import { click } from '@ember/test-helpers'; import { click } from '@ember/test-helpers';
import subMonths from 'date-fns/subMonths'; import subMonths from 'date-fns/subMonths';
import timestamp from 'core/utils/timestamp'; import timestamp from 'core/utils/timestamp';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
module('Integration | Component | clients/attribution', function (hooks) { module('Integration | Component | clients/attribution', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
@@ -19,7 +20,9 @@ module('Integration | Component | clients/attribution', function (hooks) {
hooks.before(function () { hooks.before(function () {
sinon.stub(timestamp, 'now').callsFake(() => new Date('2018-04-03T14:15:30')); sinon.stub(timestamp, 'now').callsFake(() => new Date('2018-04-03T14:15:30'));
}); });
hooks.beforeEach(function () { hooks.beforeEach(function () {
this.csvDownloadStub = sinon.stub(this.owner.lookup('service:download'), 'csv');
const mockNow = timestamp.now(); const mockNow = timestamp.now();
this.mockNow = mockNow; this.mockNow = mockNow;
this.set('startTimestamp', formatRFC3339(subMonths(mockNow, 6))); 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 }, { label: 'auth2/', clients: 2, entity_clients: 1, non_entity_clients: 1 },
]); ]);
}); });
hooks.after(function () { hooks.after(function () {
timestamp.now.restore(); timestamp.now.restore();
this.csvDownloadStub.restore();
}); });
test('it renders empty state with no data', async function (assert) { 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'); .hasText('Export attribution data', 'modal appears to export csv');
assert.dom('[ data-test-export-date-range]').includesText('June 2022 - December 2022'); 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');
});
}); });