diff --git a/addons/api/addon/generated/models/channel-recording.js b/addons/api/addon/generated/models/channel-recording.js index 841930aac..65e1a044b 100644 --- a/addons/api/addon/generated/models/channel-recording.js +++ b/addons/api/addon/generated/models/channel-recording.js @@ -50,4 +50,16 @@ export default class GeneratedChannelRecordingModel extends BaseModel { readOnly: true, }) finished_time; + + @attr({ + description: 'The array of mime types that this recording will support.', + readOnly: true, + }) + mime_types; + + @attr('duration', { + description: 'The duration of this channel recording in ms.', + readOnly: true, + }) + duration; } diff --git a/addons/api/addon/generated/models/connection-recording.js b/addons/api/addon/generated/models/connection-recording.js index 867b20c91..e78f77b8b 100644 --- a/addons/api/addon/generated/models/connection-recording.js +++ b/addons/api/addon/generated/models/connection-recording.js @@ -12,6 +12,13 @@ import { attr } from '@ember-data/model'; export default class GeneratedConnectionRecordingModel extends BaseModel { // =attributes + @attr('string', { + description: + 'The ID of the Connection which this Connection Recording recorded.', + readOnly: true, + }) + connection_id; + @attr('number', { description: 'The total number of bytes uploaded from the client in this Connection.', @@ -44,4 +51,16 @@ export default class GeneratedConnectionRecordingModel extends BaseModel { readOnly: true, }) finished_time; + + @attr({ + description: 'The array of mime types that this recording will support.', + readOnly: true, + }) + mime_types; + + @attr('duration', { + description: 'The duration of this connection recording in ms.', + readOnly: true, + }) + duration; } diff --git a/addons/api/addon/generated/models/session-recording.js b/addons/api/addon/generated/models/session-recording.js index 234f5acb0..b52c7370b 100644 --- a/addons/api/addon/generated/models/session-recording.js +++ b/addons/api/addon/generated/models/session-recording.js @@ -80,4 +80,16 @@ export default class GeneratedSessionRecordingModel extends BaseModel { readOnly: true, }) delete_on; + + @attr({ + description: 'The array of mime types that this recording will support.', + readOnly: true, + }) + mime_types; + + @attr('duration', { + description: 'The duration of this session recording in ms.', + readOnly: true, + }) + duration; } diff --git a/addons/api/addon/transforms/duration.js b/addons/api/addon/transforms/duration.js new file mode 100644 index 000000000..153a46025 --- /dev/null +++ b/addons/api/addon/transforms/duration.js @@ -0,0 +1,40 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Transform from '@ember-data/serializer/transform'; + +export default class DurationTransform extends Transform { + /** + * Normalize a duration string in the format "3.0001s" to milliseconds. + * The backend returns a precision of nanoseconds but we round to whole + * milliseconds to avoid any issues with floating point math. + * @param serialized + * @returns {number} + */ + deserialize(serialized) { + if (!serialized) return 0; + const duration = serialized.split('s')[0]; + return Math.round(parseFloat(duration) * 1000); + } + + /** + * Serialize a duration in ms to a string in seconds. + * @param deserialized + * @returns {string} + */ + serialize(deserialized) { + if (!deserialized) return '0s'; + const durationInSeconds = deserialized / 1000; + + // We use `toLocaleString` to handle scenarios with very large numbers or + // very small numbers which will be represented as exponents. + // We force the max number of fraction digits to match the precision of the + // backend which is in nanoseconds. + return `${durationInSeconds.toLocaleString('fullwide', { + useGrouping: false, + maximumFractionDigits: 9, + })}s`; + } +} diff --git a/addons/api/app/transforms/duration.js b/addons/api/app/transforms/duration.js new file mode 100644 index 000000000..3c3dd0cc5 --- /dev/null +++ b/addons/api/app/transforms/duration.js @@ -0,0 +1 @@ +export { default } from 'api/transforms/duration'; diff --git a/addons/api/mirage/factories/channel-recording.js b/addons/api/mirage/factories/channel-recording.js index 3880e60d6..685b08115 100644 --- a/addons/api/mirage/factories/channel-recording.js +++ b/addons/api/mirage/factories/channel-recording.js @@ -8,6 +8,6 @@ import generateId from '../helpers/id'; import { TYPES_CHANNEL_RECORDING as types } from 'api/models/channel-recording'; export default factory.extend({ - id: () => generateId('srcc_'), + id: () => generateId('chr_'), type: (i) => types[i % types.length], }); diff --git a/addons/api/mirage/factories/connection-recording.js b/addons/api/mirage/factories/connection-recording.js index 13619fa22..148a7fedf 100644 --- a/addons/api/mirage/factories/connection-recording.js +++ b/addons/api/mirage/factories/connection-recording.js @@ -7,5 +7,6 @@ import factory from '../generated/factories/connection-recording'; import generateId from '../helpers/id'; export default factory.extend({ - id: () => generateId('src_'), + id: () => generateId('cr_'), + connection_id: () => generateId('sc_'), }); diff --git a/addons/api/mirage/generated/factories/channel-recording.js b/addons/api/mirage/generated/factories/channel-recording.js index 8c9602e57..75b0b0c5b 100644 --- a/addons/api/mirage/generated/factories/channel-recording.js +++ b/addons/api/mirage/generated/factories/channel-recording.js @@ -17,4 +17,6 @@ export default Factory.extend({ return faker.date.recent(3, this.finished_time); }, finished_time: () => faker.date.recent(), + duration: () => `${faker.datatype.number()}s`, + mime_types: () => ['application/x-asciicast'], }); diff --git a/addons/api/mirage/generated/factories/connection-recording.js b/addons/api/mirage/generated/factories/connection-recording.js index 3f9eb3452..17f7b1583 100644 --- a/addons/api/mirage/generated/factories/connection-recording.js +++ b/addons/api/mirage/generated/factories/connection-recording.js @@ -17,4 +17,5 @@ export default Factory.extend({ return faker.date.recent(3, this.finished_time); }, finished_time: () => faker.date.recent(), + duration: () => `${faker.datatype.number()}s`, }); diff --git a/addons/api/mirage/generated/factories/session-recording.js b/addons/api/mirage/generated/factories/session-recording.js index 731e3dc3b..07032faf3 100644 --- a/addons/api/mirage/generated/factories/session-recording.js +++ b/addons/api/mirage/generated/factories/session-recording.js @@ -21,4 +21,5 @@ export default Factory.extend({ }, updated_time: () => faker.date.recent(), deleted_on: () => faker.date.soon(), + duration: () => `${faker.datatype.number()}s`, }); diff --git a/addons/api/tests/unit/transforms/duration-test.js b/addons/api/tests/unit/transforms/duration-test.js new file mode 100644 index 000000000..35027c50e --- /dev/null +++ b/addons/api/tests/unit/transforms/duration-test.js @@ -0,0 +1,49 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Transform | duration', function (hooks) { + setupTest(hooks); + + test('it deserializes a duration string', function (assert) { + assert.expect(8); + const transform = this.owner.lookup('transform:duration'); + + const num1 = transform.deserialize('0s'); + const num2 = transform.deserialize(undefined); + const num3 = transform.deserialize('3s'); + const num4 = transform.deserialize('15.003s'); + const num5 = transform.deserialize('15.0035s'); + const num6 = transform.deserialize('15.0034s'); + const num7 = transform.deserialize('3.000000001s'); + const num8 = transform.deserialize('300000000001s'); + + assert.strictEqual(num1, 0); + assert.strictEqual(num2, 0); + assert.strictEqual(num3, 3000); + assert.strictEqual(num4, 15003); + assert.strictEqual(num5, 15004); + assert.strictEqual(num6, 15003); + assert.strictEqual(num7, 3000); + assert.strictEqual(num8, 300000000001000); + }); + + test('it serializes a duration number', function (assert) { + assert.expect(7); + const transform = this.owner.lookup('transform:duration'); + const num1 = transform.serialize(0); + const num2 = transform.serialize(undefined); + const num3 = transform.serialize(3000); + const num4 = transform.serialize(15003); + const num5 = transform.serialize(15000000000001); + const num6 = transform.serialize(0.000001); + const num7 = transform.serialize(0.0000001); + + assert.strictEqual(num1, '0s'); + assert.strictEqual(num2, '0s'); + assert.strictEqual(num3, '3s'); + assert.strictEqual(num4, '15.003s'); + assert.strictEqual(num5, '15000000000.001s'); + assert.strictEqual(num6, '0.000000001s'); + assert.strictEqual(num7, '0s'); + }); +});