I'm trying to use react-admin to provide a user CRUD from my API (express)
I followed the steps from react-admin documentation.
Creating my own DataProvider.
Inserting Admin component tells me it is properly setup.
Adding a child Ressource component with users as the ressource name and ListGuesser as the list.
At this point I get a toast saying response in undefined and a console error saying Warning: Missing translation for key: "response is undefined"
I can see in the network tabs that the request is properly sent and receives a 200 response with the data I expected
I cannot understand it and where it comes from
Here is my adminComponent
import React from 'react';
import { Admin, Resource, ListGuesser } from 'react-admin'
import myDataProvider from './myDataProvider'
import './adminHomepage.css'
let myProvider = myDataProvider('http://localhost:8666')
function AdminHomepage(props) {
return (
<Admin dataProvider={myProvider}>
<Resource name="users" list={ListGuesser} />
</Admin>
);
}
export default AdminHomepage;
Here is my dataProvider
import useAuth from "../../hooks/useAuth";
import { stringify } from 'query-string';
import {
fetchUtils,
GET_LIST,
GET_ONE,
CREATE,
UPDATE,
DELETE,
GET_MANY_REFERENCE
} from 'ra-core';
const { getToken } = useAuth();
export default (apiUrl, httpClient = fetchUtils.fetchJson) => {
const convertDataRequestToHttp = (type, resource, params) => {
let url = "";
const options = {};
options.headers = new Headers({ Authorization : getToken(), Accept: "application/json" })
switch (type) {
case GET_LIST: {
url = `${apiUrl}/${resource}/`;
break;
}
case GET_ONE: {
url = `${apiUrl}/${resource}/${params.id}`;
break;
}
case CREATE: {
url = `${apiUrl}/${resource}/${params.id}`;
options.method = "POST";
options.body = JSON.stringify(params.data);
break;
}
case UPDATE: {
url = `${apiUrl}/${resource}/${params.id}`;
options.method = "PUT";
options.body = JSON.stringify(params.data);
break;
}
case DELETE: {
url = `${apiUrl}/${resource}/${params.id}`;
options.method = "DEL";
break;
}
default: {
throw new Error(`Unsupported request type ${type}`);
}
}
return { url, options };
};
const convertHttpResponse = (response, type, resource, params) => {
const { headers, json } = response;
switch (type) {
case GET_LIST:
case GET_MANY_REFERENCE: {
if (!headers.has("content-range")) {
throw new Error(
"Content-Range is missing from header, see react-admin data provider documentation"
);
}
let ret = {
data: json.users,
total: parseInt(
headers
.get("Content-Range")
.split(" ")
.pop()
)
};
console.log("RETURN", ret)
return ret
}
case CREATE: {
return { data: { ...params.data, id: json.id } };
}
default: {
return { data: json };
}
}
};
return (type, resource, params) => {
const { url, options } = convertDataRequestToHttp(type, resource, params);
return httpClient(url, options).then(response => {
console.log(response)
convertHttpResponse(response, type, resource, params);
});
};
};
Screenshot of my error
Warning: Missing translation for key: "response is undefined"
in Notification (created by Connect(Notification))
in Connect(Notification) (created by WithStyles(Connect(Notification)))
in WithStyles(Connect(Notification)) (created by Context.Consumer)
in Context.Consumer (created by translate(WithStyles(Connect(Notification))))
in translate(WithStyles(Connect(Notification))) (created by Layout)
in Layout (created by WithStyles(Layout))
in WithStyles(Layout) (created by Route)
in Route (created by withRouter(WithStyles(Layout)))
in withRouter(WithStyles(Layout)) (created by Connect(withRouter(WithStyles(Layout))))
in Connect(withRouter(WithStyles(Layout))) (created by LayoutWithTheme)
in LayoutWithTheme (created by Route)
in Route (created by CoreAdminRouter)
in CoreAdminRouter (created by Connect(CoreAdminRouter))
in Connect(CoreAdminRouter) (created by getContext(Connect(CoreAdminRouter)))
in getContext(Connect(CoreAdminRouter)) (created by Route)
in Route (created by CoreAdminBase)
in CoreAdminBase (created by withContext(CoreAdminBase))
in withContext(CoreAdminBase) (at adminHomepage.js:11)
in AdminHomepage (created by Router.Consumer)
in Router.Consumer (created by Route)
in Route (at App.js:17)
in App (at src/index.js:7)
I think you're forgetting to return the actual result back:
Change your code to:
return (type, resource, params) => {
const { url, options } = convertDataRequestToHttp(type, resource, params);
return httpClient(url, options).then(response => {
console.log(response)
return convertHttpResponse(response, type, resource, params);
});
}
This bit is probably just to pad the answer.
A Promise in JavaScript is just an object which will at some point get resolved to a value. When you do Promise.then you're basically returning a new promise which will have a callback triggered when resolved and that callback will receive the resolved property. The new promise your make will have the return value based on the result of the callback given. In your case the final resolved promise would have been undefined because nothing was returned in the callback
Related
I am receiving this error in getInitialProps function attached to my page:
const getInitialProps = async ({ req, query }: NextPageContext) => {
let isValidReferenceQuery = true;
const { path } = absoluteUrl(req as IncomingMessage);
try {
await policyService.getPolicyByReference(
path,
query.referenceQuery as string
);
} catch (error) {
isValidReferenceQuery = false;
}
return {
referenceQuery: (query.referenceQuery as string) ?? null,
isValidReferenceQuery,
};
};
Page.getInitialProps = getInitialProps;
The function absoluteUrl determines if the API call was made server-side or client-side and adjusts the path accordingly:
export default function absoluteUrl(
req: IncomingMessage
): Record<string, string> {
const protocol = req.headers.referer?.split('//')[0] ?? 'http:';
const host = req
? req.headers['x-forwarded-host'] || req.headers.host
: window.location.host;
const path =
host === 'localhost:3000'
? `${protocol}//localhost:8080`
: `${protocol}//${host}${axios.defaults.baseURL}`;
return {
path,
};
}
I have updated my _document.tsx & _app.tsx to include getInitialProps as described to opt-out of automatic static generation in development mode so req should be defined. We also need all pages to be SSR for localisation:
import React from 'react';
import App, { AppContext } from 'next/app';
import { ReactQueryCacheProvider, QueryCache } from 'react-query';
import { Hydrate } from 'react-query/hydration';
import { appWithTranslation } from '#/localisation/i18n';
import { AppPageProps } from '#/models/page';
const TextMiningApp = ({ Component, pageProps }: AppPageProps) => {
const queryCache = new QueryCache();
const Layout = Component.Layout ? Component.Layout : React.Fragment;
return (
<ReactQueryCacheProvider queryCache={queryCache}>
<Hydrate state={pageProps.dehydratedState}>
<Layout>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<Component {...pageProps} />
</Layout>
</Hydrate>
</ReactQueryCacheProvider>
);
};
TextMiningApp.getInitialProps = async (appContext: AppContext) => ({
...(await App.getInitialProps(appContext)),
});
export default appWithTranslation(TextMiningApp);
I cannot see why if i have opted out of automatic static generation that req is undefined?
Not sure if this is still needed, but currently getInitialProps in _app.js actually works a little differently than pages. Instead of directly passing context as an argument, it passes an object with ctx and Component properties. So you would need to do something like this:
TextMiningApp.getInitialProps = async( { ctx, Component } ) => {
// This should be working now.
console.log( ctx.req );
}
One catch is that App.getInitialProps it needs to be passed all properties from the original argument:
App.getInitialProps( ctx, Component );
See https://github.com/vercel/next.js/discussions/14913
I am writing a test case for my service class. I want to mock multiple calls inside one function as I am making two API calls from one function. I tried following but it is not working
it('should get store info', async done => {
const store: any = DealersAPIFixture.generateStoreInfo();
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: store
});
const nextRequest = moxios.requests.at(1);
nextRequest.respondWith({
status: 200,
response: DealersAPIFixture.generateLocation()
});
});
const params = {
dealerId: store.dealerId,
storeId: store.storeId,
uid: 'h0pw1p20'
};
return DealerServices.retrieveStoreInfo(params).then((data: IStore) => {
const expectedOutput = DealersFixture.generateStoreInfo(data);
expect(data).toMatchObject(expectedOutput);
});
});
const nextRequest is always undefined
it throw error TypeError: Cannot read property 'respondWith' of undefined
here is my service class
static async retrieveStoreInfo(
queryParam: IStoreQueryString
): Promise<IStore> {
const res = await request(getDealerStoreParams(queryParam));
try {
const locationResponse = await graphQlRequest({
query: locationQuery,
variables: { storeId: res.data.storeId }
});
res.data['inventoryLocationCode'] =
locationResponse.data?.location?.inventoryLocationCode;
} catch (e) {
res.data['inventoryLocationCode'] = 'N/A';
}
return res.data;
}
Late for the party, but I had to resolve this same problem just today.
My (not ideal) solution is to use moxios.stubRequest for each request except for the last one. This solution is based on the fact that moxios.stubRequest pushes requests to moxios.requests, so, you'll be able to analyze all requests after responding to the last call.
The code will look something like this (considering you have 3 requests to do):
moxios.stubRequest("get-dealer-store-params", {
status: 200,
response: {
name: "Audi",
location: "Berlin",
}
});
moxios.stubRequest("graph-ql-request", {
status: 204,
});
moxios.wait(() => {
const lastRequest = moxios.requests.mostRecent();
lastRequest.respondWith({
status: 200,
response: {
isEverythingWentFine: true,
},
});
// Here you can analyze any request you want
// Assert getDealerStoreParams's request
const dealerStoreParamsRequest = moxios.requests.first();
expect(dealerStoreParamsRequest.config.headers.Accept).toBe("application/x-www-form-urlencoded");
// Assert graphQlRequest
const graphQlRequest = moxios.requests.get("POST", "graph-ql-request");
...
// Assert last request
expect(lastRequest.config.url).toBe("status");
});
I'm creating an Apollo Client like this:
var { ApolloClient } = require("apollo-boost");
var { InMemoryCache } = require('apollo-cache-inmemory');
var { createHttpLink } = require('apollo-link-http');
var { setContext } = require('apollo-link-context');
exports.createClient = (shop, accessToken) => {
const httpLink = createHttpLink({
uri: `https://${shop}/admin/api/2019-07/graphql.json`,
});
const authLink = setContext((_, { headers }) => {
return {
headers: {
"X-Shopify-Access-Token": accessToken,
"User-Agent": `shopify-app-node 1.0.0 | Shopify App CLI`,
}
}
});
return new ApolloClient({
cache: new InMemoryCache(),
link: authLink.concat(httpLink),
});
};
to hit the Shopify GraphQL API and then running a query like that:
return client.query({
query: gql` {
productVariants(first: 250) {
edges {
node {
price
product {
id
}
}
cursor
}
pageInfo {
hasNextPage
}
}
}
`})
but the returned object only contain data and no extensions which is a problem to figure out the real cost of the query.
Any idea why?
Many thanks for your help
There's a bit of a hacky way to do it that we wrote up before:
You'll need to create a custom apollo link (Apollo’s equivalent of middleware) to intercept the response data as it’s returned from the server, but before it’s inserted into the cache and the components re-rendered.
Here's an example were we pull metrics data from the extensions in our API:
import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from 'apollo-boost'
const link = new HttpLink({
uri: 'https://serve.onegraph.com/dynamic?show_metrics=true&app_id=<app_id>',
})
const metricsWatchers = {}
let id = 0
export function addMetricsWatcher(f) {
const watcherId = (id++).toString(36)
metricsWatchers[watcherId] = f
return () => {
delete metricsWatchers[watcherId]
}
}
function runWatchers(requestMetrics) {
for (const watcherId of Object.keys(metricsWatchers)) {
try {
metricsWatchers[watcherId](requestMetrics)
} catch (e) {
console.error('error running metrics watcher', e)
}
}
}
// We intercept the response, extract our extensions, mutatively store them,
// then forward the response to the next link
const trackMetrics = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
runWatchers(
response
? response.extensions
? response.extensions.metrics
: null
: null
)
return response
})
})
function create(initialState) {
return new ApolloClient({
link: trackMetrics.concat(link),
cache: new InMemoryCache().restore(initialState || {}),
})
}
const apolloClient = create(initialState);
Then to use the result in our React components:
import { addMetricsWatcher } from '../integration/apolloClient'
const Page = () => {
const [requestMetrics, updateRequestMetrics] = useState(null)
useEffect(() => {
return addMetricsWatcher(requestMetrics =>
updateRequestMetrics(requestMetrics)
)
})
// Metrics from extensions are available now
return null;
}
Then use a bit of mutable state to track each request and its result, and the use that state to render the metrics inside the app.
Depending on how you're looking to use the extensions data, this may or may not work for you. The implementation is non-deterministic, and can have some slight race conditions between the data that’s rendered and the data that you've extracted from the extensions.
In our case, we store performance metrics data in the extensions - very useful, but ancillary - so we felt the tradeoff was acceptable.
There's also an open issue on the Apollo client repo tracking this feature request
I dont have any idea of ApolloClient but i tried to run your query in shopify graphql app. It return results with extensions. Please find screenshot below. Also You can put questions in ApolloClient github.
import { Page } from 'puppeteer/lib/Page';
export class MonitorRequestHelper {
public static monitorRequests(page: Page, on = false) {
if(on) {
page.on('request', req => {
if (['image', 'font', 'stylesheet'].includes(req.resourceType())) {
// Abort requests for images, fonts & stylesheets to increase page load speed.
req.abort();
} else {
req.continue();
}
});
} else {
return true;
}
}
}
I am trying to mock and spy the function to check if it got called at least once.
Also, it would be helpful if some explain me how to mock and spy event-emitter object.
The source code is available on https://github.com/Mukesh23singh/puppeteer-unit-testing
If you want to test that your logic in monitorRequests works, you need to pass in a fake Page object with an event emitter interface that produces a fake request that you can test on.
Something like:
import {spy} from 'sinon';
// Arrange
const fakePage = { on(type, cb) { this[type] = cb; } }; // "event emitter"
const fakeRequest = {
abort: sinon.spy(),
resourceType() { return 'image'; }
};
monitorRequests( fakePage, true );
// Act
// trigger fake request
fakePage['request'](fakeRequest);
// Assert
assert(fakeRequest.abort.called);
I'm using a third-party API (so far so good, I get the result), however as I'm new I'm having difficulty manipulating these results for the frontend (EJS)** ...
What the system should do is:
The site is a graphics panel, where I use the third-party API (it blocks the origin if consumed by the frontend) and I populate the data received in JSON, using the graphics plugin called (CHARTJS) .. The problem is there, because the API should be consumed as soon as the page is accessed and the graphics populated after consumption, but for me to consume this JSON I need to pass some QS ... These QSs when accessing the page for the first graphic are already automatic and can be manipulated by the form that contains the same page, in order to be able to carry out other queries ..
I am totally, lost, I would love some help and explanation ..
CONTROLLERS/LOGIN/LOGIN.JS
module.exports.index = (_application, _request, _response) => {
_response.render('login/index');
}
module.exports.check_login = (_application, _request, _response) => {
const JSON_MODEL = new _application.app.models.jsonDAO('fatVenda.xsjs', '\'BSC\'', '201807', 'vendaabs,vendam2');
JSON_MODEL.request();
console.log(JSON_MODEL.jsonData)
const VENDA_MODEL = new _application.app.models.vendaDAO('', '', '', '');
_response.render('home/home');
}
ROUTES/LOGIN/LOGIN.JS
module.exports = (_application) => {
_application.get('/login', (_request, _response) => {
_application.app.controllers.login.login.index(_application, _request, _response);
});
_application.post('/check_login', (_request, _response) => {
_application.app.controllers.login.login.check_login(_application, _request, _response);
});
}
MODELS/JSONDAO.JS
const REQUEST = require('request');
class JsonRequestDAO {
constructor(_xjs, _shoppingID, _periodUntil, _kpi){
this.xjs = _xjs;
this.shoppingID = _shoppingID;
this.periodUntil = _periodUntil;
this.kpi = _kpi;
}
request(){
REQUEST.get({
uri: `URL${this.xjs}`,
json: true,
qs: {
Shop: this.shoppingID,
PeriodoAte: this.periodUntil,
Kpi: this.kpi
},
headers: {
Authorization: 'Basic KEY',
ApiKey: 'KEY',
'Cache-Control': 'no-cache',
'Content-Type': 'application/json',
}
}, (_error, _response) => {
(_response.statusCode == 200) ? _response.body : console.log(_error);
});
}
}
module.exports = () => { return JsonRequestDAO; }
MODELS/VENDADAO.JS
class VendaDAO {
constructor(_shoppingName, _yearMonth, _competence, _sale, _salePercentage, _saleSquareMeter, _salePercentageSquareMeter){
this.shoppingName = _shoppingName;
this.yearMonth = _yearMonth;
this.competence = _competence;
this.sale = _sale;
this.salePercentage = _salePercentage;
this.saleSquareMeter = _saleSquareMeter;
this.salePercentageSquareMeter = _salePercentageSquareMeter;
}
static get shoppingName() { return this.shoppingName; }
static get yearMonth() { return this.yearMonth; }
static get competence() { return this.competence; }
static get sale() { return this.sale; }
static get salePercentage() { return this.salePercentage; }
static get saleSquareMeter() { return this.saleSquareMeter; }
static get salePercentageSquareMeter() { return this.salePercentageSquareMeter; }
}
module.exports = () => {
return VendaDAO;
}