{"id":15015934,"url":"https://github.com/elwayman02/ember-interactivity","last_synced_at":"2025-04-12T09:37:51.588Z","repository":{"id":57223902,"uuid":"116547918","full_name":"elwayman02/ember-interactivity","owner":"elwayman02","description":"Latency Tracking for Ember Applications","archived":false,"fork":false,"pushed_at":"2020-08-03T21:20:05.000Z","size":1784,"stargazers_count":33,"open_issues_count":40,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-02T07:37:40.348Z","etag":null,"topics":["addon","ember","ember-addon","emberjs","interactivity","latency","metrics"],"latest_commit_sha":null,"homepage":"http://jhawk.co/interactivity-demo","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elwayman02.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-01-07T07:56:20.000Z","updated_at":"2024-05-30T02:39:08.000Z","dependencies_parsed_at":"2022-08-30T02:10:32.001Z","dependency_job_id":null,"html_url":"https://github.com/elwayman02/ember-interactivity","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elwayman02%2Fember-interactivity","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elwayman02%2Fember-interactivity/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elwayman02%2Fember-interactivity/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elwayman02%2Fember-interactivity/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elwayman02","download_url":"https://codeload.github.com/elwayman02/ember-interactivity/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248547579,"owners_count":21122536,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["addon","ember","ember-addon","emberjs","interactivity","latency","metrics"],"created_at":"2024-09-24T19:48:10.574Z","updated_at":"2025-04-12T09:37:51.563Z","avatar_url":"https://github.com/elwayman02.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Ember Interactivity](docs/hero-logo.png)\n==========================================\n\n[![Netlify Status](https://api.netlify.com/api/v1/badges/233e41f6-8b57-4f43-9622-a772edf9f244/deploy-status)](https://app.netlify.com/sites/optimistic-booth-95742f/deploys)\n[![Build Status](https://travis-ci.org/elwayman02/ember-interactivity.svg?branch=master)](https://travis-ci.org/elwayman02/ember-interactivity)\n[![Ember Observer Score](https://emberobserver.com/badges/ember-interactivity.svg)](https://emberobserver.com/addons/ember-interactivity)\n[![Code Climate](https://codeclimate.com/github/elwayman02/ember-interactivity/badges/gpa.svg)](https://codeclimate.com/github/elwayman02/ember-interactivity)\n[![Greenkeeper badge](https://badges.greenkeeper.io/elwayman02/ember-interactivity.svg)](https://greenkeeper.io/)\n\nUsing Google's [RAIL model](https://developers.google.com/web/fundamentals/performance/rail#load), \nwe learn to focus on the more critical aspects of a page or component \nin order to improve the user's perception application speed. We define \n*Time to Interactivity* to be the time it takes for the user to perceive \nthat the application is ready for interaction.\n\nEmber Interactivity allows us to generate latency metrics tailored to this definition; \nspecifically, by identifying the critical components required to render a parent \nroute or component, we can track load times and identify bottlenecks that are \ncritical to the user experience. By focusing on perceived load times, we are \nable to reduce user bounce rates and churn through making the content appear to \nload faster. Some strategies for this involve adding placeholders for necessarily \nlong content wait times, but often there is plenty of low-hanging fruit to make \nactual improvements if we have the proper instrumentation to locate these issues.\n\nCheck out the [Demo](http://jhawk.co/interactivity-demo)!\n\nWant to see this addon used in a real application?\n\n[www.JordanHawker.com](https://www.jordanhawker.com/) \nis open-source, so you can see examples of how to use the features outlined below.\n\nTable of Contents\n------------------------------------------------------------------------------\n\n* [Installation](#Installation)\n* [Usage](#usage)\n    * [Routes](#routes)\n    * [Components](#components)\n    * [isInteractive](#isinteractive)\n    * [Beacons](#beacons)\n    * [Tracking](#tracking)\n    * [Timeline Marking](#timeline-marking)\n    * [Configuration](#configuration)\n    * [Testing](#testing)\n* [Contributing](#contributing)\n* [License](#license)\n\nInstallation\n------------------------------------------------------------------------------\n\n```\nember install ember-interactivity\n```\n\n\nUsage\n------------------------------------------------------------------------------\n\nEmber Interactivity requires developers to instrument routes and \ncritical components in order to report when they have completed rendering.\n\n### Routes\n\nThe `route-interactivity` mixin provides instrumentation for \nroute latency. This can be added to [all routes](https://github.com/elwayman02/jordan-hawker/blob/master/app/ext/route.js):\n\n```javascript\n// ext/route.js\nimport Route from '@ember/routing/route';\nimport RouteInteractivityMixin from 'ember-interactivity/mixins/route-interactivity';\n\nRoute.reopen(RouteInteractivityMixin);\n```\n\n```javascript\n// app.js\nimport './ext/route';\n```\n\nAlternatively, add the mixin only to the routes you want instrumented:\n\n```javascript\n// routes/foo.js\nimport Route from '@ember/routing/route';\nimport RouteInteractivityMixin from 'ember-interactivity/mixins/route-interactivity';\n\nexport default Route.extend(RouteInteractivityMixin);\n```\n\nBy default, routes will naively report that it is interactive by \nscheduling an event in the `afterRender` queue. The instrumentation \nwill take latency of the model hook into account, as well as any \ntop-level render tasks This is an easy, but relatively inaccurate \nmethod of instrumentation. It is only recommended for routes that \nare either low priority for instrumentation or render only basic \nHTML elements with no components.\n\nFor better instrumentation, read how to utilize the \n[isInteractive](#isInteractive) method.\n\nNote: The mixins in this addon rely on a number of lifecycle hooks, \nsuch as beforeModel \u0026 didTransition. If you have any issues sending events, \nplease make sure you are calling `this._super(...arguments)` in your app when \nutilizing these hooks.\n\n### Components\n\nThe `component-interactivity` mixin provides instrumentation for \ncomponent latency. This mixin should be added to all components that \nare required for a route to be interactive. For the most accurate data, \ninstrument each top-level component's critical children as well. Non-critical \ncomponents can also be instrumented to understand their own latency, \neven if they are not critical for a route or parent component to render.\n\nLike routes above, we can implement a [basic instrumentation strategy](https://github.com/elwayman02/jordan-hawker/blob/master/app/components/dj-bio.js#L10) \nvia the `afterRender` queue. If a component renders only basic HTML elements \nand does not depend on any asynchronous behavior to render, this is an ideal approach: \n\n```handlebars\n// templates/components/foo-bar.hbs\n\u003cp\u003eI am a basic template with no child components.\u003c/p\u003e\n```\n\n```javascript\n// components/foo-bar.js\nimport Component from '@ember/component';\nimport { run } from '@ember/runloop';\nimport ComponentInteractivity from 'ember-interactivity/mixins/component-interactivity';\n\nexport default Component.extend(ComponentInteractivity, {\n  didInsertElement() {\n    this._super(...arguments);\n    run.scheduleOnce('afterRender', this, this.reportInteractive);\n  }\n});\n```\n\nIf your component relies on asynchronous behavior (such as data loading), \nyou can delay your `afterRender` scheduling until after that behavior completes.\n\n```javascript\n// components/foo-bar.js\nimport Component from '@ember/component';\nimport { run } from '@ember/runloop';\nimport ComponentInteractivity from 'ember-interactivity/mixins/component-interactivity';\n\nexport default Component.extend(ComponentInteractivity, {\n  init() {\n    this._super(...arguments);\n    \n    this.loadData().then(() =\u003e {\n      run.scheduleOnce('afterRender', this, this.reportInteractive);\n    });\n  }\n});\n```\n\nFor components that rely on their child components to be interactive, \nread how to utilize the [isInteractive](#isInteractive) method.\n\n### isInteractive\n\nIn order to instrument latency more accurately, we define the list of \ncomponents we expect to report as interactive in order to complete \nthe critical rendering path of the route/component (known as the \"subscriber\"). \nThis is handled by implementing an [`isInteractive` method](https://github.com/elwayman02/jordan-hawker/blob/master/app/routes/music.js#L4-L6) \nin each subscriber. This method is passed a function that will tell you if a reporter is interactive.\n\n```javascript\n// routes/foo.js or components/foo-bar.js\nisInteractive(didReportInteractive) {\n  return didReportInteractive('first-component') \u0026\u0026 didReportInteractive('second-component');\n}\n```\n\nPass `didReportInteractive` the name of a component the subscriber renders \nthat is considered critical for interactivity. Once `isInteractive` \nreturns true, the relevant tracking events will be fired.\n\nIf you expect the subscriber to render multiple instances of the same component \n(e.g. an `#each` loop), you can [pass the expected number](https://github.com/elwayman02/jordan-hawker/blob/master/app/components/github-projects.js#L28-L30) \nto `didReportInteractive`:\n\n```javascript\n// routes/foo.js or components/foo-bar.js\nisInteractive(didReportInteractive) {\n  let count = this.get('someData.length');\n  return didReportInteractive('first-component', { count }) \u0026\u0026 didReportInteractive('second-component');\n}\n```\n\nIf there are multiple interactivity states to consider, simply add those \nconditions to `isInteractive`:\n\n```handlebars\n// templates/foo.hbs or templates/components/foo-bar.hbs\n{{if someState}}\n  {{first-component}}\n{{else}}\n  {{second-component}}\n{{/if}}\n```\n\n```javascript\n// routes/foo.js or components/foo-bar.js\nisInteractive(didReportInteractive) {\n  if (this.get('someState')) {\n    return didReportInteractive('first-component');\n  }\n  return didReportInteractive('second-component');\n}\n```\n\n### Beacons\n\nOften a template has multiple rendering states (e.g. a loading state), \nwhich may or may not render child components. If such a situation occurs, \nneither basic or complex instrumentation is a perfect fit. To address this, \nEmber Interactivity provides an `interactivity-beacon` component. These \nbeacons are simple components that you can append to the end of a template \nblock in order to time the rendering of that block.\n\nProvide the beacon with a `beaconId` to give it a unique identifier: \n\n```handlebars\n// routes/foo.js or components/foo-bar.js\n{{#if isLoading}}\n  \u003cp\u003eLoading...\u003c/p\u003e\n  {{interactivity-beacon beaconId='foo-loading'}}\n{{else}}\n  {{first-component}}\n  {{second-component}}\n{{/if}}\n```\n\nEach `beaconId` is prepended with 'beacon:' for use in `didReportInteractive`:\n\n```javascript\n// routes/foo.js or components/foo-bar.js\nisInteractive(didReportInteractive) {\n  if (this.get('isLoading')) {\n    return didReportInteractive('beacon:foo-loading');\n  }\n  return didReportInteractive('first-component') \u0026\u0026 didReportInteractive('second-component');\n}\n```\n\n### Tracking\n\nEmber Interactivity sends its events to the `interactivity-tracking` service. \nUse this interface to implement your own integration points for sending data \nto your favorite analytics service. For example, if you want to use [`ember-metrics`](https://github.com/poteto/ember-metrics) \nto send interactivity events to Mixpanel:\n\n```javascript\n// app/services/interactivity-tracking.js\nimport { inject as service } from '@ember/service';\nimport InteractivityTrackingService from 'ember-interactivity/services/interactivity-tracking';\n\nexport default InteractivityTrackingService.extend({\n  metrics: service(),\n\n  trackComponent(data) {\n    this.get('metrics').trackEvent('mixpanel', data);\n  }\n\n  trackRoute(data) {\n    this.get('metrics').trackEvent('mixpanel', data);\n  }\n});\n```\n\nThe interface is simple; it just passes through a data object for \nvarious events, and you can handle them however you like. All data will \ninclude an `event` name as detailed below; you can map these strings to \nwhatever names you prefer for sending to your analytics service.\n\n#### trackRoute\n\nThis method is called whenever a route interactivity event is triggered. \nThere are three possible events: `routeInitializing`, `routeActivating`, \u0026 `routeInitialized`\n\nThese events are useful for segmenting your route latency numbers to know \nif bottlenecks are caused by your APIs, the actual content rendering, or \nsome upstream app dependency (such as the CDN). Each `trackRoute` event \npasses the following base data:\n\n* event - The name of the event (e.g. `routeInitializing`)\n* clientTime - The time the event occurred, formatted as a Float\n* destination - The destination route for the transition\n* routeName - The name of the route this event belongs to\n* lostVisibility - Whether or not the app lost visibility\n\nWhen `routeName` and `destination` are the same, you are on a leaf route \n(as opposed to a parent route whose hooks trigger as part of the rendering process). \nBy default only leaf routes report interactivity, so while all routes will fire \n`routeInitializing` \u0026 `routeActivating` events, only leaf routes \n(or routes where `isInteractive` is defined) send `routeInitialized`.\n\n###### Visibility Tracking\n\nEmber Interactivity uses [`ember-is-visible`](https://github.com/elwayman02/ember-is-visible) \nto track if the document loses visibility while the route is loading. This is \nuseful because the browser may de-optimize loading some part of your application \nwhen a user switches tabs to another site. Using this data, we can identify events \nwhere latency numbers may be increased due to visibility loss, as well as \ntrack user behavior to know if they are frequently moving away from the site \nwhile waiting for it to load.  \n\n##### routeInitializing\n\nThis event is called from the `beforeModel` hook of your route and \nindicates the beginning of each route's loading phases.\n\n##### routeActivating\n\nThis event is called when the `activate` hook is triggered, after the model hooks complete. \nThis is the point at which the route will begin scheduling its rendering tasks.\n\n##### routeInteractive\n\nThis event is called when the route reports itself as interactive, per the definitions \noutlined above. In addition to the base data, two additional properties are added to this event:\n\n* isAppLaunch - Boolean indicating if the app is launching for first time \nor if this is a transition from another route.\n* timeElapsed - This indicates the time (in milliseconds) that the route \ntook to become interactive since the initial browser fetch. Only included \nif `isAppLaunch` is true.\n\n`timeElapsed` is usually your primary data point for tracking the load times of your routes.\n\n#### trackComponent\n\nThis method is called whenever a component interactivity event is triggered. \nThere are two possible events: `componentInitializing` \u0026 `componentInteractive`\n\nEvent data contains the following properties:\n\n* event - The name of the event (e.g. `componentInteractive`)\n* clientTime - The time the event occurred, formatted as a Float\n* component - The name of the component\n* componentId - A unique id for the component (to differentiate instances of the same component)\n\nThe `componentInteractive` event adds an additional property:\n\n* timeElapsed - This indicates the time (in milliseconds) that the component \ntook to become interactive since it began initializing. \n(Essentially subtracting the clientTimes for the two events)\n\n##### isComponentInstrumentationDisabled\n\nThis method allows you to control whether components are instrumented in the application. \nBy default, it reads the configuration property [`tracking.disableComponents`](#Configuration), \nbut you can override the method to add custom logic for when to disable instrumentation.\n\n#### trackError (_Experimental_)\n\nThis method is called whenever an error occurs in Ember Interactivity. \nCurrently, no data is sent along with an error; please file issues if you \nhave requests for data to include! `trackError` is only hooked up for routes \nat the moment, such as when a user has transitioned away from the route before completion.\n\n### Timeline Marking\n\nEmber Interactivity automatically marks each route/component using the \n[Performance Timeline](https://developer.mozilla.org/en-US/docs/Web/API/Performance_Timeline/Using_Performance_Timeline) \nstandard. DevTools such as the [Chrome Timeline](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/reference) \ncan display the timings for easy visualization of the critical rendering waterfall. \nThis can help developers identify bottlenecks for optimizing time to interactivity.\n\n![Component Waterfall](docs/waterfall.png)\n\nNote: It's important to realize that in some cases, components you may not have\nconsidered to be critical are creating rendering bottlenecks in your application. \nLook for suspicious gaps in the rendering visualization to identify these situations.\n\n### Configuration\n\nDevelopers can toggle individual features of Ember Interactivity by \nadding an `interactivity` object to their application's environment config. \nThis can be useful if you only want features run in certain environments, \nor if you want to sample a percentage of your users to stay within data storage limits.\n\nThree features can be configured:\n\n* `instrumentation` - Toggle instrumentation altogether (Note: Does not support leaf/parent configs below)\n* `timelineMarking` - Toggle marking the performance timeline\n* `tracking` - Toggle sending tracking events\n\nEach feature can be configured for four subsets of the addon:\n\n* `disableComponents` - Set true to disable for all components\n* `disableLeafComponents` - Set true to disable for child components \n(those that do not implement `isInteractive`). This is useful if you \nonly want a feature enabled for subscribers (parent routes/components).\n* `disableRoutes` - Set true to disable for all routes\n* `disableParentRoutes` - Set true to disable for all non-leaf routes \n(those that are not the target of a transition). This is useful if you \naren't trying to identify bottlenecks in your route chain and just want \nto collect latency numbers for each transition.\n\n```javascript\n// config/environment.js\nmodule.exports = function (environment) {\n  let ENV = {\n    interactivity: {\n      tracking: {\n        disableLeafComponents: true\n      },\n      timelineMarking: {\n        disableRoutes: true\n      }\n    }\n  };\n  return ENV;\n};\n```\n\n#### Overrides\n\nTODO: Per-instance Overrides\n\n### Testing\n\nEmber Interactivity provides a number of test helpers to support testing your application's latency instrumentation.\n\n#### Mock Services\n\nMock service instances are provided for your use. It is recommended to \nregister these mock services in each of the tests of your application.\n\n```javascript\nimport MockInteractivityService from 'ember-interactivity/test-support/mock-interactivity-service';\nimport MockInteractivityTrackingService from 'ember-interactivity/test-support/mock-interactivity-tracking-service';\n\nmodule('foo', 'Integration | Component | foo', function (hooks) {\n  setupRenderingTest(hooks);\n\n  hooks.beforeEach(function () {\n    this.owner.register('service:interactivity', MockInteractivityService);\n    this.owner.register('service:interactivity-tracking', MockInteractivityTrackingService);\n  });\n});\n```\n\nTo avoid writing this for every test in your application, you can write \na wrapper around `module` that handles registering any mock services for your tests.\n\n#### Interactivity Assertions\n\nThe `assert-interactivity` helper provides methods to test that your routes/components \nare correctly reporting latency events when rendering. As your tests exercise \nthese modules, these assertions will confirm the interactivity events get sent. \nThis helper relies on the `MockInteractivityService` being registered.\n\nFirst, make the assertion available to your tests:\n\n```javascript\n// tests/test-helper.js\nimport 'ember-interactivity/test-support/assert-interactivity';\n```\n\nThen, use the `trackInteractivity` assertion in your tests for routes and component subscribers:\n\n```javascript\n// tests/acceptance/foo.js\nimport { module, test } from 'qunit';\nimport { click, fillIn, visit } from '@ember/test-helpers';\nimport { setupApplicationTest } from 'ember-qunit';\n\nmodule('Acceptance | foo', function (hooks) {\n  setupApplicationTest(hooks);\n\n  test('should report interactive', async function (assert) {\n    await visit('/foo');\n    assert.trackInteractivity('foo');\n  });\n});\n```\n\nLet's say you want to simulate some async behavior and make sure interactivity \nconditions aren't being fulfilled prematurely. The `trackNonInteractivity` \nassertion can be used to test this scenario:\n\n```javascript\n// tests/acceptance/foo.js\nimport { module, test } from 'qunit';\nimport { click, fillIn, visit } from '@ember/test-helpers';\nimport { setupApplicationTest } from 'ember-qunit';\n\nmodule('Acceptance | foo', function (hooks) {\n  setupApplicationTest(hooks);\n  \n  hooks.beforeEach(function () {\n    this.resolveAsyncBehavior = () =\u003e {\n      // Do stuff to resolve interactivity conditions\n    };\n  });\n\n  test('should report interactive', async function (assert) {\n    await visit('/foo');\n    assert.trackNonInteractivity('foo');\n    this.resolveAsyncBehavior();\n    assert.trackInteractivity('foo');\n  });\n});\n```\n\nContributing\n------------------------------------------------------------------------------\n\nWe adhere to the [Ember Community Guidelines](https://emberjs.com/guidelines/) for our Code of Conduct.\n\n[![Powered By Netlify](https://www.netlify.com/img/global/badges/netlify-light.svg)](https://www.netlify.com)\n\n### Installation\n\n* `git clone https://www.github.com/elwayman02/ember-interactivity.git`\n* `cd ember-interactivity`\n* `yarn install`\n\n### Linting\n\n* `yarn lint:js`\n* `yarn lint:js --fix`\n\n### Running tests\n\n* `ember test` – Runs the test suite on the current Ember version\n* `ember test --server` – Runs the test suite in \"watch mode\"\n* `ember try:each` – Runs the test suite against multiple Ember versions\n\n### Running the dummy application\n\n* `ember serve`\n* Visit the dummy application at [http://localhost:4200](http://localhost:4200).\n\nFor more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/).\n\nLicense\n------------------------------------------------------------------------------\n\nThis project is licensed under the [MIT License](LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felwayman02%2Fember-interactivity","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felwayman02%2Fember-interactivity","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felwayman02%2Fember-interactivity/lists"}