play framework - what is the proper way to return list of data? - scala

I'm new to the reactive/observables concept.
I've defined a route for getting Products like this:
GET /products controllers.ProductController.findProducts
and the implementation in the controller:
def findProducts() = secured.async { implicit request =>
productDao.find()
.map(products => Ok(Json.toJson(products)))
// products: List[Product]
.recover(errHandler)
}
Then, on the client-side, I'm making the call and subscribing to it like this:
let sub = this.find()
.subscribe(
products => {
this.products = products;
this.productsBehaviorSubject.next( this.products );
if (!!sub)
sub.unsubscribe();
},
error => console.log("Error fetching units: " + error)
);
As soon as I get the data, I unsubscribe from it. (basically using it like Promises).
So I wonder, if this is the right way to do it. Instead of returning List[Product] in one single response, should I be returning in several responses??
def findProducts() = secured.async { implicit request =>
productDao.find()
.map(products => {
products.map(p => Ok(Json.toJson(p))) // haven't tried this yet
})
// products: List[Product]
.recover(errHandler)
}
then on the client side .. maybe .:
this.find()
.take( 50 )
.subscribe(
product => {
this.products.push( product )
},
error => console.log("Error fetching units: " + error),
() => this.productsBehaviorSubject.next( this.products )
);

Your first approach is correct, you will notice that the secured.async expects the code block to return Future[Result]. So if you try to map over the products then the signature will be Future[Seq[Result]] which will give you a compilation error.
Also in my experience, returning the full list in a single response is the standard way to code.

Related

Pass Parameters Besides URL in Next JS in Client Side API Fetch

I've been working on calling an internal GetCompanies API through the website client side.
So far I made attempts like this based on search results and documentation
// const fetcher = (includeFiles, folderLocation, SharePointCreds) => fetch(includeFiles, folderLocation, SharePointCreds).then(res => res.json());
// const { data, error } = useSWR('http://localhost:55419/api/SharePoint/GetCompanies', fetcher);
// if (error) return <div>failed to load</div>
// if (!data) return <div>loading...</div>
// render data
//return <div>hello {data}!</div>
useEffect((includeFiles, folderLocation, SharePointCreds) => {
fetch('http://localhost:55419/api/SharePoint/GetCompanies', includeFiles, folderLocation, { method: "POST", body: SharePointCreds})
.then((res) => res.json())
.then((data) => {
console.log("data is ", data)
})
}, [])
if (isLoading) return <p>Loading...</p>
// async function GetCompanies(includeFiles, folderLocation, SharePointCreds){
// const res = await fetch('http://localhost:55419/api/SharePoint/GetCompanies');
// const companies = await res.json();
// }
// return (
// <div>
// <p>{data}</p>
// </div>
// )
corresponding to an API call which works when we test it in Swagger
And I got generic error messages like TypeError: Failed to fetch
I guess the error is in how I pass in includeFiles and folderLocation since I haven't yet found documentation or search results for how to pass in parameters besides the URL, though I could be wrong.
I define all the arguments earlier in the portfolio component for now.
Do you see how else I should pass in includeFiles and folderLocation?
Also, please be kind because I'm learning and doing my best and I've worked on this for a few hours already.

Suspense returns data too fast in #tanstack/react-query v4

I am upgrading a React app from react-query v3 to #tanstack/react-query v4.
Almost everything works, but I'm having a problem with Suspense.
I have a react component:
const WrapperPageEdit: React.FC<MyProps> = ({
pageUuid,
redirect,
}: MyProps) => {
const FormPage = React.lazy(() => import('./FormPage'));
const { data } = usePageView(pageUuid);
if (data?.[0]) {
const pageObjectToEdit= data[0];
const content = pageObjectToEdit.myStuff.content;
return (
<Suspense
fallback={<Trans id="loading.editor">Loading the editor...</Trans>}
>
<FormPage
id={uuid}
content={content}
redirect={redirect}
/>
</Suspense>
);
}
return <p>No data.</p>;
};
And here's my query:
export function usePageView(
uuid: string,
): UseQueryResult<DrupalPage[], Error> {
return useQuery<DrupalPage[], Error>(
queryKeyUsePageView(uuid),
async () => {
return fetchAnon(getPageByPageUuid(uuid));
},
{
cacheTime: YEAR_MILLISECONDS,
staleTime: YEAR_MILLISECONDS,
onSuccess: (data) => {
if (data?.[0]) {
data.map((element) => processResult(element));
}
},
},
);
}
This works in v3 but fails in v4 with the following error:
TypeError: Cannot read properties of undefined (reading 'content')
The reason the property is undefined is because that property is set by the processing in onSuccess (data.map).
The issue appears to be that in v4, the component WrapperPageEdit is refreshed before onSuccess in the usePageView query has finished processing, whereas in v3, the component WrapperPageEdit is not refreshed until the onSuccess data.map is complete.
How can I correctly fix this? I can write some additional code to try to check whether the onSuccess data.map is complete, but since react-query handled this automatically in v3, I'd like to rewrite my code in v4 so that it is the same.
The problem is likely that you are mutating the data in onSuccess. Directly modifying data in callbacks is not a good idea. Instead, do your transformation for example directly in the queryFn:
async () => {
const data = fetchAnon(getPageByPageUuid(uuid));
if (data?.[0]) {
data.map((element) => processResult(element));
}
return data
},
other good places to do data transformation is e.g. the select option, but it should always happen in an immutable way, because otherwise, you are overwriting the cached data inadvertently. React prefers updates to be immutable.

How to display API data using fetch on Dialogflow GUI

I want to display data from OMDB API on Dialogflow GUI but it's not happening. Data is being displayed fine on Google Cloud Console.
function infoHandler(agent){
let movieName = agent.parameters.movie;
agent.add(`The information for ${movieName} is as follow`);
fetch('http://www.omdbapi.com/?apikey=e255decd%20&s='+ movieName)
.then(result => result.json())
.then((json) => {
let id = json.Search[0].imdbID;
fetch('http://www.omdbapi.com/?apikey=e255decd%20&i=' + id)
.then(result => result.json())
.then((json) => {
agent.add(json.Title + json.Plot + json.imdbRatinng);
return;
}).catch((ex) => {
console.log(ex);
});
})
.catch((e) => {console.log(e);});
The issue is that fetch() causes an asynchronous operation. However, there is nothing that indicates to the Dialogflow handler dispatcher that it is asynchronous and that it should wait for it to complete before sending back a reply. To do that, you'd need to return a Promise.
Fortunately the then/catch chain that you have that are built off the fetch() return a Promise. So all you need to do is return the Promise that they have. This is as simple, in your case, by placing a return before the fetch() call. So it would look something like this:
return fetch('http://www.omdbapi.com/?apikey=e255decd%20&s='+ movieName)
// Other lines remain the same

Sequence of maps not working - scala play framework

I'm having some problems when trying to map some different objects so that I can extract some fields from it.
I've the function in my controller like this:
def index = SecuredAction.async { implicit request =>
transportService.allNonActive().map { transports =>
val sourceEmailsListBuffer = ListBuffer[String]()
val destinyEmailsListBuffer = ListBuffer[String]()
val sortingCenterStockListBuffer = ListBuffer[SortingCenterStock]()
val transportsListBuffer = ListBuffer[Transport]()
transports.map { transport =>
transportsListBuffer.append(transport)
// gets SC of this transport
sortingCenterStockService.retrieve(transport.idSCStock).map { sortingCenterStock =>
Logger.debug(s"Entry on SCS")
sortingCenterStockListBuffer.append(sortingCenterStock)
}
// gets email from source
userDAO.find(transport.idSourceUser).map { option =>
option.map { user =>
user.email.map { email =>
sourceEmailsListBuffer.append(email)
Logger.debug(s"Entry on Source Email")
}
}
}
// gets email from destiny
userDAO.find(transport.idDestinyUser).map { option =>
option.map { user =>
user.email.map { email =>
destinyEmailsListBuffer.append(email)
Logger.debug(s"Entry on Destiny Email")
}
}
}
}
Logger.debug(s"Size of source emails: ${sourceEmailsListBuffer.size}")
Logger.debug(s"Size of destiny emails: ${destinyEmailsListBuffer.size}")
Logger.debug(s"Size of scs: ${sortingCenterStockListBuffer.size}")
Logger.debug(s"Size of transp: ${transportsListBuffer.size}")
Ok(views.html.transports.index(request.identity, sourceEmailsListBuffer.toList, destinyEmailsListBuffer.toList, sortingCenterStockListBuffer.toList, transportsListBuffer.toList))
}
}
When I load the page for the first time (with any minor change, i.e. I change the string I use to indicate what I'm debugging), it gets the info from the last map userDAO.find(transport.idDestinyUser).map. When I refresh the page, the list's size destinyEmailsListBuffer is 0 and it is returned to the view before doing the map (at least I think so).
This is what I get after refreshing, after getting the correct output for the first time:
second load of the page
Thanks in advance, I hope you can help me!
I think your general structure is wrong. For instance:
userDAO.find(transport.idDestinyUser).map { option =>
option.map { user =>
user.email.map { email =>
destinyEmailsListBuffer.append(email)
Logger.debug(s"Entry on Destiny Email") // This returns Unit!
}
}
}
So you are using map operations and chaining those results to other functions, but instead if returning lists of items, you are incrementing an existing list that is never returned. Either return destinyEmailsListBuffer after logging or re-write to use forEach and to pick up the right values from somewhere.

spray routing access to url path

I have a route with a portion like this:
...
(pathEnd | path("summary")) {
parameters(...).as(Query) { query =>
onSuccess(model ? query) {
case MyResponse(list) =>
// at this point I would like to know if I hit pathEnd or
// path(summary) so I can complete with summary or full response.
if (???)
complete(OK, list)
else
complete(OK, list map (_.toSummary))
}
}
}
...
Essentially there's a lot of parameter wrangling and querying of the model that is identical, but I'm doing an extra transformation of the response to shed some data if the summary endpoint is hit. Is it possible to do this in some way?
I tried adding a ctx => after the (pathEnd | path("summary")) { ctx => but that didn't work at all. (The route didn't match, and never returned anything.)
I gave this custom directive a quick unit test and it seems to work:
def pathEndOr(p: String) =
pathEnd.hmap(true :: _) | path(p).hmap(false :: _)
You can use it in you example like so:
...
pathEndOr("summary") { isPathEnd =>
parameters(...).as(Query) { query =>
onSuccess(model ? query) {
case MyResponse(list) =>
// at this point I would like to know if I hit pathEnd or
// path(summary) so I can complete with summary or full response.
if (isPathEnd)
complete(OK, list)
else
complete(OK, list map (_.toSummary))
}
}
}
...