Smart View Initial Recordset

I have a Smart View that needs to filter out some initial records before showing them to the template. I have tried to use fetchData as was described in the Smart View documents, but not having any luck.

Here’s how I’d imagine it should work:

@service store;
constructor () {
    super(...arguments);

    set(this, "isLoading", true);
    set(this, "currentRecordIsLoading", true);
  }

 async fetchData() {
    set(this, "isLoading", true);

    const params = {
      'page[number]': 1,
      'page[size]': 500
    };

    // get all the talent queues
    await this.store.query('forest_talentQueue', params)
      .then((talentQueues) => {
        // only return TQ if it has a beacon
        talentQueues.filter((talentQueue) => {
          const currentBeacon = talentQueue["forest-Beacons"]?.firstObject || {};

          if (currentBeacon.id !== undefined) {
            talentQueue.beacon = currentBeacon;
            return talentQueue;
          }
        });

        this.set('records', talentQueues);
      });
  }

Basically, I want to only show all the records that have a beacon attached to them, and then use that the initial this.args.records. Is this not possible?

If not, then how can I fetch the filtered data without causing a render on the template?

In my template, I need to:

  1. show a loading state while the filter records get found:
<div class="wrapper"  {{did-insert this.setDefaultTalentQueue}}>
  {{#if (or this.isLoading @isLoading)}}
    <div class="wrapper--is-loading">
      <span class="fa fa-spinner fa-spin fa-5x"></span>
    </div>
  {{else}}
    <div class="wrapper__list">
      <div class="wrapper__list-inner">
        {{#each this.records as |talentQueue|}}
  1. setDefaultTalentQueue should select the first record after the filtered records load.

Hope this makes sense. Thanks!

Hello @David_Panzarella,

The function fetchData in the documentation is supposed to be called when you click on a given button. It’s not a particular function name that gets called when the component loads.

If you want to call it when the component is rendering, you must call it in the constructor, or as described in the docs, add this in your template

<div {{did-insert this.fetchData}}></div>

And add an @action on the function fetchData

I had a feeling that might have been the case.

So this fires off just fine by adding it to the constructor or changing the did-insert to point to another call to fetch the data, but it won’t replace the state of this.args.records or get loaded into my template correctly.

In my edit mode, I was able to finally get a working version (so I thought) using:
Component.js:

  // called from template during load
  @action
  setDefaultTalentQueue() {
    if (!this.currentRecord && this.talentQueuesWithBeacons?.firstObject?.beacon?.id) {
      this.currentTalentQueue(this.talentQueuesWithBeacons.firstObject);
    }
  }

  // after first render (eg showing loading state in template)
  @computed("args.records")
  get talentQueuesWithBeacons() {
    set(this, "isLoading", true);

    // get only the beacons that have a talent queue
    const talentQueues = this.args.records.filter((record) => {
      const currentBeacon = record["forest-Beacons"]?.firstObject || {};

      // only return TQ if it has a beacon
      if (currentBeacon.id !== undefined) {
        record.beacon = currentBeacon;
        return record;
      }
    });

    set(this, "isLoading", false);
    return talentQueues;
  }

Template.js

<div class="wrapper"  {{did-insert this.setDefaultTalentQueue}}>
  {{#if (or this.isLoading @isLoading)}}
    <div class="wrapper--is-loading">
      <span class="fa fa-spinner fa-spin fa-5x"></span>
    </div>
  {{else}}
    <div class="wrapper__list">
      <div class="wrapper__list-inner">
        {{#each this.talentQueuesWithBeacons as |talentQueue|}}

I’m guessing the lifecycle (which would be great to know if you have some forest documentation here) here is not finding the first Talent Queue in the list because I’m filtering it.

One error I see on the initial load, in edit or live view, is this:

Uncaught (in promise) TaskCancelation: TaskInstance 'fetchRecordsList' was canceled because it belongs to a 'restartable' Task that was .perform()ed again. For more information, see: http://ember-concurrency.com/docs/task-cancelation-help

The only way for me to actually get the list of TalentQueues to load is by saving the code in the edit mode (after it already loaded) or clicking the refresh button in the production live view(after it already loaded as well). Clearly, I’m still missing something.

I need to figure out the lifecycle so that I can:

  1. Check all the records and remove those that don’t have an attached forest-Beacons.
  2. Set the current.record if its not been set or selected.
  3. Load the currentTalentQueue once current.record is selected.

This should be pretty simple, but again, the life cycle of how forest is firing things must be off or that TaskCancelation is happening due to this.args.records being manipulated.

Thoughts?

Hello @David_Panzarella,

With emberJS, this.args contains the list of properties passed to your component by the calling component. It is read only, and can only be modified by the parent component.

In your case, you will need to maintain another property, that you will need to set at the end of fetchData. For instance with this.talentQueues = talentQueues

Then, you’ll be able to use it in the template with this.talentQueues.

Hi @GuillaumeGautreau,

I figured that was the case as well.

So I think really what is going on here is the sequence of when this.args request is getting fulfilled. TalentQueue has a hasMany with Beacon having a belongsTo TalentQueue. Even if I try to fetch all beacons and make sure that each TalentQueue has a Beacon attached before the template is rendered, I still get the same result that I get if I just filter the this.args.records (which would be the ideal).

So this:

  @computed("args.records")
  get talentQueuesWithBeacons() {
    set(this, 'isLoading', true)
    const records = this.args.records || [];

    return DS.PromiseArray.create({
      promise: this.loadBeacons(records)
        .then(beacons => {
          const finalTalentQueues = records.filter(record => {
            const talentQueueBeacon = record['forest-Beacons']?.firstObject || {};

            if (talentQueueBeacon.id !== undefined) {
              const currentBeacon = (beacons || []).find(beacon => record.id === beacon["forest-TalentQueue"].id);

              record.beacon = currentBeacon;
              return record;
            }
          }).sort((b, a) => a.id - b.id);

          set(this, 'isLoading', false);
          return finalTalentQueues;
        })
    })
  }

has the same result as this:

    // get only the beacons that have a talent queue
    const filtered = records.filter(talentQueue => {
      const currentBeacon = talentQueue["forest-Beacons"]?.firstObject || {};

      if (currentBeacon.id !== undefined) {
        talentQueue.beacon = currentBeacon;
        return talentQueue;
      }
    }).sort((b, a) => a.id - b.id);

And this still occurs on initial load regardless:

Uncaught (in promise) TaskCancelation: TaskInstance 'fetchRecordsList' was canceled because it belongs to a 'restartable' Task that was .perform()ed again. For more information, see: http://ember-concurrency.com/docs/task-cancelation-help

The problem remains that Beacons aren’t getting loaded until a refresh happens; even if I create a this.loadBeacons(records) to fetch all Beacons that have a TalentQueueID match from this.args.records.

Can you and I schedule a time to look into this on my production admin?

Thanks again!

@David_Panzarella

I’ll take this thread from now on (I need to setup a test projet, I’ll come back to you shortly)

1 Like

Thanks @romaing!

I was finally able to get this to work by doing a fetch for the Beacon before loading anything in the component to guarantee the presence of its Beacon. It’s an additional check, but as there isn’t another way to stop it from sending it to the template until its fully got all of its data, I had to take the same approach as @GuillaumeGautreau mentioned in another thread:


  // beacon promises to get beacon added to talent queues
  async loadBeacons(records) {
    return Promise.all(records.map(record => this.loadBeacon(record)));
  }

  @computed("args.records")
  get talentQueuesWithBeacons() {
    set(this, 'isLoading', true)
    const records = this.args.records || [];

    return DS.PromiseArray.create({
      promise: this.loadBeacons(records)
        .then(recordsWithBeacons => {

          // only return those that have a beacon
          const filtered = recordsWithBeacons.filter(talentQueue => {
            if (talentQueue?.beacon.id !== undefined) {
              return talentQueue;
            }
          }).sort((b, a) => a.id - b.id);

          set(this, 'isLoading', false);

          // select the first record on load
          if (filtered.firstObject?.beacon?.id) {
            this.currentTalentQueue(filtered.firstObject);
          }

          return filtered;
        })
    });
  }

However, even after getting this to work, when in Edit Smart View mode, I still see this on load:

vendor-f38421eac2e7eb9c59ddee8383db0f70.js:158 Uncaught (in promise) TaskCancelation: TaskInstance 'fetchRecordsList' was canceled because it belongs to a 'restartable' Task that was .perform()ed 

Sorry for the super long delay :sweat_smile:

I was still trying to replicate
Happy to learn that you found a way to work around your issue!