Replace time element with Hds::Time component (#3112)

* refactor: 💡 use Hds::Time component

* refactor: 💡 fix data test selector

* refactor: 💡 standardardized date/time format

* refactor: 💡 fix tests and remove date helpers

* fix: 🐛 fixed helper test import
This commit is contained in:
Priya Patel authored and GitHub committed 2026-02-02 18:52:19 -06:00
1 parent 1e49283a33
commit 1780077068
19 files changed
+85 -169

No files matched your search

@@ -9,10 +9,9 @@ import {
TYPE_CREDENTIAL_JSON, TYPE_CREDENTIAL_JSON,
TYPE_CREDENTIAL_SSH_PRIVATE_KEY, TYPE_CREDENTIAL_SSH_PRIVATE_KEY,
TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN, TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN,
TYPE_CREDENTIAL_USERNAME_PASSWORD TYPE_CREDENTIAL_USERNAME_PASSWORD,
} from 'api/models/credential'; } from 'api/models/credential';
module('Unit | Model | credential', function (hooks) { module('Unit | Model | credential', function (hooks) {
setupTest(hooks); setupTest(hooks);
@@ -35,7 +34,9 @@ module('Unit | Model | credential', function (hooks) {
test('it has isJSON property and returns the expected values', async function (assert) { test('it has isJSON property and returns the expected values', async function (assert) {
const store = this.owner.lookup('service:store'); const store = this.owner.lookup('service:store');
const modelA = store.createRecord('credential', { type: TYPE_CREDENTIAL_JSON }); const modelA = store.createRecord('credential', {
type: TYPE_CREDENTIAL_JSON,
});
const modelB = store.createRecord('credential', { const modelB = store.createRecord('credential', {
type: TYPE_CREDENTIAL_USERNAME_PASSWORD, type: TYPE_CREDENTIAL_USERNAME_PASSWORD,
}); });
@@ -51,13 +52,15 @@ module('Unit | Model | credential', function (hooks) {
test('it has isUnknown property and returns the expected values', async function (assert) { test('it has isUnknown property and returns the expected values', async function (assert) {
const store = this.owner.lookup('service:store'); const store = this.owner.lookup('service:store');
const modelA = store.createRecord('credential', { const modelA = store.createRecord('credential', {
type: TYPE_CREDENTIAL_USERNAME_PASSWORD type: TYPE_CREDENTIAL_USERNAME_PASSWORD,
}); });
const modelB = store.createRecord('credential', { const modelB = store.createRecord('credential', {
type: TYPE_CREDENTIAL_SSH_PRIVATE_KEY type: TYPE_CREDENTIAL_SSH_PRIVATE_KEY,
}); });
const modelC = store.createRecord('credential', { type: 'unknown' }); const modelC = store.createRecord('credential', { type: 'unknown' });
const modelD = store.createRecord('credential', { type: TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN }); const modelD = store.createRecord('credential', {
type: TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN,
});
assert.false(modelA.isUnknown); assert.false(modelA.isUnknown);
assert.false(modelB.isUnknown); assert.false(modelB.isUnknown);
@@ -70,7 +70,8 @@ module('Unit | Utility | sqlite-query', function (hooks) {
{ field: 'name', alias: 'resource_name' }, { field: 'name', alias: 'resource_name' },
{ field: 'type', alias: 'resource_type' }, { field: 'type', alias: 'resource_type' },
], ],
expectedSelect: '"target".name as resource_name, "target".type as resource_type', expectedSelect:
'"target".name as resource_name, "target".type as resource_type',
}, },
'count with alias': { 'count with alias': {
select: [{ field: 'id', isCount: true, alias: 'total_count' }], select: [{ field: 'id', isCount: true, alias: 'total_count' }],
@@ -118,7 +119,8 @@ module('Unit | Utility | sqlite-query', function (hooks) {
{ field: 'type', isDistinct: true, isCount: true }, { field: 'type', isDistinct: true, isCount: true },
{ field: 'status', isDistinct: true, isCount: true }, { field: 'status', isDistinct: true, isCount: true },
], ],
expectedSelect: 'count(DISTINCT "target".type), count(DISTINCT "target".status)', expectedSelect:
'count(DISTINCT "target".type), count(DISTINCT "target".status)',
}, },
'distinct with alias': { 'distinct with alias': {
select: [{ field: 'type', isDistinct: true, alias: 'unique_type' }], select: [{ field: 'type', isDistinct: true, alias: 'unique_type' }],
@@ -129,7 +131,8 @@ module('Unit | Utility | sqlite-query', function (hooks) {
{ field: 'type', isDistinct: true, alias: 'unique_type' }, { field: 'type', isDistinct: true, alias: 'unique_type' },
{ field: 'status', isDistinct: true, alias: 'unique_status' }, { field: 'status', isDistinct: true, alias: 'unique_status' },
], ],
expectedSelect: 'DISTINCT "target".type as unique_type, "target".status as unique_status', expectedSelect:
'DISTINCT "target".type as unique_type, "target".status as unique_status',
}, },
'count distinct multiple fields': { 'count distinct multiple fields': {
select: [ select: [
@@ -1,17 +0,0 @@
/**
* Copyright IBM Corp. 2021, 2026
* SPDX-License-Identifier: BUSL-1.1
*/
import { helper } from '@ember/component/helper';
/**
* Takes a `Date` instance and returns a string formatted in human-friendly
* ISO-ish, e.g. "2020-01-01 00:00:00".
*/
export default helper(function formatDateIsoHuman(params /*, hash*/) {
return params[0]
?.toISOString()
.replace('T', ' ')
.replace(/\.\d*Z/, '');
});
@@ -1,14 +0,0 @@
/**
* Copyright IBM Corp. 2021, 2026
* SPDX-License-Identifier: BUSL-1.1
*/
import { helper } from '@ember/component/helper';
/**
* Takes a `Date` instance and returns a string formatted in ISO,
* e.g. "2020-01-01T00:00:00.999Z".
*/
export default helper(function formatDateIso(params /*, hash*/) {
return params[0]?.toISOString();
});
@@ -1,6 +0,0 @@
/**
* Copyright IBM Corp. 2021, 2026
* SPDX-License-Identifier: BUSL-1.1
*/
export { default } from 'core/helpers/format-date-iso-human';
@@ -1,6 +0,0 @@
/**
* Copyright IBM Corp. 2021, 2026
* SPDX-License-Identifier: BUSL-1.1
*/
export { default } from 'core/helpers/format-date-iso';
@@ -1,29 +0,0 @@
/**
* Copyright IBM Corp. 2021, 2026
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Helper | format-date-iso-human', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
this.set('inputValue', new Date('2020-01-01T00:00:00.999Z'));
await render(hbs`{{format-date-iso-human this.inputValue}}`);
assert.strictEqual(this.element.textContent.trim(), '2020-01-01 00:00:00');
});
test('it renders when null', async function (assert) {
this.set('inputValue', null);
await render(hbs`{{format-date-iso-human this.inputValue}}`);
assert.strictEqual(this.element.textContent.trim(), '');
});
});
@@ -1,32 +0,0 @@
/**
* Copyright IBM Corp. 2021, 2026
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Helper | format-date-iso', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
this.set('inputValue', new Date('2020-01-01T00:00:00.999Z'));
await render(hbs`{{format-date-iso this.inputValue}}`);
assert.strictEqual(
this.element.textContent.trim(),
'2020-01-01T00:00:00.999Z',
);
});
test('it renders when null', async function (assert) {
this.set('inputValue', null);
await render(hbs`{{format-date-iso this.inputValue}}`);
assert.strictEqual(this.element.textContent.trim(), '');
});
});
@@ -32,7 +32,7 @@ module('Integration | Helper | format-day-year', function (hooks) {
}); });
test('it renders when null', async function (assert) { test('it renders when null', async function (assert) {
this.set('inputValue', null); this.set('inputValue', null);
await render(hbs`{{format-date-iso this.inputValue}}`); await render(hbs`{{format-day-year this.inputValue}}`);
assert.strictEqual(this.element.textContent.trim(), ''); assert.strictEqual(this.element.textContent.trim(), '');
}); });
}); });
@@ -183,9 +183,10 @@
<B.Td> <B.Td>
{{#if data.created_time}} {{#if data.created_time}}
<Hds::Text::Body @color='faint'> <Hds::Text::Body @color='faint'>
<time datetime={{format-date-iso data.created_time}}> <Hds::Time
{{format-date-iso data.created_time}} @date={{data.created_time}}
</time> @display='friendly-local'
/>
</Hds::Text::Body> </Hds::Text::Body>
{{/if}} {{/if}}
</B.Td> </B.Td>
@@ -54,13 +54,10 @@
{{#if @model.channelRecording.start_time}} {{#if @model.channelRecording.start_time}}
<list.Item @icon='calendar' @color='faint'> <list.Item @icon='calendar' @color='faint'>
{{t 'form.started.label'}} {{t 'form.started.label'}}
<time <Hds::Time
datetime={{format-date-iso @display='friendly-local'
@model.channelRecording.start_time @date={{@model.channelRecording.start_time}}
}} />
>
{{format-date-iso @model.channelRecording.start_time}}
</time>
</list.Item> </list.Item>
{{/if}} {{/if}}
{{! Duration }} {{! Duration }}
@@ -216,13 +216,10 @@
{{#if @model.sessionRecording.created_time}} {{#if @model.sessionRecording.created_time}}
<list.Item @icon='calendar' @color='faint'> <list.Item @icon='calendar' @color='faint'>
{{t 'form.created.label'}} {{t 'form.created.label'}}
<time <Hds::Time
datetime={{format-date-iso @date={{@model.sessionRecording.created_time}}
@model.sessionRecording.created_time @display='friendly-local'
}} />
>
{{format-date-iso @model.sessionRecording.created_time}}
</time>
</list.Item> </list.Item>
{{/if}} {{/if}}
{{! Duration }} {{! Duration }}
@@ -257,32 +254,27 @@
{{#if @model.sessionRecording.retain_until}} {{#if @model.sessionRecording.retain_until}}
<list.Item @icon='hourglass' @color='faint'> <list.Item @icon='hourglass' @color='faint'>
{{t 'resources.policy.titles.retain_until'}} {{t 'resources.policy.titles.retain_until'}}
<time <div data-test-retain-until>
data-test-retain-until
datetime={{format-date-iso
@model.sessionRecording.retain_until
}}
>
{{#if @model.sessionRecording.retainForever}} {{#if @model.sessionRecording.retainForever}}
{{t 'resources.policy.titles.forever'}} {{t 'resources.policy.titles.forever'}}
{{else}} {{else}}
{{format-date-iso @model.sessionRecording.retain_until}} <Hds::Time
@date={{@model.sessionRecording.retain_until}}
@display='friendly-local'
/>
{{/if}} {{/if}}
</time> </div>
</list.Item> </list.Item>
{{/if}} {{/if}}
{{#if @model.sessionRecording.delete_after}} {{#if @model.sessionRecording.delete_after}}
<list.Item @icon='hourglass' @color='faint'> <list.Item @icon='hourglass' @color='faint'>
{{t 'resources.policy.titles.delete_after'}} {{t 'resources.policy.titles.delete_after'}}
<time <Hds::Time
data-test-delete-after data-test-delete-after
datetime={{format-date-iso @date={{@model.sessionRecording.delete_after}}
@model.sessionRecording.delete_after @display='friendly-local'
}} />
>
{{format-date-iso @model.sessionRecording.delete_after}}
</time>
</list.Item> </list.Item>
{{/if}} {{/if}}
</Rose::MetadataList> </Rose::MetadataList>
@@ -153,9 +153,10 @@
{{data.target.displayName}} {{data.target.displayName}}
</B.Td> </B.Td>
<B.Td> <B.Td>
<time datetime={{format-date-iso data.created_time}}> <Hds::Time
{{format-date-iso-human data.created_time}} @date={{data.created_time}}
</time> @display='friendly-local'
/>
</B.Td> </B.Td>
<B.Td> <B.Td>
<SessionStatus @model={{data}} /> <SessionStatus @model={{data}} />
@@ -29,8 +29,7 @@ export const MANAGE_DROPDOWN_HOST_CATALOG_DELETE =
export const FIELD_ROLE_ARN = '[name="role_arn"]'; export const FIELD_ROLE_ARN = '[name="role_arn"]';
export const FIELD_ROLE_ARN_VALUE = 'arn:aws:iam'; export const FIELD_ROLE_ARN_VALUE = 'arn:aws:iam';
export const FIELD_CREDENTIAL_TYPE = export const FIELD_CREDENTIAL_TYPE = '.credential-selection input:checked';
'.credential-selection input:checked';
export const FIELD_DYNAMIC_CREDENTIAL = '[name="dynamic-credential"]'; export const FIELD_DYNAMIC_CREDENTIAL = '[name="dynamic-credential"]';
export const FIELD_DYNAMIC_CREDENTIAL_VALUE = 'dynamic-credentials'; export const FIELD_DYNAMIC_CREDENTIAL_VALUE = 'dynamic-credentials';
export const FIELD_STATIC_CREDENTIAL = '[name="static-credential"]'; export const FIELD_STATIC_CREDENTIAL = '[name="static-credential"]';
@@ -11,6 +11,7 @@ import { setupSqlite } from 'api/test-support/helpers/sqlite';
import { faker } from '@faker-js/faker'; import { faker } from '@faker-js/faker';
import * as commonSelectors from 'admin/tests/helpers/selectors'; import * as commonSelectors from 'admin/tests/helpers/selectors';
import * as selectors from './selectors'; import * as selectors from './selectors';
import { formatDateUserFriendly } from 'admin/tests/helpers/format-date-user-friendly';
import { import {
STATE_SESSION_RECORDING_STARTED, STATE_SESSION_RECORDING_STARTED,
STATE_SESSION_RECORDING_AVAILABLE, STATE_SESSION_RECORDING_AVAILABLE,
@@ -31,6 +32,10 @@ module('Acceptance | session-recordings | list', function (hooks) {
'2020-01-01T00:01:00.000Z', '2020-01-01T00:01:00.000Z',
]; ];
const CREATED_TIME_VALUES_FORMATTED = CREATED_TIME_VALUES_ARRAY.map(
formatDateUserFriendly,
);
let featuresService; let featuresService;
let createdTimeValues; let createdTimeValues;
@@ -325,6 +330,8 @@ module('Acceptance | session-recordings | list', function (hooks) {
this.server.schema.sessionRecordings.all().destroy(); this.server.schema.sessionRecordings.all().destroy();
const expectedDescendingSort = CREATED_TIME_VALUES_ARRAY.toReversed(); const expectedDescendingSort = CREATED_TIME_VALUES_ARRAY.toReversed();
const expectedDescendingFormattedSort =
CREATED_TIME_VALUES_FORMATTED.toReversed();
faker.helpers.shuffle(expectedDescendingSort).forEach((value) => { faker.helpers.shuffle(expectedDescendingSort).forEach((value) => {
this.server.create('session-recording', { this.server.create('session-recording', {
created_time: value, created_time: value,
@@ -337,7 +344,7 @@ module('Acceptance | session-recordings | list', function (hooks) {
assert assert
.dom(commonSelectors.TABLE_ROWS) .dom(commonSelectors.TABLE_ROWS)
.isVisible({ count: CREATED_TIME_VALUES_ARRAY.length }); .isVisible({ count: CREATED_TIME_VALUES_ARRAY.length });
expectedDescendingSort.forEach((expected, index) => { expectedDescendingFormattedSort.forEach((expected, index) => {
// nth-child index starts at 1 // nth-child index starts at 1
assert.dom(commonSelectors.TABLE_ROW(index + 1)).containsText(expected); assert.dom(commonSelectors.TABLE_ROW(index + 1)).containsText(expected);
}); });
@@ -351,7 +358,7 @@ module('Acceptance | session-recordings | list', function (hooks) {
key: 'created_time', key: 'created_time',
values: CREATED_TIME_VALUES_ARRAY, values: CREATED_TIME_VALUES_ARRAY,
}, },
expectedAscendingSort: CREATED_TIME_VALUES_ARRAY, expectedAscendingSort: CREATED_TIME_VALUES_FORMATTED,
column: 1, column: 1,
}, },
'on state': { 'on state': {
@@ -18,6 +18,7 @@ import {
} from 'api/models/session'; } from 'api/models/session';
import * as commonSelectors from 'admin/tests/helpers/selectors'; import * as commonSelectors from 'admin/tests/helpers/selectors';
import * as selectors from './selectors'; import * as selectors from './selectors';
import { formatDateUserFriendly } from 'admin/tests/helpers/format-date-user-friendly';
import { setRunOptions } from 'ember-a11y-testing/test-support'; import { setRunOptions } from 'ember-a11y-testing/test-support';
module('Acceptance | sessions | list', function (hooks) { module('Acceptance | sessions | list', function (hooks) {
@@ -31,14 +32,9 @@ module('Acceptance | sessions | list', function (hooks) {
'2020-01-01T01:00:10.000Z', '2020-01-01T01:00:10.000Z',
'2020-01-01T10:00:00.000Z', '2020-01-01T10:00:00.000Z',
]; ];
const CREATED_TIME_VALUES_FORMATTED = CREATED_TIME_VALUES_ARRAY.map(
const CREATED_TIME_ISO_VALUES_ARRAY = [ formatDateUserFriendly,
'2020-01-01 00:00:01', );
'2020-01-01 00:00:10',
'2020-01-01 00:10:01',
'2020-01-01 01:00:10',
'2020-01-01 10:00:00',
];
const ID_VALUES_ARRAY = ['i_0001', 'i_0010', 'i_0100', 'i_1000', 'i_10000']; const ID_VALUES_ARRAY = ['i_0001', 'i_0010', 'i_0100', 'i_1000', 'i_10000'];
const instances = { const instances = {
@@ -485,7 +481,7 @@ module('Acceptance | sessions | list', function (hooks) {
}); });
this.server.schema.sessions.all().destroy(); this.server.schema.sessions.all().destroy();
const expectedDescendingSort = CREATED_TIME_ISO_VALUES_ARRAY.toReversed(); const expectedDescendingSort = CREATED_TIME_VALUES_FORMATTED.toReversed();
faker.helpers.shuffle(CREATED_TIME_VALUES_ARRAY).forEach((value) => { faker.helpers.shuffle(CREATED_TIME_VALUES_ARRAY).forEach((value) => {
this.server.create('session', { this.server.create('session', {
created_time: value, created_time: value,
@@ -520,7 +516,7 @@ module('Acceptance | sessions | list', function (hooks) {
key: 'created_time', key: 'created_time',
values: CREATED_TIME_VALUES_ARRAY, values: CREATED_TIME_VALUES_ARRAY,
}, },
expectedAscendingSort: CREATED_TIME_ISO_VALUES_ARRAY, expectedAscendingSort: CREATED_TIME_VALUES_FORMATTED,
column: 4, column: 4,
}, },
'on status': { 'on status': {
@@ -0,0 +1,22 @@
/**
* Copyright IBM Corp. 2021, 2026
* SPDX-License-Identifier: BUSL-1.1
*/
/**
* Formats a date string into a user-friendly date/time format.
* Example: Jan 1, 2024, 12:00:00 AM GMT
* @param {date string} value
* @returns
*/
export const formatDateUserFriendly = (value) =>
new Intl.DateTimeFormat('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: '2-digit',
second: '2-digit',
hour12: true,
timeZoneName: 'short',
}).format(new Date(value));
@@ -82,9 +82,7 @@
<:body as |B|> <:body as |B|>
<B.Tr> <B.Tr>
<B.Td> <B.Td>
<time datetime={{format-date-iso B.data.created_time}}> <Hds::Time @date={{B.data.created_time}} @display='friendly-local' />
{{format-date-iso-human B.data.created_time}}
</time>
</B.Td> </B.Td>
<B.Td> <B.Td>
<div class='link-copy-button-container'> <div class='link-copy-button-container'>
@@ -253,9 +253,10 @@
<:body as |B|> <:body as |B|>
<B.Tr> <B.Tr>
<B.Td> <B.Td>
<time datetime={{format-date-iso B.data.created_time}}> <Hds::Time
{{format-date-iso-human B.data.created_time}} @date={{B.data.created_time}}
</time> @display='friendly-local'
/>
</B.Td> </B.Td>
<B.Td> <B.Td>
<div class='link-copy-button-container'> <div class='link-copy-button-container'>