In my Angular app I'm using $odataresource directive to consume OData v4 feed in the following way:
http://10.0.0.4:8080/InformationProduct?$expand=DataEntities($expand=DataSources)
This query runs fine in Fiddler, Postman, as well as any modern browser:
{
#odata.context: "http://10.0.0.4:8080/$metadata#InformationProduct",
value: [{
ID: 1,
Name: "ODM Dashboard",
Description: "ODM Dashboard",
Governance_ID: 1,
PerformanceMetric_ID: 1,
DataEntities: [{
ID: 1,
Name: "Data Entity 1",
Description: "Data Entity 1",
InformationProduct_ID: 1,
BiMeasure_ID: null,
BiFact_ID: null,
BiDimension_ID: 1,
DataSources: [{
ID: 40,
Category: "Service Performance",
SourceSystemName: "Account Improvement Plan",
SourceSystemOwner: null,
SourceSystemLocation: null,
SourceSystemTeam: null,
SourceSystemNetworkSegment: null,
SourceSystemOsType: null,
SourceDatabaseName: null,
SourceDatabaseType: null,
SourceDatabaseVersion: null,
BiFact_ID: null
}]
}]
}]
}
I'm trying to implement the same query within my Angular controller like this:
function getData(){
$odataresource("http://windows-10:8080/InformationProduct")
.odata()
.expand('SourceTools')
.expand('DataEntities','DataSources')
{}
}
I'm getting this error:
GET http://windows-10:8080/InformationProduct?$expand=SourceTools,DataEntities/DataSources 400 (Bad Request)
It is clear, that $odataresource does not translate .expand('DataEntities','DataSources')
into $expand=DataEntities($expand=DataSources) as expected
What is the proper way to have $odataresource directive produce such nested $expand?
The docs state that you need to use the isodatav4 property to let it know that you are using a v4 endpoint:
To enable this behavior set the isodatav4 property to true when
invoking the $odataresource method:
User = $odataresource('/user', {}, {}, {
odatakey: 'id',
isodatav4: true
});
Then use the expand method like this:
var result = User.odata().expand("roles", "role").query();
// /user?$expand=roles($expand=role)
For you, this means that you have the right expand call already, you just need to add the isodatav4 flag to $odataresource("http://windows-10:8080/InformationProduct") to make $odataresource("http://windows-10:8080/InformationProduct", {}, {}, { isodatav4: true })
Related
I'm currently making my first network call in a paginated series of calls on the server side. Prior to doing this I was making all of the calls client side and stored the last document in the collection call as an offset.
The offset was then sent as a .startAfter call for the same collection. The offset document looked like this:
exists: (...)
id: (...)
metadata: (...)
ref: (...)
_document: Document {key: DocumentKey, version: SnapshotVersion, data: ObjectValue, proto: {…}, hasLocalMutations: false, …}
_firestore: Firestore {_queue: AsyncQueue, INTERNAL: {…}, _config: FirestoreConfig, _databaseId: DatabaseId, _dataConverter: UserDataConverter, …}
_fromCache: false
_hasPendingWrites: false
_key: DocumentKey {path: ResourcePath}
__proto__: DocumentSnapshot
When I make the call on the server I'm currently able to see the document looks the same, but when I send it over the network it seems to be stripped or at least looks very different by the time it's sent over JSON and parsed back.
Sent like this:
res.json(offset)
then parsed like this:
feedData = await dataWithOffset.json();
After the parsing it looks like this:
{ _ref:
{ _firestore:
{ _settings: [Object],
_settingsFrozen: true,
_serializer: [Object],
_projectId: '***-prod',
_lastSuccessfulRequest: 1566308918946,
_preferTransactions: false,
_clientPool: [Object] },
_path:
{ segments: [Array],
projectId: '***-prod',
databaseId: '(default)' } },
_fieldsProto:
{ lastModified: { timestampValue: [Object], valueType: 'timestampValue' },
...,
_serializer: { timestampsInSnapshots: true },
_readTime: { _seconds: 1566308918, _nanoseconds: 909566000 },
_createTime: { _seconds: 1565994031, _nanoseconds: 304997000 },
_updateTime: { _seconds: 1565994031, _nanoseconds: 304997000 } }
Any idea why it is losing its shape and what I can do to fix it so it returns to working as a proper offset? Should I not be converting to JSON and back, as that may strip some important things?
Solution (tested)
So I went ahead and made a sandbox to test the implementation, and like I mentioned in the comments .startAfter expects a DocumentSnapshot not a DocumentReference
Given that I created a collection named cars with 4 records like this:
[
{
"name": "car 1",
"hp": 5
},
{
"name": "car 2",
"hp": 10
},
{
"name": "car 2.5",
"hp": 10
},
{
"name": "car 3",
"hp": 15
}
]
Then I configured an express endpoint:
router.get('/cars/:lastDocId', (req, res) => {
const query = db.collection('cars')
.orderBy('hp')
.limit(2);
const handleQueryRes = (snap) => {
return res.send({
docs: snap.docs.map(doc => doc.data()),
last: snap.docs.length > 0 ? snap.docs[snap.docs.length - 1].id : null
});
}
if(req.params.lastDocId != -1) {
// this makes an "auxiliar" read from the DB to transform the given ID in a DocumentSnapshot needed for startAfter
return db.collection('cars').doc(req.params.lastDocId).get().then(snap => {
return query.startAfter(snap).get();
}).then(handleQueryRes).catch(console.log);
} else {
return query.get().then(handleQueryRes).catch(console.log);
}
});
The path parameter lastDocId is either -1 for first page or the last document fetched in the previous page
When I make a GET request to /cars/-1 it returns this:
{
"docs": [
{
"hp": 5,
"name": "car 1"
},
{
"name": "car 2",
"hp": 10
}
],
"last": "9sRdLOvV8REwEpHDjEw7"
}
Now if I grab the last prop in the response and use it in my next GET like so /cars/9sRdLOvV8REwEpHDjEw7 I get my next 2 cars:
{
"docs": [
{
"name": "car 2.5",
"hp": 10
},
{
"name": "car 3",
"hp": 15
}
],
"last": "tZPF7Wav7jZuchoCp6zM"
}
Even though I only have 4 records and the second request should be the last the function does not know the length of the collection so it returns another last ID
If you make the last request /cars/tZPF7Wav7jZuchoCp6zM it returns:
{
"docs": [],
"last": null
}
Thus giving indication that is the last page.
I'm not too fond of having to read the document again to transform it into a DocumentSnapshot but I guess that's a firebase limitation
Hope it helped
Answer with steps
When converting to JSON and back you lose the DocumentReference prototype
The solution to pass that type of data through network requests is:
Client sends a plain-text ref to your endpoint ( or doesn't for first page)
Have the server side code re-create the DocumentReference with the received ref
Use the re-created DocumentReference to paginate query
Return from server the requested page's documents alongside with a plain-text refto get the next page
Initial answer (obsolete) but for future reference
You need to send offset.data() instead of sending offset alltogether
One is the document's data (probably what you're after) the other is a DocumentReference which has methods that cannot be represented in JSON.
If you need anything else from the document (like it's id or reference) you're better off building a new object:
res.json({
data: offset.data(),
ref: offset.ref,
id: offset.id
});
When querying a model with include, incase of no rows available, it is returning data with null values.
I know this problem has been posted many times before, but i have not found any conclusions till now.
Model.findOne({
where: { id: 1 },
include: [{model: Model1}]
})
Result i am getting is like
{
"id": null,
"name": null,
"age": null,
"model1": []
}
If i use raw: true, it is printing like
{
"id": null,
"name": null,
"age": null,
"model1.model1n": 0
}
One cause of this is if you exclude id from attributes. If you're using scopes or attributes in your includes or through tables, make sure you're returning the id primary key.
Another cause of this is if you return null in a custom get() getter function on the column.
I'm looking at this template to build a web application: https://js.devexpress.com/Demos/WidgetsGallery/Demo/PivotGrid/FieldChooser/AngularJS/Light/
In the example there are static data. I have to retrieve them from the server. So, I wrote this:
$scope.testData = [];
$scope.pivotGridDataSource = new DevExpress.data.PivotGridDataSource({
fields: [{
caption: "Nome",
dataField: "fullName",
area: "row"
}, {
caption: "Country",
dataField: "country",
area: "column"
}, {
caption: "Count",
dataField: "countOne",
dataType: "number",
summaryType: "sum",
area: "data"
}],
store: $scope.testData
});
$scope.pivotGridOptions = {
allowSortingBySummary: true,
allowSorting: true,
allowFiltering: true,
showBorders: true,
dataSource: $scope.pivotGridDataSource,
fieldChooser: {
enabled: false
}
},
$scope.fieldChooserOptions = {
dataSource: $scope.pivotGridDataSource,
texts: {
allFields: "All",
columnFields: "Columns",
dataFields: "Data",
rowFields: "Rows",
filterFields: "Filter"
},
width: 400,
height: 400,
bindingOptions: {
layout: "layout"
}
};
// Now I call the server to retrieve data
$scope.getTestData = () => {
$scope.testData.length = 0;
result.forEach(e => {
$scope.testData.push(e);
);
$scope.pivotGridDataSource.reload();
}
$scope.getTestData();
The problem is that when the data are loaded, in the Fields below it shows just the fields written at the beginning (so the name, the count and the country). But I saw in the demo that it should be display ALL parameters of the object.
For example, if the object is so structured:
{ "name": "Test1", "country": "Germany", "creationDate": "xxx", "surname": "yyy" }
So, I expect that in the fields there should be ALL parameters, so name, country, creationDate, surname. So, I did this at the beginning:
I changed $scope.testData = [] into:
$scope.testData = [{ "name": "", "country": "", "creationDate": "", "surname": "" }]
so the component will preparare all fields. And this works. But what if the server gives me back an Object that has another parameters? How can I display them?
I tried so after the calling and before the reload():
let fields = $scope.pivotGridDataSource.fields();
let newField = {
llowExpandAll: false,
allowFiltering: true,
allowSorting: true,
allowSortingBySummary: true,
caption: "This is a new field",
dataField: "newField",
dataType: "string",
displayFolder: "",
index: fields.length
}
$scope.pivotGridDataSource.fields().push(newField);
$scope.pivotGridDataSource.reload();
But it doesn't work yet. Worse, it does not even initialize the Pivot.
The fieldChooser uses the store fields, in this case $scope.testData fields, in your code I see your store is first declared (as null or with some format as you described) and then you have a function to fill it.
I don't know how your code looks and why you create your store that way, but that is basically your problem (the flow).
In the sample code the flow is:
Store with data (static in this case)
PivotGridDataSource
FieldChooser
In your code the flow is:
Store (empty)
PivotGridDataSource
FieldChooser
Store (fill) --> at this point your FieldChooser has been initialized with the fields of the empty version of your store so not much to do (in Jquery you could re-create your object, you dan do it using Jquery within AngularJs see a simple code sample here and below)
$('#chartContainer').dxChart('instance').dispose();
$('#chartContainer').dxPieChart({
...
});
To avoid all of this you can just use the DevExpress.data.CustomStore and your flow will be basically identical to the demo.
I am new to vue and can't find a solution to this -
I have a JSON object here, and I am trying to dynamically fetch the "info" of a user based on their "userRegion".
{
"userData": {
"kr": {
"info": {
"name": "testing-123",
}
},
"any": null,
"us": null,
"eu": {
"info": {
"name": "testing-456",
}
},
},
"userRegion": "eu"
}
I then have this object in vue and I want to dynamically change region and pull the data from the object based on this region value in the "user" object below.
user:{
region: this.userData.userRegion,
name: this.userData[this.user.region].info.name
},
For example, I have tried using something like this
this.userData.userData[this.user.region]
but I get an error:
TypeError: Cannot read property 'region' of undefined"
the variable I am using "userData" is passed down from the parent like so:
<infoWindow :userData='userData'></infoWindow>
and is set as a prop:
props: {
userData: app.userData,
},
any help would be aprpeciated, thanks
I don’t really understand where you are setting this user, whether its part of an initialized data object, or a computed property. However, there is a temporal issue there:
user: {
region: this.userData.userRegion,
name: this.userData[this.user.region].info.name
},
In order to set up user.name, user.region needs to be already there. But since you are creating the user object at once, this does not work. So you either have to split that up, or repeat the logic for the user region again:
user: {
region: this.userData.userRegion,
name: this.userData[this.userData.userRegion].info.name
},
I have to deal with a huge json like this acting as live datasource, is loaded every 5 min from a url..
sports: [
{
id: 200,
title: "Horse Racing",
meetings: [ ],
is_virtual: false,
events: [...],
pos: 83
},
{
id: 600,
title: "Tennis",
meetings: [ ],
is_virtual: false,
events: [
{
id: 301804310,
is_virtual: false,
outcomes: [
{
id: 32779738900,
description: "Brown/Pliskova",
},
{
id: 32779738900,
description: "Brown/Pliskova",
}]
}]
}]
And need to write methods like
getAllSports() returning an array object with all sports
getSport(sport_id) returning the object with this sport id
getAllEvents(Sport) returning all events list object of this sprot
getEvent(Sport, event_id) returning events that matches with given event_id
getOutcomes(Event, outcomes) ... and so on
Is there is a library that parses the json and already have methods some methods to help me to do this kind of stuff? example: obj.find(sport_id)...
In JS you have LowDB https://github.com/typicode/lowdb for this, any similar in Ruby/Sinatra? Or any approach suggestion? Im not using Rails.
Thanks in advice
You could always use Ruby's built in JSON library. You would be able to do something like
json_string = '{"name": "my name", "age": 5}'
object = JSON.parse(json_string)
object["name"] => "my name"
You can then use regular ruby hash / array functions on the returned object. In your case, you could do something along the lines of
def getSport(json_object, id)
json_object["sports"].select { | h | h["id"] == id }.first
end
Which, assuming you have already parsed the JSON and passed the resulting value into that function, would return the sport that had the given ID.