Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
"overrides": {
"nomnom>underscore": "^1.12.1",
"@hashicorp/design-system-components>ember-stargate": "^0.6.0",
"@hashicorp/ember-asciinema-player>asciinema-player": "3.4.0",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We previously had to lock in on this version because we relied on specific internal DOM structure and classes that changed after 3.4.0. With this PR we can remove this but still need to ensure that our dependency brought in within admin UI is 3.4.0. When we upgrade to a newer version we'll have make sure our additional css styles still apply

"micromatch": "^4.0.8",
"css-select>nth-check": "^2.0.1",
"node-gyp": "^10.0.0",
Expand Down
26 changes: 3 additions & 23 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}

<div ...attributes {{this.initializePlayer data=@data}}></div>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seemed better to bring in the @data through the element modifier args than reference this.args in the modifier logic although in both cases this.args.* usage should be tracked, this way is a bit more explicit as a dependency input to the modifier function

Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import Component from '@glimmer/component';
import * as AsciinemaPlayer from 'asciinema-player';
import { modifier } from 'ember-modifier';

export default class SessionRecordingPlayerAsciinemaPlayerComponent extends Component {
// =properties

/**
* @type {?AsciinemaPlayer}
*/
player = null;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I set up a modified test where the data arg was set, and then 4 seconds later set with a new cast input. This change was required otherwise ember detected that this.player is being read and set in the same autotracking routing which it errors could cause infinite render loops. This is because the modifier dispose function sets a this.player = null and then a new this.player is being set.

In reality though this can all be avoided because player doesn't need to be @tracked. Render does not need to know when it changes to update something it is responsible for rendering. Everything within the div passed to AsciinemaPlayer is managed by that library


/**
* Options of the underlying AsciinemaPlayer supported by this component,
* which may be passed as named arguments to the player.
* @type {string[]}
* @see https://github.com/asciinema/asciinema-player#options
*/
supportedOptions = new Array(
'autoPlay',
'loop',
'startAt',
'speed',
'idleTimeLimit',
'theme',
'poster',
'fit',
'controls',
'markers',
'pauseOnMarkers',
);

/**
* An object of options where each possible key from `supportOptions` is
* included if and only if its associated value was passed to the component
* as an argument.
*
* E.g. `@autoPlay={{true}} @fit='both'` results in
* the options object `{autoPlay: true, fit: 'both'}`.
* @type {object}
* @see https://github.com/asciinema/asciinema-player#options
*/
get options() {
return this.supportedOptions.reduce((obj, key) => {
return this.args?.[key] !== undefined
? { ...obj, [key]: this.args[key] }
: obj;
}, {});
}

/**
* Creates an AsciinemaPlayer within the passed `containerElement`.
*/
initializePlayer = modifier((containerElement, _, { data }) => {
if (!data) return;
this.player = AsciinemaPlayer.create(
{ data },
containerElement,
this.options,
);

return () => {
this.player?.dispose();
this.player = null;
};
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
{{yield}}
{{else}}
<div class='session-recording-player-theme'>
<Heap::Player @data={{@asciicast}} @poster='npt:1:30' />
<SessionRecording::Player::AsciinemaPlayer
@data={{@asciicast}}
@poster='npt:1:30'
/>
</div>
{{/if}}
</div>
2 changes: 1 addition & 1 deletion ui/admin/app/styles/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

@use 'rose';
@use 'notify';
@use '@hashicorp/ember-asciinema-player';
@use 'asciinema-player/dist/bundle/asciinema-player.css';

@use 'ember-basic-dropdown';
@use 'ember-power-select';
Expand Down
2 changes: 1 addition & 1 deletion ui/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
},
"dependencies": {
"@babel/runtime": "7.27.6",
"asciinema-player": "3.4.0",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ember-named-blocks-polyfill": "^0.2.5",
"lodash": "^4.17.21",
"uuid": "^11.0.3"
Expand All @@ -55,7 +56,6 @@
"@faker-js/faker": "^8.0.2",
"@glimmer/component": "^2.0.0",
"@glimmer/tracking": "^1.1.2",
"@hashicorp/ember-asciinema-player": "https://github.com/hashicorp/ember-asciinema-player.git#e047a096039cff70234c232efe75dcad74c6358a",
"@warp-drive/build-config": "^0.0.2",
"broccoli-asset-rev": "^3.0.0",
"concurrently": "^9.1.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import { module, test } from 'qunit';
import { render, waitUntil } from '@ember/test-helpers';
import { setupRenderingTest } from 'admin/tests/helpers';
import { hbs } from 'ember-cli-htmlbars';

module(
'Integration | Component | session-recording/player/asciinema-player',
function (hooks) {
setupRenderingTest(hooks);

test('it renders', async function (assert) {
assert.expect(1);

const asciicast = await fetch('/session.cast');
Copy link
Collaborator Author

@hashicc hashicc Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our app we have the same cast file as this test was using in the repo, it's just under a different filename

const asciicastContent = await asciicast.text();
this.set('data', asciicastContent);

await render(
hbs`<SessionRecording::Player::AsciinemaPlayer @data={{this.data}} @poster='npt:1:30' />`,
);
// AsciinemaPlayer does not come with a "ready" event, and its
// initialization is async. Therefore tests must `waitUntil` the expected
// DOM state is reached.
await waitUntil(() =>
assert
.dom('.ap-player')
.hasAnyText('ember-asciinema-player git:(main*)'),
);
});
},
);
Loading