cult-of-coders / grapher

Grapher: Meteor Collection Joins + Reactive GraphQL like queries
https://atmospherejs.com/cultofcoders/grapher
MIT License
275 stars 53 forks source link

Nested Dynamic Filters #440

Closed LordA98 closed 3 years ago

LordA98 commented 3 years ago

Hi,

Is it possible to use the dynamic filter function on nested objects?

I currently do this to filter by the current day:

orders: {
      $filters: {
        'createdAt.date': new Date().toLocaleDateString('en-US')
      },
      $options: {
        sort: {'createdAt.time': -1}
      },
      items: 1,
      orderTotal: 1,
      createdAt: 1
}

CreatedAt is an object containing a date property and a time property.

I cannot seem to add a filter to filter by the date property of createdAt.

I have tried this:

 orders: {
      $filter({filters, params}) {
        filters.createdAt.date = '11/18/2020' //new Date(params.date).toLocaleDateString('en-US')
      },      
      $options: {
        sort: {'createdAt.time': -1}
      },
      items: 1,
      orderTotal: 1,
      createdAt: 1
}

but it returns these errors:

TypeError: Cannot set property 'date' of undefined and error occurred in the <ForwardRef> component.

Changing createdAt: 1 to

createdAt: {
    date: 1,
    time: 1
}

had no effect.

I thought the issue might have been that I needed to filter inside createdAt:

createdAt: {
    $filter({filters, params}) {
          filters.date = '11/18/2020' //new Date(params.date).toLocaleDateString('en-US')
    }
},

but that returns this error:

Exception in callback of async function: Error: Exception while polling query {"collectionName":"orders","selector":{"venueOrderedAt.id":"XXX"},"options":{"transform":null,"sort":{"createdAt.time":-1},"fields":{"items":1,"orderTotal":1,"createdAt.$filters.date":"11/18/2020","createdAt.$options":1,"lastUpdatedAt":1}}}: Positional projection 'createdAt.$filters.date' does not match the query document.

The whole current createQuery is shown below for reference (without any changes made):

const query = createQuery('venuesAndOrdersByUser', {
  venues: {
    $filter({filters}) {
      filters.userId = Meteor.userId()
    },
    name: 1,
    orders: {
      $filters: {
        'createdAt.date': new Date().toLocaleDateString('en-US')
      },
      $options: {
        sort: {'createdAt.time': -1}
      },
      items: 1,
      orderTotal: 1,
      comments: 1,
      paymentOptionSelected: 1,
      status: 1,
      hasBeenAcknowledged: 1,
      hasBeenCompleted: 1,
      hasBeenPaidFor: 1,
      tableToDeliverTo: 1,
      venueOrderedAt: 1,
      acknowledgedAt: 1,
      completedAt: 1,
      paidForAt: 1,
      createdAt: 1,
      lastUpdatedAt: 1,
    }
  },
});

Happy to provide more information if needed. I apologise if this is a stupid question but I could not find anything in the documentation.

LordA98 commented 3 years ago

I've 'solved' this by moving both the date and time properties to the highest level of the schema.

So rather than my schema looking like this:

createdAt: { type: Object, defaultValue: {} },
'createdAt.date': {
      type: String,
      autoValue: function() { 
        if(this.isSet) {
          return this.value;
        } else {
          return new Date().toLocaleDateString(); 
        }
      }
},
'createdAt.time': {
      type: String,
      autoValue: function() { 
        if(this.isSet) {
          return this.value;
        } else {
          return new Date().toLocaleTimeString(); 
        }
      }
},

It looks like this:

createdAtDate: {
      type: String,
      autoValue: function() { 
        if(this.isSet) {
          return this.value;
        } else {
          return new Date().toLocaleDateString(); 
        }
      }
},
    createdAtTime: {
      type: String,
      autoValue: function() { 
        if(this.isSet) {
          return this.value;
        } else {
          return new Date().toLocaleTimeString(); 
        }
      }
},

And query & filter look like this:

const query = createQuery('venuesAndOrdersByUser', {
  venues: {
    $filter({filters}) {
      filters.userId = Meteor.userId()
    },
    name: 1,
    orders: {
      $filter({filters, params}) {
        filters.createdAtDate = new Date(params.date).toLocaleDateString('en-US')
      },
      $options: {
        sort: {'createdAtTime': -1}
      },
      items: 1,
      orderTotal: 1,
      comments: 1,
      paymentOptionSelected: 1,
      status: 1,
      hasBeenAcknowledged: 1,
      hasBeenCompleted: 1,
      hasBeenPaidFor: 1,
      tableToDeliverTo: 1,
      venueOrderedAt: 1,
      acknowledgedAt: 1,
      completedAt: 1,
      paidForAt: 1,
      createdAtDate: 1,
      lastUpdatedAt: 1,
    }
  }
});

For whatever reason, I couldn't get the $filter function to work inside of the createdAt field so I have changed to this method instead.

I will close this issue but would still be interested to know what I was getting wrong here.