How do you target ember-cli-mirage from a regular AJAX request using ember-concurrency?

Howdy! I’ve refactored the Debounced Type-Ahead Search Example from ember-concurrency’s docs as follows:

// glossary-app/app/pods/application/controller.js

import Controller from "@ember/controller";
import { task, isBlank, timeout } from "ember-concurrency";
import $ from "jquery";

const DEBOUNCE_MS = 250;

export default Controller.extend({
  searchRepo: task(function * (term) {
  if (isBlank(term)) { return []; }

  yield timeout(DEBOUNCE_MS);

  let url = `/api?q=${term}`;

  let json = yield this.get("getJSON").perform(url);
  return json.items.slice(0, 10);
}).restartable(),

getJSON: task(function * (url) {
  let xhr;
  try {
    xhr = $.getJSON(url);
    let result = yield xhr.promise();
    return result;

  } finally {
    xhr.abort();
  }
}),
});

I placed the Template into the application like so:

// glossary-app/app/pods/application/template.hbs

<div class="container mx-auto max-w-xl bg-grey-white xxs:w-95p xsml:w-375 xsm:w-425 smm:w-480 sm:w-600 md:w-768 lg:w-980 md:shadow">

  {{ui-nav}}

</div>

<div class="container lg:mx-auto sm:flex sm:flex-col-reverse smm:flex smm:flex-col-reverse xsm:flex xsm:flex-col-reverse xsml:flex xsml:flex-col-reverse xxs:flex xxs:flex-col-reverse xxs:ml-15 xsml:mt-10 max-w-xl bg-grey-white md:flex md:flex-col-reverse lg:flex-row lg:w-980 md:shadow">

  <div>
    <input type="text" oninput={{perform searchRepo value="target.value"}} placeholder="Search GitHub..." />

    {{#if searchRepo.isRunning}}
      {{loading-spinner}}
    {{/if}}

    <ul>
      {{#each searchRepo.lastSuccessful.value as |repo|}}
        <li>{{repo.full_name}}</li>
      {{/each}}
    </ul>
  </div>

  <div class="xxs:w-95p mx-auto xxs:mr md:w-700 md:ml-20 md:mt-10">
    {{outlet}}
    {{glossaryData.data.url}}
  </div>
</div>

{{ui-footer}}

This throws:

Uncaught TypeError: (0 , _emberConcurrency.isBlank) is not a function

Which looks like…

If the context helps, I am using Mirage Fixtures and just pushed the latest code to Github here: https://github.com/jameshahn2/glossary-app

Can anyone spot what I’m doing wrong?

Based on the stack trace the error is coming from the use of ‘isBlank’ in your controller. Looking at your controller code you are importing isBlank from ember-concurrency but isBlank is provided by ember, not ember-concurrency, so you’ll want to remove isBlank from your ember concurrency imports and import it from ember instead:

import { isBlank } from '@ember/utils';

1 Like

As for mirage though, mirage should intercept any xhr request to the configured host (in mirage config, you probably want just the default host anyway) that you don’t explicitly whitelist, so any raw ajax requests should also hit mirage.

Good catch on isBlank, that was the first thing @samselikoff saw. Got the proper import in there and it fixed the errors, good. But now when you start typing nothing happens; bad. Added a debugger to the mirage/config.js and it’s getting triggered.

Not sure where to go from here.

I think the spot that you have the debugger statement is not what you want. That’s just verifying that mirage is booting. I think you want the debugger in the route handler itself, what would be line 17 in the screenshot (let { term } = queryParams;).

I also noticed that in the quick search code you’re using the query param “q” but in the mirage handler you’re using “term” so you probably want to change this line:

  let url = `/api?q=${term}`;

to

  let url = `/api?term=${term}`;

Now we’re cooking with gas! Watching the Promises in Ember Inspector shows concurrency doing exactly what it’s configured to do. It’s still broken but it’s also damn exciting for me to look at this and recognize this is a series of concurrency tasks firing right on schedule… until the rejection on finally. I know it’s right on schedule because I was entering “b-e-a-n” into the input. Fantastic since I didn’t even get a handle on the difference between objects and methods or the this keyword (among many other things) until last night!

This is where my excitement gets the big 'ol cancelAll because after pushing the rejection value to Console I’m told:

"Error: Mirage: Your Ember app tried to GET '/api?term=bean', but there was no route defined to handle this request. Define a route that matches this path in your mirage/config.js file. Did you forget to add your namespace?"

But we did not forget our namespace.

// mirage/config.js

/* eslint-disable */

export default function() {
  this.namespace = "api";
this.get("/terms", function({ terms }, { queryParams }) {
  debugger;
  let { term } = queryParams;
  if (!term) {
    return terms.all();
  } else {
    term = term.toLowerCase();
    return terms.findBy({ term });
  }
});
  this.get("/terms/:id");
}

Moreover, the results are the same whether you use:

let url = `/api?q=${term}`;

or

let url = `/api?term=${term}`;

Long shot, but could this be a result of not customizing any of the code to match the rest of the app?

Ah whoops that was a lazy miss on my part, it’s because the EC code is just making a call to /api and not /api/terms.

let url = `/api?term=${term}`;

That will just hit the servers /api endpoint with query param term which isn’t what you want.

In the mirage config your handler endpoint is {namespace}/terms:

  this.namespace = "api";
  this.get("/terms", function({ terms }, { queryParams }) {

So change the line to be:

let url = `/api/terms?term=${term}`;

If it’s not one thing, it’s another.

Error: json.items is undefined

And the Console.

jQuery.Deferred exception: json.items is undefined _default<.searchRepo<@http://localhost:4200/assets/glossary-app.js:5567:7
_resumeGenerator@http://localhost:4200/assets/vendor.js:111031:46
_handleResolvedContinueValue@http://localhost:4200/assets/vendor.js:111187:12
_proceed@http://localhost:4200/assets/vendor.js:111151:14
invoke@http://localhost:4200/assets/vendor.js:27727:24
flush@http://localhost:4200/assets/vendor.js:27637:25
flush@http://localhost:4200/assets/vendor.js:27804:31
_end@http://localhost:4200/assets/vendor.js:28271:42
end@http://localhost:4200/assets/vendor.js:28011:18
_run@http://localhost:4200/assets/vendor.js:28316:26
_join@http://localhost:4200/assets/vendor.js:28292:29
join@http://localhost:4200/assets/vendor.js:28065:25
join@http://localhost:4200/assets/vendor.js:16746:28
_proceedSoon@http://localhost:4200/assets/vendor.js:111109:19
proceed@http://localhost:4200/assets/vendor.js:111124:12
handleYieldedUnknownThenable/<@http://localhost:4200/assets/vendor.js:110533:20
mightThrow@http://localhost:4200/assets/vendor.js:3898:29
resolve/</process<@http://localhost:4200/assets/vendor.js:3966:12
 undefined

Searched all of @machty’s repos and the only variation on:

let json = yield this.get("getJSON").perform(url);

is

let json = yield $.getJSON(url);

This produces the same error.

Probably from the second of these two lines:

  let json = yield this.get("getJSON").perform(url);
  return json.items.slice(0, 10);

Try putting a breakpoint on that line to see what you’re getting in “json”. My guess is that it looks like a JSONAPI payload, so you’d want json.data.slice(0,10) but that’s just a guess.

Interesting. Looks like it’s successful with the breakpoint.

10

Breaks when you remove it though.

json.data.slice seems like a good guess… at least it throws a different error?

jQuery.Deferred exception: json.data.slice is not a function _default<.searchRepo<@http://localhost:4200/assets/glossary-app.js:5567:24
_resumeGenerator@http://localhost:4200/assets/vendor.js:111031:46
_handleResolvedContinueValue@http://localhost:4200/assets/vendor.js:111187:12
_proceed@http://localhost:4200/assets/vendor.js:111151:14
invoke@http://localhost:4200/assets/vendor.js:27727:24
flush@http://localhost:4200/assets/vendor.js:27637:25
flush@http://localhost:4200/assets/vendor.js:27804:31
_end@http://localhost:4200/assets/vendor.js:28271:42
end@http://localhost:4200/assets/vendor.js:28011:18
_run@http://localhost:4200/assets/vendor.js:28316:26
_join@http://localhost:4200/assets/vendor.js:28292:29
join@http://localhost:4200/assets/vendor.js:28065:25
join@http://localhost:4200/assets/vendor.js:16746:28
_proceedSoon@http://localhost:4200/assets/vendor.js:111109:19
proceed@http://localhost:4200/assets/vendor.js:111124:12
handleYieldedUnknownThenable/<@http://localhost:4200/assets/vendor.js:110533:20
mightThrow@http://localhost:4200/assets/vendor.js:3898:29
resolve/</process<@http://localhost:4200/assets/vendor.js:3966:12
 undefined

Maybe that:

finally {
    xhr.abort();
  }

should go?

So the problem is that you’re trying to slice a non-array. Now you have to decide how you want this to behave. The example from ember-concurrency is doing a slice to trim the results to 10, but your backend is only returning one record (or all), not an array. If you want it to return anything that partially matches the query string you’ll need to modify your mirage route handler.

Then you have to ask yourself if you want to be using ember data for this or just raw AJAX. If the latter then you’ll have to parse the attributes out yourself OR push the json into the store when you’re ready to use it in the route.

So at this point it’s sort of a design/product question than a technical one.

I’ve been hammering away at this for 11 months and made the decision to go with Ember Data early on for several reasons so definitely want to stick it out. It doesn’t look like much with 27 definitions in the store now, but the full json has over 6,000 terms.

I started at “What the heck is command line??” but was determined to figure out how to interact with the json by calling it from an S3 bucket. Oil and gas people don’t trust the cloud, but they trust AWS and if I want to sell this I knew I had to slay that dragon. Was able to get all configured, learn TailwindCSS, clone SLB’s glossary look and feel, wire up the basic components of the app, and basically learn Ember the hardest way possible. Which is why I kept running headlong into brick walls trying to work with json from AWS.

Was nearly ready to give up hope until a couple weeks ago when I decided to start over, chop the json down to the bare minimum, and try to power through my json/Ember Data problems/learning curve using Mirage. Figured if I could build-up the code to get a couple dozen terms, I could turn around and refactor it to get a couple/few/6,000+.

We live to fight another day.

Keep hope alive!

Thank you for attending my TED Talk.

Ok if you want to stick with Ember Data then your ember concurrency task should probably just use store.query instead of jquery.json. I don’t think there would be any issues with that anyway, I think the quicksearch exampe from EC was just demonstrating a non-Ember Data example. So your search task looks like:

searchRepo: task(function * (term) {
  if (isBlank(term)) { return []; }
  yield timeout(DEBOUNCE_MS);
  return yield this.store.query('term', { term });
}).restartable(),

You could also slice the result down if you wanted, the above will return all matches. And of course this is assuming that you want to return more than once match per query, which I think makes sense for a quicksearch. Next your mirage handler needs to return an array instead of a single result…

this.get("/terms", function({ terms }, { queryParams }) {
  debugger;
  let { term } = queryParams;
  let terms = terms.all();
  if (term) {
     const match = term.toLowerCase();
     terms = terms.filter(t => t.term.toLowerCase().includes(match));
  }
  return terms;
});

That should return any term models that have a ‘term’ that includes the query string submitted by the client.

When running a search.

stack: “Error: Ember Data Request GET api/terms returned a 500↵Payload (application/json)↵[object Object]↵ at ErrorClass.EmberError`

When navigating to localhost:4200/terms/bean

Error while processing route: terms Your Ember app tried to GET ‘api/terms/bean’, but there was no route defined to handle this request. Define a route that matches this path in your mirage/config.js file. Did you forget to add your namespace?

Hmmm can’t tell offhand what that would be without more code. Could you push your latest to github? Seems like you might have some code in your adapter that’s rewriting query URLs? That’s just a guess though…

Pushed.

https://github.com/jameshahn2/glossary-app

Small issue in the mirage config and once that was fixed the application template wasn’t be displaying the right things in the dropdown. I got the dropdown list part working, here’s a git diff:

--- a/app/pods/application/template.hbs
+++ b/app/pods/application/template.hbs
@@ -14,8 +14,8 @@
     {{/if}}

     <ul>
-      {{#each searchRepo.lastSuccessful.value as |repo|}}
-        <li>{{repo.full_name}}</li>
+      {{#each searchRepo.lastSuccessful.value as |term|}}
+        <li>{{term.term}}</li>
       {{/each}}
     </ul>
   </div>
diff --git a/mirage/config.js b/mirage/config.js
index f2464d4..9c2d59c 100755
--- a/mirage/config.js
+++ b/mirage/config.js
@@ -1,12 +1,12 @@
 export default function() {
   this.namespace = "api";
-this.get("/terms", function({ terms }, { queryParams }) {
-
-  let { term } = queryParams;
-  if (term) {
-    const match = term.toLowerCase();
-    terms = terms.filter(t => t.term.toLowerCase().includes( match ));
-  }
-  return terms;
-});
+  this.get("/terms", function({ terms }, { queryParams }) {
+    let { term } = queryParams;
+    let results = terms.all();
+    if (term) {
+       const match = term.toLowerCase();
+       results = results.filter(t => t.term.toLowerCase().includes(match));
+    }
+    return results;
+  });
 }

Basically my original snipped a couple comments ago was overloading “terms” which caused an error so I changed it to “results”. And then a tweak to the template to match the models being returned.

This fixes the search, thank you!!

Unfortunately no longer able to navigate to individual words. Still getting “…but there was no route defined…” error posted in full above. Do I need to update the router.js?

Ah, so the route model hook is gone, and Ember has this convenient but confusing behavior where it guesses your model. So it’s making a request to findRecord('term', params.term_id). I’d recommend putting the model hook back (and by back I mean just return the same findRecord from the previous sentence) because explicit is always better.

It probably would have worked anyway but it looks like you may have removed the mirage shorthand for get('terms/:id') so mirage doesn’t know how to handle that findRecord request. You’ll need to put that back.