Smart View not automatically loading BelongsTo data

I have a smart view and some data that I’m trying to iterate over, but Ember is not loading the belongsTo data automatically.

I have two models, Beacon and TalentFilter and the beacon has a relationship like this:

    Beacon.belongsTo(models.TalentFilter, {
      foreignKey: {
        name: 'talentFilterIdKey',
        field: 'TalentFilterID'
      },
      target: {
        name: 'ID',
        field: 'ID'
      },
      as: 'TalentFilter'
    });

In ember, I’m trying to do:

{{record.forest-TalentFilter.forest-paySchedule}}

but it’s blank.

After digging around, this works:

{{record.forest-TalentFilter.id}}

which tells me the ID is there, but the data is not being automatically loaded.

WHEN I APPLY THE VIEW TO THE ACTUAL TABLE, IT WORKS.

Meaning, this is only happening in the editor!

EDIT: It’s not just happening in the editor, it’s sportatic and I sometimes randomly it’ll get stuck in a state where no associated records are fetched.

Expected behavior

The related data should be automatically loaded, but only the ID is present.

Actual behavior

Only the ID is present.

Failure Logs

Please include any relevant log snippets, if necessary.

Context

Please provide any relevant information about your setup.

  • Package Version:
  • Express Version:
  • Sequelize Version:
  • Database Dialect:
  • Database Version:
  • Project Name:

Hi @austinrupie,

Thank you for reaching us with your issue.

I just tried to replicate it on my end and I haven’t been able to do so, all my belongsTo fields are accessible from my records both on the preview and the table view of my smart view.

If I understand you well, you’re facing this belongs to fields access issues only on the preview tool of the smart view and not on any access to it, just random ones right?

Also could you give me the field type of the paySchedule you are trying to display?

Thank you

Hey there!

Sent you a private DM with a loom video showing you the behavior.

Is there a way I can force-fetch associations instead of relying on this buggy mechanism?

I need a way to get around this bug.

Hello @austinrupie,

I tested on my side with a collection that has a belongsTo relationship, and it seems to work properly on my personal projects, so I guess that there is something special with yours that I did not notice.

In the example you shared, we can see that users are not properly loaded, but I’m not sure to understand where they are supposed to come from, looking at the code you shared. So, to sum everything up, can you share more details with us, about all the models that are involved in this smart view?

I would need :

  • the SQL Table declaration of the involved collections (only the relevant fields)
  • the Sequelize declaration of these collections
  • A simplified version of your template, to see how you access the data from a list of beacons

It’ll help a lot reproducing your issue.

Beacon model:

// This model was generated by Lumber. However, you remain in control of your models.
// Learn how here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/models/enrich-your-models
module.exports = (sequelize, DataTypes) => {
  const { Sequelize } = sequelize;
  // This section contains the fields of your model, mapped to your table's columns.
  // Learn more here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/models/enrich-your-models#declaring-a-new-field-in-a-model
  const Beacon = sequelize.define('Beacon', {
    id: {
      type: DataTypes.INTEGER,
      field: 'ID',
      primaryKey: true,
      autoIncrement: true
    },
    talentQueueId: {
      type: DataTypes.INTEGER,
      field: 'TalentQueueID',
    },
    createdAt: {
      type: DataTypes.DATE,
      defaultValue: Sequelize.literal('now()')
    },
    updatedAt: {
      type: DataTypes.DATE,
      defaultValue: Sequelize.literal('now()')
    },
    deletedAt: {
      type: DataTypes.DATE
    }
  }, {
    tableName: 'Beacon',
    schema: process.env.DATABASE_SCHEMA
  });

  // This section contains the relationships for this model. See: https://docs.forestadmin.com/documentation/v/v6/reference-guide/relationships#adding-relationships.
  Beacon.associate = models => {

    Beacon.belongsTo(models.TalentQueue, {
      foreignKey: {
        name: 'talentQueueIdKey',
        field: 'TalentQueueID',
      },
      target: {
        name: 'ID',
        field: 'ID',
      },
      as: 'TalentQueue',
    });

  };

  return Beacon;
};

TalentQueue model

// This model was generated by Lumber. However, you remain in control of your models.
// Learn how here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/models/enrich-your-models
module.exports = (sequelize, DataTypes) => {
  const { Sequelize } = sequelize;
  // This section contains the fields of your model, mapped to your table's columns.
  // Learn more here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/models/enrich-your-models#declaring-a-new-field-in-a-model
  const TalentQueue = sequelize.define('TalentQueue', {
    id: {
      type: DataTypes.INTEGER,
      field: 'ID',
      primaryKey: true,
      autoIncrement: true
    },
    createdAt: {
      type: DataTypes.DATE,
      defaultValue: Sequelize.literal('now()')
    },
    updatedAt: {
      type: DataTypes.DATE,
      defaultValue: Sequelize.literal('now()')
    },
    deletedAt: {
      type: DataTypes.DATE
    }
  }, {
    tableName: 'TalentQueue',
    schema: process.env.DATABASE_SCHEMA
  });

  // This section contains the relationships for this model. See: https://docs.forestadmin.com/documentation/v/v6/reference-guide/relationships#adding-relationships.
  TalentQueue.associate = models => {
    TalentQueue.hasMany(models.Beacon, {
      foreignKey: {
        name: 'talentQueueIdKey',
        field: 'TalentQueueID'
      },
      target: {
        name: 'ID',
        field: 'ID'
      },
      as: 'Beacon'
    });

    TalentQueue.hasMany(models.TalentQueuexUser, {
      foreignKey: {
        name: 'TalentQueueID',
        field: 'TalentQueueID'
      },
      sourceKey: 'id',
      as: 'TalentQueuexUsers'
    });

    TalentQueue.belongsToMany(models.User, {
      as: 'Users',
      through: 'TalentQueuexUser',
      foreignKey: 'TalentQueueID',
      otherKey: 'UserID'
    });

  };

  return TalentQueue;
};

TalentQueuexUser model:

  const TalentQueuexUser = sequelize.define('TalentQueuexUser', {
    id: {
      type: DataTypes.INTEGER,
      field: 'ID',
      primaryKey: true,
      autoIncrement: true
    },
    userId: {
      type: DataTypes.INTEGER,
      field: 'UserID'
    },
    talentQueueId: {
      type: DataTypes.INTEGER,
      field: 'TalentQueueID'
    },
    createdAt: {
      type: DataTypes.DATE,
      defaultValue: Sequelize.literal('now()')
    },
    updatedAt: {
      type: DataTypes.DATE,
      defaultValue: Sequelize.literal('now()')
    },
    deletedAt: {
      type: DataTypes.DATE
    }
  }, {
    tableName: 'TalentQueuexUser',
    schema: process.env.DATABASE_SCHEMA
  });

  // This section contains the relationships for this model. See: https://docs.forestadmin.com/documentation/v/v6/reference-guide/relationships#adding-relationships.
  TalentQueuexUser.associate = models => {
    TalentQueuexUser.belongsTo(models.TalentQueue, {
      as: 'TalentQueue',
      foreignKey: {
        name: 'TalentQueueID',
        field: 'TalentQueueID'
      },
      target: {
        name: 'ID',
        field: 'ID'
      }
    });

    TalentQueuexUser.belongsTo(models.User, {
      foreignKey: {
        name: 'UserID',
        field: 'UserID'
      },
      target: {
        name: 'ID',
        field: 'ID'
      },
      as: 'User'
    });
  };

  return TalentQueuexUser;
};

Using the typical @record and @each pattern:

No length in the ember data object:

No length in the smart view query fetch:

the talent queue shows associated users:

I may need some premium support on this one, really making me scratch my head.

It seems like the serializer isn’t grabbing the associated data in the route.

Hi @austinrupie !
Could you share your component code ?

Very simple

<div class="c-smart-view">
  <div class="c-smart-view__content">
    <span class="c-smart-view_icon fa fa-{{@collection.iconOrDefault}} fa-5x"></span>
    <h1>
      {{@collection.pluralizedDisplayName}}
      ({{@recordsCount}})
    </h1>
    <p>
       {{#each @records as |record|}}
         <!-- this is always 0 (unless I load the records from another table, then suddenly they appear) -->
         {{record.forest-TalentQueue.forest-TalentQueuexUsers.length}}
          {{#each record.forest-TalentQueue.forest-TalentQueuexUsers as |talent|}}
            {{talent.id}}
          {{/each}}
          {{record.forest-SuggestedTalent.length}}
          {{#each record.forest-SuggestedTalent as |suggestedTalent|}}
            {{suggestedTalent.forest-User.forest-firstName}}
          {{/each}}
       {{/each}}
    </p>
  </div>
</div>

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { triggerSmartAction, deleteRecords, getCollectionId, loadExternalStyle, loadExternalJavascript } from 'client/utils/smart-view-utils';

export default class extends Component {
  @action
  triggerSmartAction(...args) {
    return triggerSmartAction(this, ...args);
  }

  @action
  deleteRecords(...args) {
    return deleteRecords(this, ...args);
  }
}

I give you guys full permission to log into our production project and test this, btw

Hello Austin,

I am looking into your problem. Just to reassure you with the information provided we do not have access to your project or any of your personal data. :slight_smile:
To make it easier for me to investigate / reproduce your problem, could you share with me a SQL dump (in a private message for example) of the concerned tables?

Thanks

DM’d you with the data

Thanks, I can reproduce your problem and as you can see, you can’t get the related data of the related data of a record from a smart view. However you could see them after browsing the table in question because ember-data keeps them cached once loaded.

So to implement what you want to do I see two possibilities, rewrite the GET of the associated route directly or modify the ember component of the smart view editor so that it also fetches the related data associated to your record.

Hope this helps, :slight_smile:

Can you give me examples of both?

I couldn’t get the re-fetch in Ember to work and I couldn’t get the GET override to work either (because of the serializer)

Also, do you know this issue is happening? Is it a bug on the Forest side?

This is a pretty pressing matter for our business. I’d love some additional thoughts and support around this.

Hi @austinrupie,

As @lclisson said previously, today we just do not support accessing related data of the related data of a record from a smart view.

Now I am both creating a product board entry to ask this feature to be implemented.
Also, I am asking my teammates if we have some tips to help you implement this now.

Regards

1 Like

Thank you. I’m looking forward to getting a solution to this.

I’m doing other belongsTo fetching of other related data elsewhere, and It’s working – so I’m still a little confused why this isn’t working.

Hi @austinrupie,

Sadly, as @Sliman_Medini & @lclisson stated, fetching belongsTo of belongsTo is not something we support right now, and the feature request has been pushed to our product board.

For now, only belongsTo data are fetch during the retrieval of the records for the smart view. From time to time, it is possible that data that were already fetched during previous navigation in the app are still accessible in the smart view editor/table view due to ember-data storing the previous request result locally.

However, this is something achievable by manually doing the request using the store service. Keep in mind that this is not something we do support out of the box & this is something that might change in the future.

import Component from '@glimmer/component';
import { action, computed } from '@ember/object';
import { triggerSmartAction, deleteRecords } from 'client/utils/smart-view-utils';
import { inject as service } from '@ember/service';
import DS from 'ember-data';

export default class extends Component {
  @service store;

  @computed('args.records')
  get formattedRecords() {
    if (!this.args.records) return [];

    const records = this.args.records;

    const transactionsQueryConditions = records.reduce((acc, record) => {
      if (typeof (record['forest-customer'].id) !== 'undefined') {
        acc.push({
          field: 'emitter_customer_id',
          operator: 'equal',
          value: record['forest-customer'].id,
        });
      }
      return acc;
    }, []);

    const params = {
      filters: JSON.stringify({
        aggregator: 'or',
        conditions: transactionsQueryConditions,
      }),
      timezone: 'America/Los_Angeles',
      'page[number]': 1,
      'page[size]': 50
    };

    const promise = this.store.query('forest_transaction', params)
      .then((transactions) => {
        return records.map((record) => {
          record['relatedEmitterCustomerTransactions'] = transactions.filter(transaction => {
            return transaction['forest-emitterCustomer'].id === record['forest-customer'].id;
          });
          return record;
        });
      });

    return DS.PromiseArray.create({
      promise,
    });
  }

  @action
  triggerSmartAction(...args) {
    return triggerSmartAction(this, ...args);
  }

  @action
  deleteRecords(...args) {
    return deleteRecords(this, ...args);
  }
}

The following example show how to force retrieve forest_transaction, that are not present in the original args.records. Using the @service store & manually tweaking the request should allow you to retrieve the missing data. Since the computed property returns a PromiseArray (It will most likely return a PromiseObject in your case), you should also be able to add a loader while retrieving the associated data.

Let me know if that helps :pray:

3 Likes

Holy shit thank you!

YOU ARE AMAZING

2 Likes