Skip to content
Snippets Groups Projects
single.js 5.91 KiB
Newer Older
import Ember from 'ember';

Mathis Neumann's avatar
Mathis Neumann committed
/**
 * Loads all necessary data for any visualisation types
 *
 * @class SingleDeploymentsRoute
 * @extends {Ember.Route}
 * @module routes
Mathis Neumann's avatar
Mathis Neumann committed
 * @public
Mathis Neumann's avatar
Mathis Neumann committed
 */
export default Ember.Route.extend({
  session: Ember.inject.service(), // loads services/session.js
Mathis Neumann's avatar
Mathis Neumann committed
  changelogStream: Ember.inject.service(),
Mathis Neumann's avatar
Mathis Neumann committed

  /**
   * prepares data for route which will be passed to the controller
   *
   * @method model
   * @param  {Object} params, handled by Embers routing
   * @return {Promise|Object} Object containing the systemId, revision, mode (deployments/architectures) and the instances
   * @public
   */
  model(params) {
Mathis Neumann's avatar
Mathis Neumann committed
    const systemId = params.systemId;
    return this.loadSystem(systemId)
        .then(this.loadRevision.bind(this))
        .then((revision) => {
            return Ember.RSVP.resolve()
                .then(this.loadMetaModel.bind(this))
                .then(models => this.verifyRevision(revision, models))
                .then((models) => {
                    this.debug('loaded models', models);
                    return {
                        systemId: systemId,
                        revision: revision,
                        instances: models,
                        mode: this.get('routeName').split('.')[0] // deployments/architectures
                    };
                });
        })
        .catch(err => {
            if(err === 'outdated') {
                // wait a bit to avoid DDOS, TODO: exponential backoff?
                this.refresh();
            } else {
                console.error('could not load models', err);
            }
            throw err; // TODO: handle errors in UI
        });
  },
Mathis Neumann's avatar
Mathis Neumann committed
  /**
   * load the current system record
   * @param  {String} systemId id of the system (from URL)
   * @return {Promise|SystemRecord}
   * @public
   */
  loadSystem(systemId) {
Mathis Neumann's avatar
Mathis Neumann committed
    return this.store.findRecord('system', systemId) // cached if user comes via navigation
        .then((system) => {
            this.debug('loaded system', system);
            this.set('session.system', system); // add the system to all requests
            const changelogStream = this.get('changelogStream'); // lazy loaded, requires session id
            changelogStream.connect(system.id);
            return system;
        });
  },
Mathis Neumann's avatar
Mathis Neumann committed
  /**
   * Loads the current revision from API (without caching)
   * @param  {SystemRecord}
   * @return {Object} API response, containing changelogSequence, revisionNumber and the lastUpdated date
   * @public
   */
  loadRevision(system) {
    return system.getRevision();
  },
Mathis Neumann's avatar
Mathis Neumann committed

  /**
   * note that findAll returns an Observable Array which automatically
   * update whenever new records are pushed into the store.
   * The controller can observe this.
   * Also note that since we changed the behavior of findAll() to use the systemId
   * Ember will probably also update for other systems. These are filtered in the controller.
   *
   * We also load all the data, so that the transformation strategies can assume that the whole
   * meta model is cached. This also allowes that the architecture view is only an alias
   *
   * @method loadMetaModel
   * @return {Promise|Object} Promise for an object containing the model records in lists, where the keys are the pluralized model names
   */
  loadMetaModel() {
    const load = (type) => this.store.findAll(type);
    return Ember.RSVP.hash({
        nodes: load('node'),
        nodeGroups: load('nodegroup'),
        services: load('service'),
        serviceInstances: load('serviceinstance'),
        communications: load('communication'),
        communicationInstances: load('communicationinstance')
Mathis Neumann's avatar
Mathis Neumann committed
  /**
   * checks whether any of the loaded instances from the API where created while the requests still in progress.
   *
   * @method verifyRevision
   * @param  {Object} revision Revision Object directly from the API (no Ember record, but plain Object)
   * @param  {Object} models   Object with all instances of a model, keys are the pluralized identiefers
   * @return {Promise|Object} promise that is rejected with 'outdated' if any instance is newer than expected, otherwise resolves models
   */
  verifyRevision(revision, models) {
    const outdatedRecords = Object
        .keys(models)
        .map(key => models[key])
        .filter(instances => {
            return instances.filter(record =>
                record.get('revisionNumber') >= revision.revisionNumber && record.get('changelogSequence') > revision.changelogSequence
            ).length > 0;
        });
    if(outdatedRecords.length > 0) {
        this.debug('records loaded from server seem to have changed during loading, refreshing data!', revision, models);
        return Ember.RSVP.Promise.reject('outdated');
    }

    return models;
  },
  actions: {
    loadDetails(rawEntity) {
        this.debug('loadDetails action', rawEntity);
        const entityType = rawEntity.type.toLowerCase();
        const entityId = rawEntity.id;

        /* I would love to not generate the url first, but there seem to be unknown (to me) assumptions about
        * passing object parameters to transitionTo which break with the current path variables.
        * Otherwise this would use transitionTo('deployments.single.details', {...})
        const url = this.router.generate(`${this.get('routeName')}.details`, { // use routeName to support architectures alias
            systemId: this.get('session.systemId'),
            entityType,
            entityId
        });
        this.replaceWith(url);
    },
    backToSystem() {
        this.replaceWith(this.get('routeName'));
    willTransition(transition) {
        this.debug('transition', transition.targetName, this.get('routeName'));
        // do not disconnect if transitioning to a child route (details)
        if (transition.targetName.indexOf(this.get('routeName')) !== 0) {
            this.get('changelogStream').disconnect();
            clearTimeout(this.get('refreshTimeout'));