I've been trying for a few days now to get the Breeze 1.4.9 to work with a rails back end in a different manner than the Breeze Ruby SPA sample. I would rather send bulk save changes instead of trying to send RESTful calls to the server on every entity change. To that end, I've written a rails controller/model methods that will parse out all the different entities in a Breeze SaveChanges POST and act accordingly. Everything works great except that the response to SaveChanges POST doesn't seem to satisfy all the checks for Breeze and EntityManager.hasChanges() is still true even after the response is processed successfully.
Here's a typical cycle:
Breeze requests my hand crafted metadata and parses it fine:
{
"metadataVersion": "1.0.5",
"namingConvention": "rubyNamingConvention",
"localQueryComparisonOptions": "caseInsensitiveSQL",
"dataServices": [
{
"serviceName": "breeze\/Breeze\/",
"hasServerMetadata": true,
"jsonResultsAdapter": "webApi_default",
"useJsonp": false
}
],
"structuralTypes": [
{
"shortName": "VarianceReason",
"namespace": "Icon",
"autoGeneratedKeyType": "Identity",
"defaultResourceName": "VarianceReasons",
"dataProperties": [
{
"name": "id",
"dataType": "Int32",
"isNullable": false,
"defaultValue": 0,
"isPartOfKey": true,
"validators": [
{
"name": "required"
},
{
"name": "int32"
}
]
},
{
"name": "name",
"dataType": "String",
"isNullable": false,
"defaultValue": "",
"maxLength": 256,
"validators": [
{
"name": "required"
},
{
"maxLength": 256,
"name": "maxLength"
}
]
},
{
"name": "createdAt",
"dataType": "DateTime",
"isNullable": false,
"defaultValue": "1900-01-01T08:00:00.000Z",
"validators": [
{
"name": "required"
},
{
"name": "date"
}
]
},
{
"name": "updatedAt",
"dataType": "DateTime",
"isNullable": false,
"defaultValue": "1900-01-01T08:00:00.000Z",
"validators": [
{
"name": "required"
},
{
"name": "date"
}
]
}
]
}
],
"resourceEntityTypeMap": {
"VarianceReasons": "VarianceReason:#Icon"
}
}
I make an entity change in Breeze and it POSTs the below to rails when I call em.SaveChanges():
{
"entities":[
{
"id":-1,
"name":"anyuthingasd",
"created_at":"1900-01-01T08:00:00.000Z",
"updated_at":"1900-01-01T08:00:00.000Z",
"entityAspect":{
"entityTypeName":"VarianceReason:#Icon",
"defaultResourceName":"VarianceReasons",
"entityState":"Added",
"originalValuesMap":{
},
"autoGeneratedKey":{
"propertyName":"id",
"autoGeneratedKeyType":"Identity"
}
}
}
],
"saveOptions":{
}
}
Rails then responds with:
{
"KeyMappings":[
{
"EntityTypeName":"VarianceReason:#Icon",
"TempValue":-1,
"RealValue":16
}
],
"Entities":[
{
"id":16,
"name":"anyuthingasd",
"created_at":"2014-05-02T14:21:24.221Z",
"updated_at":"2014-05-02T14:21:24.221Z",
"Entity":null
}
]
}
Breeze then merges in the new id key mapping but doesn't clear the cache, so next time I make another entity change it still has the first change which has already persisted to the server and the new change. Can anyone tell me what I'm not responding with from the rails side that makes Breeze EntityManager not satisfied? I'm trying to trace through the 15k lines of code but can't say I'm a JS ninja.
We really do need to show folks how to build a data service adapter for whatever service they've got.
In this case, it appears you chose to implement something like the SaveChanges method in C# on the Web API. In other words, you've chosen to emulate the out-of-the-box Breeze protocol. That's cool! And non-trivial too so kudos to you.
I think what's missing from the entity JSON in your save response is the EntityType name. Breeze can't find the corresponding cached entities without knowing their types and thus cannot update their change-states.
Again, because you've decided to use the default Web API data service adapter, you'll want to return a response that adapter expects. That adapter defines a "jsonResultsAdapter" that expects each JSON entity data object to have a $type property specifying the full type name (namespace.typename).
In your example, I think you'd want to return
...
"Entities":[
{
"$type": "Icon.VarianceReason",
"id":16,
"name":"anyuthingasd",
"created_at":"2014-05-02T14:21:24.221Z",
"updated_at":"2014-05-02T14:21:24.221Z",
}
]
How about an example?
I suspect that you may not have easy access to a server with Web API that can show you what a save response looks like with the default adapter. Therefore, I've pasted below a Todo app's saveChanges request and response for a change-set that includes a new, a modified, and a deleted TodoItem.
The Request
Below is the payload of the POST request to the "SaveChanges" endpoint. It is probably way more verbose than you need (more verbose than I'd need). Just to pick one example, the "autoGeneratedKey" is of no interest to the server whatsoever.
I'm just showing you what the default data service adapter sends. Someday you'll write your own to do it the way you want it. For now I suppose there is no harm in sending too much crappola ... as long as you're happy to ignore it on the Rails end :-)
{
"entities": [
{
"Id": 5,
"Description": "Cheese",
"CreatedAt": "2012-08-22T09:05:00.000Z",
"IsDone": true,
"IsArchived": false,
"entityAspect": {
"entityTypeName": "TodoItem:#Todo.Models",
"defaultResourceName": "Todos",
"entityState": "Deleted",
"originalValuesMap": {
},
"autoGeneratedKey": {
"propertyName": "Id",
"autoGeneratedKeyType": "Identity"
}
}
},
{
"Id": 6,
"Description": "Modified Todo",
"CreatedAt": "2012-08-22T09:06:00.000Z",
"IsDone": false,
"IsArchived": false,
"entityAspect": {
"entityTypeName": "TodoItem:#Todo.Models",
"defaultResourceName": "Todos",
"entityState": "Modified",
"originalValuesMap": {
"Description": "Wine"
},
"autoGeneratedKey": {
"propertyName": "Id",
"autoGeneratedKeyType": "Identity"
}
}
},
{
"Id": -1,
"Description": "New Todo",
"CreatedAt": "2014-05-02T17:34:00.904Z",
"IsDone": false,
"IsArchived": false,
"entityAspect": {
"entityTypeName": "TodoItem:#Todo.Models",
"defaultResourceName": "Todos",
"entityState": "Added",
"originalValuesMap": {
},
"autoGeneratedKey": {
"propertyName": "Id",
"autoGeneratedKeyType": "Identity"
}
}
}
],
"saveOptions": {
}
}
The Response
The $id property is a node counter. It's useful when you have repeated entities so you don't have to worry about cycles or repeated entity data in your payload (an object with a $ref property is the placeholder for the repeated entity). You can ignore $id if you don't need this feature (and you rarely would need it in a save result).
Notice that the $type is in the .NET "CSDL" type format "namespace.typename", not the Breeze type format "typename:#namespace". This is an artifact of the data service adapter's jsonResultsAdapter ... which you can change to better suit your Rails implementation. None of this is cast in stone. I'm just reporting what these adapters do as delivered.
You can ignore the assembly name (", Todo-Angular") in the $type value; Breeze doesn't care about it.
Notice that the deleted "Cheese" entity was returned with all of its contents. I bet you don't have to do that. You could get away with returning a stripped down version that simply lets the client know Rails got the message:
{
"$id": "2",
"$type": "Todo.Models.TodoItem, Todo-Angular",
"Id": 5
},
And now ... the complete JSON response body:
{
"$id": "1",
"$type": "Breeze.ContextProvider.SaveResult, Breeze.ContextProvider",
"Entities": [
{
"$id": "2",
"$type": "Todo.Models.TodoItem, Todo-Angular",
"Id": 5,
"Description": "Cheese",
"CreatedAt": "2012-08-22T09:05:00.000Z",
"IsDone": true,
"IsArchived": false
},
{
"$id": "3",
"$type": "Todo.Models.TodoItem, Todo-Angular",
"Id": 6,
"Description": "Modified Todo",
"CreatedAt": "2012-08-22T09:06:00.000Z",
"IsDone": false,
"IsArchived": false
},
{
"$id": "4",
"$type": "Todo.Models.TodoItem, Todo-Angular",
"Id": 7,
"Description": "New Todo",
"CreatedAt": "2014-05-02T17:34:00.904Z",
"IsDone": false,
"IsArchived": false
}
],
"KeyMappings": [
{
"$id": "5",
"$type": "Breeze.ContextProvider.KeyMapping, Breeze.ContextProvider",
"EntityTypeName": "Todo.Models.TodoItem",
"TempValue": -1,
"RealValue": 7
}
],
"Errors": null
}
Related
I'm trying to fetch all users from a specific group via an HTTP connector, a registered app, and Microsoft Graph.
The registered app has Directory.Read.All permissions.
My idea is that I'm calling the nextLink as long as it's there while appending all of the fetched users' userPrincipalName to an array eventually filling the array with all users of the group.
My Logic App looks like this:
Unfortunately, I'm just 1 reputation short of posting images, please forgive. The 3 links should provide an overview of the structure of my app.
First, nextLink is initialized to the first Graph API endpoint. This variable is set to the current nextLink through each iteration of the until loop.
Second, For the purpose of this exercise, I only get the top 5. I know there are only 9 users:
Lastly, I call the union method on the "users" array that I initialized earlier and the "value" array from the HTTP get method, to get one single array consisting of all users:
The issue is that the HTTP action always returns the same top 5 users. I've checked that the nextLink provided in the first HTTP GET call to Graph, is correct by copying it from the Runs history and pasting it into Microsoft Graph Explorer and there the next 4 users are correctly returned.
I also made sure that, for each iteration in the until loop, I call the Graph API with the nextLink from the previous iteration as expected.
The nextLink returned inside of the Logic App is exactly the same when I test it in Graph Explorer, but the same nextLink returns 2 different results when called from Graph Explorer and inside my Logic App.
Why is the result always the same top 5 users and not the next 4 users as expected?
If not sure about the reason why you will get this issue, but based on your requirement, I did a sample below:
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"actions": {
"Initialize_variable": {
"inputs": {
"variables": [
{
"name": "GetGroupUrl",
"type": "string",
"value": "https://graph.microsoft.com/v1.0/groups/<your group id>/members?$select=userPrincipalName&$top=5"
}
]
},
"runAfter": {},
"type": "InitializeVariable"
},
"Initialize_variable_2": {
"inputs": {
"variables": [
{
"name": "users",
"type": "array"
}
]
},
"runAfter": {
"Initialize_variable": [
"Succeeded"
]
},
"type": "InitializeVariable"
},
"Until": {
"actions": {
"Compose": {
"inputs": "#union(variables('users'),body('HTTP')['value'])",
"runAfter": {
"HTTP": [
"Succeeded"
]
},
"type": "Compose"
},
"HTTP": {
"inputs": {
"authentication": {
"audience": "https://graph.microsoft.com",
"clientId": "<app id>",
"secret": "<app secret>",
"tenant": "<your secret>",
"type": "ActiveDirectoryOAuth"
},
"method": "GET",
"uri": "#variables('GetGroupUrl')"
},
"runAfter": {},
"type": "Http"
},
"Set_variable": {
"inputs": {
"name": "GetGroupUrl",
"value": "#{if(equals(body('HTTP')?['#odata.nextLink'], null),null,body('HTTP')['#odata.nextLink'])}"
},
"runAfter": {
"Compose": [
"Succeeded"
]
},
"type": "SetVariable"
}
},
"expression": "#equals(variables('GetGroupUrl'), '')",
"limit": {
"count": 60,
"timeout": "PT1H"
},
"runAfter": {
"Initialize_variable_2": [
"Succeeded"
]
},
"type": "Until"
}
},
"contentVersion": "1.0.0.0",
"outputs": {},
"parameters": {
"$connections": {
"defaultValue": {},
"type": "Object"
}
},
"triggers": {
"manual": {
"inputs": {
"method": "GET",
"schema": {
"properties": {
"text": {
"type": "string"
}
},
"type": "object"
}
},
"kind": "Http",
"type": "Request"
}
}
},
"parameters": {}
}
You can just replace the params with your own and paste it into your logic app code view and test it .
It works for me, as you can see , each request results are different :
Hope it helps .
This issue solved by OP self, this issue is due to queries in request URL , copy OP's comment as an answer :
After fiddling a bit more around with what each of you providing I
found a solution. It seems that when the query arguments are passed to
the HTTP GET outside of the endpoint itself (meaning in the "queries"
field inside of the block) it seems to keep overriding the nextLink.
When writing the endpoint URL out entirely with the odata parameters,
it works as intended.
I am trying to build up a response from a variety of schema components using OpenAPI 3. There are basically three parts to the response:
A shared component that other endpoints use (i.e. success/failure flags). - #/components/schemas/core_response_schema inside allOf.
Properties that all responses on this endpoint use (i.e., user_id) - the properties component of the below.
One of several schemas that will vary depending on the type of user. - the oneOf component.
I've determined that I have to use allOf to be able to mix properties (item 2) and the core response (item 1), though this feels wrong as there's only one item. I tried a $ref, but it didn't work.
The below successfully passes three different OpenAPI linting tools, but in the example it builds, Swagger UI does not show the item 2 things (properties), and does show all of the item 3 things (should be oneOf).
"responses": {
"200": {
"description": "Operation successfully executed.",
"content": {
"application/json": {
"schema": {
"properties": {
"user_id": {
"$ref": "#/components/schemas/user_id"
},
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/result_user_by_id"
}
}
},
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/core_response_schema"
}
],
"oneOf": [
{
"$ref": "#/components/schemas/user_type_a"
},
{
"$ref": "#/components/schemas/user_type_b"
},
{
"$ref": "#/components/schemas/user_type_c"
}
]
}
}
}
}
},
"components": {
"schemas": {
"core_response_schema": {
"properties": {
"success": {
"description": "A flag indicating whether the request was successfully completed or not.",
"type": "boolean"
},
"num_results": {
"description": "The number of results for this request",
"type": "integer"
}
},
"type": "object"
},
"user_id": {
"description": "Unique 10 character `user_id`.",
"type": "string",
"maxLength": 10,
"minLength": 10,
"example": "a1b2c3d4e5"
},
}
}
And example payloads for two users. Type A and B (it's a contrived example).
User Type A:
{
"success": true,
"num_results": 1,
"user_id": "c1b00cb714",
"results": [{
"user_type": "a",
"group_id": "e7a99e3769",
"name": null,
"title": null,
... (and so on until we get to the stuff that's unique to this type of user) ...
"favourite_artworks": [
"sunflowers",
"landscapes"
],
"artwork_urls": [
"http://sunflowers.example"
]
}
]
}
User Type B:
{
"success": true,
"num_results": 1,
"user_id": "c1b00cb715",
"results": [{
"user_type": "B",
"group_id": "e7a99e3769",
"name": null,
"title": null,
... (and so on until we get to the stuff that's unique to this type of user) ...
"supported_charities": [
"UN Foundations"
],
"charity_urls": [
"http://www.un.int"
],
}
]
}
What's the correct way to merge together different schemas and properties in OpenAPI? Is this right and Swagger UI just can't handle it?
And how do you mix a schema with properties without having to use allOf?
This suggests it's possible: Swagger Schema: oneOf, anyOf, allOf valid at the same time?
After further investigation, I've determined this is a bug in swagger-ui - https://github.com/swagger-api/swagger-ui/issues/3803 - they simply don't support oneOf (or anyOf) currently.
As far as at least three different linting tools are concerned, a mixture of anyOf, oneOf, and allOf can be used together in the same schema.
Redoc appears to have similar problems - https://github.com/Rebilly/ReDoc/issues/641
I am trying to store a list of changes made to a Vertex in the Vertex itself. Ideally I would want something like this:
{
"id": "95fcfa87-1c03-436d-b3ca-340cea926ee9",
"label": "person",
"type": "vertex",
"log": [{
"user": "user#user.dk",
"action": "update",
"timestamp": "22-03-2017",
"field": "firstName",
"oldValue": "Marco"
}
]
}
Using this method chain I am able to a achieve the following structure
graph.addV('person')
.property('firstName', 'Thomas')
.property(list, 'log', '22-03-2017')
.properties('log')
.hasValue('22-03-2017', '21-03-2017')
.property('user','user#user.dk')
.property('action', 'update')
.property('field', 'firstName')
.property('oldValue', 'Marco')
{
"id": "95fcfa87-1c03-436d-b3ca-340cea926ee9",
"label": "person",
"type": "vertex",
"properties": {
"firstName": [{
"id": "f23482a9-48bc-44e0-b783-3b74a2439a11",
"value": "Thomas"
}
],
"log": [{
"id": "5cfa35e1-e453-42e2-99b1-eb64cd853f22",
"value": "22-03-2017",
"properties": {
"user": "user#user.dk",
"action": "update",
"field": "firstName",
"oldValue": "Marco"
}
}
]
}
}
However this seems overly complex, as I will have to store a value and add properties to it.
Is it possible to add anonymous objects (i.e. without id and value) with the above mentioned data?
Not an actual solution to storing proper objects in a history log, but if you just use it as a log and don't have to access or query it by its properties, you could just put the serialised JSON in the value?
Something like along these lines should approximate the structure you're requesting:
dynamic entry = new JObject();
entry.user = "user#user.dk";
entry.action = "update";
entry.timestamp = "22-03-2017 12:34:56";
entry.field = "firstName";
entry.oldValue = "Marco";
graph.addV('person')
.property('firstName', 'Thomas')
.property(list, 'log', entry.ToString());
{
"id": "95fcfa87-1c03-436d-b3ca-340cea926ee9",
"label": "person",
"type": "vertex",
"properties": {
"firstName": [{
"id": "f23482a9-48bc-44e0-b783-3b74a2439a11",
"value": "Thomas"
}
],
"log": [{
"id": "5cfa35e1-e453-42e2-99b1-eb64cd853f22",
"value": "{\"user\":\"user#user.dk\",\"action\":\"update\",\"timestamp\":\"22-03-2017\",\"field\":\"firstName\",\"oldValue\":\"Marco\"}"
}
]
}
}
These log entries can easily be read, deserialised, used, and presented, but will not do much for queriability.
I have an internal app that uses a webhook listener and some scripting to manipulate the input data. I'm posting this to it:
curl -X POST -d '{
"assignment_id": 12345,
"updated_custom_fields": [{
"name": "RNVIDAYEBB",
"value": "updated!"
},
{
"name": "QUFTXSIBYA",
"value": "and me too"
}
],
"custom_fields": [{
"id": 981,
"name": "RDEXDPVKRD",
"fields": [
{
"id": 4096,
"name": "RNVIDAYEBB",
"default": "EDJEAJICYW",
"required": true,
"value": "Blah"
},
{
"id": 4097,
"name": "QUFTXSIBYA",
"default": "",
"required": true,
"value": ""
}]
}]
}' "https://hooks.zapier.com/hooks/catch/......"
My script is as follows:
update_custom_fields_by_name_pre_write: function(bundle) {
var updatedFields = _.map(bundle.request.data.custom_fields, function(group) {
return _.map(group.fields, function(field) {
return _.extend(field, _.findWhere(bundle.request.data.updated_custom_fields, { name: field.name} ));
});
});
bundle.request.data = updatedFields;
return bundle.request;
}
I know that the merging logic is good, but it appears that the custom_fields and updated_custom_fields arrays are not present in the bundle.request.data object. Anyone know how to get access to them in the script?
It seems like you should be using update_custom_fields_by_name_catch_hook to capture the incoming static webhook data (instead of _pre_write). If you use that, you can capture the data within bundle.cleaned_request.custom_fields and bundle.cleaned_request.updated_custom_fields.
When running exportEntities, breeze does some magic to flatten entities, by removing their properties and popping them in to arrays. In entities with only simple properties, this prevents circular references as the entityAspect is not copied across. However, complex properties are not flattened and still have this entityAspect, leading to a circular structure exception when breeze attempts to call JSON.stringify();
Here's some code to replicate this issue:
var jsonMetadata = {
"metadataVersion": "1.0.3",
"dataServices": [
{
"serviceName": "api/Foo/",
"hasServerMetadata": false,
"jsonResultsAdapter": "webApi_default",
"useJsonp": false
}
],
"structuralTypes": [
{
"shortName": "address",
"namespace": "YourNamespace",
"isComplexType": true,
"dataProperties": [
{ "name": "street", "dataType": "String" },
{ "name": "city", "dataType": "String" },
{ "name": "country", "dataType": "String" }
]
},
{
"shortName": "person",
"namespace": "YourNamespace",
"dataProperties": [
{ "name": "id", "dataType": "Int32", isPartOfKey: true },
{ "name": "name", "dataType": "String" },
{ "name": "hobbies", "dataType": "String" },
{ "name": "address", "complexTypeName": "address:#YourNamespace" }
] }
]
};
var manager = new breeze.EntityManager();
manager.metadataStore.importMetadata(jsonMetadata)
var person = manager.createEntity('person', {id: 1});
person.address.street = "Sample Street";
console.log("Complex property is a circular datatype, cannot convert to JSON - that's fine")
// JSON.stringify(person.address); // fails with error
console.log("... except that manager.exportEntities() doesn't handle that case!");
manager.exportEntities(); // also fails
Edit: woops, poor form, left out the question: is this a bug?
This was a bug and has been fixed in Breeze v 1.3.5, available now via nuget and the Breeze web site.
and ... good repro. We appreciate it.