How do I exclude a (non-reframe) atom from re-frame processing e.g with o'doyle rule engine? - re-frame

Clojure: 1.10.0
clojurescript: "1.10.764
shadow-cljs "2.11.7"
re-frame "1.2.0"
I am creating a SPA using re-frame, and everything is working well. However, I recently watched a video on o'Doyle rules engine and I thought it might be fun to try experimenting with this in my re-frame project. It creates a separate "facts table" in its own atom:
(ns cube-test.twizzlers.rules
(:require
[re-frame.core :as re-frame]
[odoyle.rules :as o]))
(def rules
(o/ruleset
{::print-time
[:what
[::time ::total tt]
:then
(println "upate time rule:" tt)]}))
;; create session and add rule
(def ^:dynamic *session
(atom (reduce o/add-rule (o/->session) rules)))
(defn update-time []
(swap! *session
(fn [session]
(-> session
(o/insert ::time ::total 100)
o/fire-rules))))
I then created a button to activate the update:
[:button.user-action {:on-click #(re-frame/dispatch [::twiz.events/update-time])} "update time rule"]
The rule fires and everything works well, however I see a bunch of re-frame warnings on the console suggesting that re-frame is trying to hook and manage the Odoyle atom as one of it's own:
"twizzler.events.update-time: entered" cljs.core.js:168:20
upate time rule: 100 cljs.core.js:168:20
re-frame: no handler registered for effect:
Object { ns: null, name: "alpha-node", fqn: "alpha-node", _hash: 1411603897, "cljs$lang$protocol_mask$partition0$": 2153775105, "cljs$lang$protocol_mask$partition1$": 4096 }
. Ignoring. cljs.core.js:13285:10
re-frame: no handler registered for effect:
Object { ns: null, name: "beta-nodes", fqn: "beta-nodes", _hash: -674152665, "cljs$lang$protocol_mask$partition0$": 2153775105, "cljs$lang$protocol_mask$partition1$": 4096 }
. Ignoring. cljs.core.js:13285:10
re-frame: no handler registered for effect:
Object { ns: null, name: "last-id", fqn: "last-id", _hash: -1231616450, "cljs$lang$protocol_mask$partition0$": 2153775105, "cljs$lang$protocol_mask$partition1$": 4096 }
. Ignoring. cljs.core.js:13285:10
re-frame: no handler registered for effect:
Object { ns: null, name: "rule-name->node-id", fqn: "rule-name->node-id", _hash: -494456865, "cljs$lang$protocol_mask$partition0$": 2153775105, "cljs$lang$protocol_mask$partition1$": 4096 }
. Ignoring. cljs.core.js:13285:10
re-frame: no handler registered for effect:
Object { ns: null, name: "node-id->rule-name", fqn: "node-id->rule-name", _hash: 1615893599, "cljs$lang$protocol_mask$partition0$": 2153775105, "cljs$lang$protocol_mask$partition1$": 4096 }
. Ignoring. cljs.core.js:13285:10
re-frame: no handler registered for effect:
Object { ns: null, name: "id-attr-nodes", fqn: "id-attr-nodes", _hash: -1814751183, "cljs$lang$protocol_mask$partition0$": 2153775105, "cljs$lang$protocol_mask$partition1$": 4096 }
. Ignoring. cljs.core.js:13285:10
re-frame: no handler registered for effect:
Object { ns: null, name: "then-queue", fqn: "then-queue", _hash: 899186975, "cljs$lang$protocol_mask$partition0$": 2153775105, "cljs$lang$protocol_mask$partition1$": 4096 }
. Ignoring. cljs.core.js:13285:10
re-frame: no handler registered for effect:
Object { ns: null, name: "then-finally-queue", fqn: "then-finally-queue", _hash: 2088468149, "cljs$lang$protocol_mask$partition0$": 2153775105, "cljs$lang$protocol_mask$partition1$": 4096 }
. Ignoring. cljs.core.js:13285:10
"then-finally-queue" and "alpha-node" et. al are presumably o'doyle life cycle events on it's atom, and it looks like re-frame is trying to find handlers for them.
This question isn't really about making odoyle rule engine and re-frame work together, although if anyone has opinions about the plausibility of this scenario I'd be interested in hearing them (yes, there's an overlap between the frameworks, but I would regard the odoyle "fact table" as simply an alternate view that I would manage with re-frame subscriptions).
Anyhow, to make it clearer that this question isn't simply about odoyle, or an effect of odoyle's design, I created a generic atom like so:
(def ^:dynamic *a* (atom 10))
(defn update-atom []
(swap! *a* inc))
and when I drive it, it generates:
"twizzler.events.update-dmy-atom: entered" cljs.core.js:168:20
Uncaught Error: No protocol method IMap.-dissoc defined for type number: 12
cljs$core$missing_protocol https://localhost:8281/js/compiled/cljs-runtime/cljs.core.js:312
cljs$core$IMap$_dissoc$dyn_41386 https://localhost:8281/js/compiled/cljs-runtime/cljs.core.js:2213
cljs$core$_dissoc https://localhost:8281/js/compiled/cljs-runtime/cljs.core.js:2224
cljs$core$IFn$_invoke$arity$2 https://localhost:8281/js/compiled/cljs-runtime/cljs.core.js:6832
re_frame$fx$do_fx_after https://localhost:8281/js/compiled/cljs-runtime/re_frame.fx.js:40
re_frame$interceptor$invoke_interceptor_fn https://localhost:8281/js/compiled/cljs-runtime/re_frame.interceptor.js:216
re_frame$interceptor$invoke_interceptors https://localhost:8281/js/compiled/cljs-runtime/re_frame.interceptor.js:255
re_frame$interceptor$execute https://localhost:8281/js/compiled/cljs-runtime/re_frame.interceptor.js:365
re_frame$events$handle https://localhost:8281/js/compiled/cljs-runtime/re_frame.events.js:85
re_frame$router$IEventQueue$_process_1st_event_in_queue$arity$1 https://localhost:8281/js/compiled/cljs-runtime/re_frame.router.js:580
re_frame$router$IEventQueue$_run_queue$arity$1 https://localhost:8281/js/compiled/cljs-runtime/re_frame.router.js:325
vec__42331 https://localhost:8281/js/compiled/cljs-runtime/re_frame.router.js:417
re_frame$router$IEventQueue$_fsm_trigger$arity$3 https://localhost:8281/js/compiled/cljs-runtime/re_frame.router.js:459
G__42328 https://localhost:8281/js/compiled/cljs-runtime/re_frame.router.js:370
NextJS 3
day8$re_frame_10x$inlined_deps$re_frame$v0v12v0$re_frame$router$IEventQueue$_run_next_tick$arity$1 https://localhost:8281/js/compiled/cljs-runtime/day8.re_frame_10x.inlined_deps.re_frame.v0v12v0.re_frame.router.js:372
vec__42751 https://localhost:8281/js/compiled/cljs-runtime/day8.re_frame_10x.inlined_deps.re_frame.v0v12v0.re_frame.router.js:478
day8$re_frame_10x$inlined_deps$re_frame$v0v12v0$re_frame$router$IEventQueue$_fsm_trigger$arity$3 https://localhost:8281/js/compiled/cljs-runtime/day8.re_frame_10x.inlined_deps.re_frame.v0v12v0.re_frame.router.js:549
day8$re_frame_10x$inlined_deps$re_frame$v0v12v0$re_frame$router$IEventQueue$push$arity$2 https://localhost:8281/js/compiled/cljs-runtime/day8.re_frame_10x.inlined_deps.re_frame.v0v12v0.re_frame.router.js:363
day8$re_frame_10x$inlined_deps$re_frame$v0v12v0$re_frame$router$dispatch https://localhost:8281/js/compiled/cljs-runtime/day8.re_frame_10x.inlined_deps.re_frame.v0v12v0.re_frame.router.js:699
day8$re_frame_10x$db$init_db https://localhost:8281/js/compiled/cljs-runtime/day8.re_frame_10x.db.js:24
day8$re_frame_10x$init_db_BANG_ https://localhost:8281/js/compiled/cljs-runtime/day8.re_frame_10x.js:352
<anonymous> https://localhost:8281/js/compiled/cljs-runtime/day8.re_frame_10x.preload.js:3
globalEval https://localhost:8281/js/compiled/app.js:597
evalLoad https://localhost:8281/js/compiled/app.js:1690
<anonymous> https://localhost:8281/js/compiled/app.js:2162
app.js line 597 > eval:312:9
IOTW, re-frame is trying to "manage" this atom as well.
It looks like re-frame hooks all atom processing functions not just its own app-db ratom?
Is there a way I can mark an atom to exclude it from re-frame's hooks? Or will I be forced to create a non-re-frame project to experiment with odoyle? Note: odoyle has an adapter for Rum, so it can work with other reactive frameworks.

Regarding the superfluous messages:
This is an "answer" from the original poster. Since I received no responses, I'll just explain what I discovered and how I dealt with the situation. I do not claim to have any expert knowledge of re-frame beyond what I know as a basic user of the framework.
In looking at the source code for re-frame (which is amazingly quite small, and not very complicated BTW), fx.cljs seems to be the source of all the messages I see. I can see that in fact it has a lot of interceptors:
(def do-fx
(->interceptor
:id :do-fx
:after (fn do-fx-after
After thinking about it, it makes sense that the hooking and intercepting is tied to the code and not to the atom i.e. you typically have one copy of the code loaded to deal with n atoms.
My main concerns about re-frame hooking my non-reframe atoms are twofold: performance and interference. As far as performance goes, this is just re-frame doing a simple check for a handler, not finding it, and printing a warning message. Since state changes are at app level and not on the animation tick (my project is a Babylon.js 3d app), this is not a big issue. Indeed, to add a lookup table of what atoms to monitor or not, while also not a big performance hit, would, if anything, decrease the performance even further. So no big deal to have these messages.
I also realize that re-frame should not be interfering with the non-reframe atom at all. Indeed, these messages are an indication that re-frame is unable to intercept the native atom. So once again nothing to worry about.
So I was able to convince myself that these messages are really nothing to worry about. However, the specter of having a lot of superfluous wordy message clogging up my console was annoying.
To deal with getting rid of all the messages, I made a call to re-frame's set-loggers! call, where I used a regex to filter the superfluous messages, something like:
(defn rf-odoyle-warn-override-logger [& args]
(let [text (apply str args)]
; (prn "utils.rf-odoyle-warn-override-logger: text=" text)
(if (re-matches #".*no handler registered for effect:.*Ignoring.*" text)
(js/console.log "probable rf-odoyle interceptor no handler msg warning detected")
(js/console.warn text))))
Invoked like this in my startup:
(re-frame.core/set-loggers! {:warn utils/rf-odoyle-warn-override-logger})
Now, I just get one simple "collapsed" message in the console:
probable rf-odoyle interceptor no handler msg warning detected
On using re-frame and o'doyle together:
vis a vis the viability of using o'doyle rules in re-frame, I see great promise for me since my main "view" is a babylon.js scene and not the DOM. Thus I need to do a lot of game level logic outside of the DOM, where I think O-doyle will complement re-frame. If your app is a basic Web app where the DOM is your main view, then I think it might feel kind of redundant to attempt to maintain odoyle state on top of the re-frame state (e.g. in app.db).

Related

Firebase Messaging fails sporadically with internal-error

I've been getting internal error quite a lot this week while sending messages to my iOS devices through the Node.js library (code is the same, same library version etc.)
It's hard to debug because sometimes it works. When I put a for loop to send 10 messages, my devices would get 3-4.
FirebaseMessagingError: Internal error encountered.
> at FirebaseMessagingError.FirebaseError [as constructor] (/node_modules/firebase-admin/lib/utils/error.js:42:28)
> at FirebaseMessagingError.PrefixedFirebaseError [as constructor] (/node_modules/firebase-admin/lib/utils/error.js:88:28)
> at new FirebaseMessagingError (/node_modules/firebase-admin/lib/utils/error.js:254:16)
> at Function.FirebaseMessagingError.fromServerError (/node_modules/firebase-admin/lib/utils/error.js:287:16)
> at Object.createFirebaseError (/node_modules/firebase-admin/lib/messaging/messaging-errors.js:34:47)
> at FirebaseMessagingRequestHandler.buildSendResponse (/node_modules/firebase-admin/lib/messaging/messaging-api-request.js:119:47)
> at /node_modules/firebase-admin/lib/messaging/messaging-api-request.js:94:30
> at Array.map (<anonymous>)
> at /node_modules/firebase-admin/lib/messaging/messaging-api-request.js:93:30
> at processTicksAndRejections (internal/process/task_queues.js:97:5) {
> errorInfo: {
> code: 'messaging/internal-error',
> message: 'Internal error encountered.'
> },
> codePrefix: 'messaging'
> }
I tried changing the auth key, but still getting errors.
Code is very simple
import * as admin from 'firebase-admin'
admin.initializeApp()
async function sendPushNotification(
tokens: string[],
title: string,
body: string
): Promise<admin.messaging.BatchResponse> {
console.log('sending %s to %d devices', body, tokens.length)
const message = {
notification: {
title: title,
body: body,
},
tokens: tokens,
apns: {
payload: {
aps: {
sound: 'default',
},
},
},
}
return admin.messaging().sendMulticast(message)
}
I have been having the same problem: node.js firebase admin SDK sending notifications to iOS occasionally fails with 500/ISE.
It seems specific to, or at least more common with, iOS as during Android development I never had this issue. I contacted Firebase support and here is what they said:
Internal Server Error are usually due to timeouts. Some minor hiccups can't be avoided that's why we recommend developers to implement exponential back-off retry mechanism. You can refer to this StackOverflow discussion for more information on retry-after and exponential back offs.
It seems like a good idea to build in some retry support on my side in any case. I haven't used it before but I like the look of the cockatiel module on npm. I'm planning on going with the retryWithBreaker example given at the start of their README, just with the backoff attempts and breaker set higher, maybe 5 and 20 respectively.

Symfony - No Extension Is Able To Load The Configuration (Subscriber/Event Listening) (REST API Exception Handling with JSON output)

Purpose
Hello, I am fairly new to Symfony and am trying to create an exception/error handling functionality for our Web API.
Requirements
When a user makes an invalid request to our API, we want to return a simple JSON object that looks something like the following:
{"errorCode": 1234567, "errorMessage": "Parameter 1 is invalid. String expected"}
In addition to API-specific errors, we also want to hook into the built-in exception-handling in Symfony and re-use that, but instead of returning an HTML error page (which is what happens by default), we want to return a JSON object like:
{"errorCode": 404, "errorMessage": "Not found"}
Current Approach
Obviously, we want this to be implemented in the most efficient way, so after doing some research, I found what I think is a great approach which I can then adapt to our specific needs. Here is the tutorial I followed:
https://knpuniversity.com/screencast/symfony-rest2
I ended up buying this course in order to get access to the full code. The issue is that it was written for Symfony 2, but we are running Symfony 3.3, so some things are outdated and I have not been able to get the example running yet.
Code
So, I have posted some of the relevant code below. (I obviously cannot post the whole code, but have posted what are hopefully the relevant portions of the publicly-available code).
services.yml (Their version, Symfony 2 Style)
api_exception_subscriber:
class: AppBundle\EventListener\ApiExceptionSubscriber
arguments: []
tags:
- { name: kernel.event_subscriber }
services.yml (my full file with all comments removed)
This includes a modified version of what they have above (hopefully this is the correct Symfony 3.3 way of doing things).
NOTE: Anything that was part of the private code which I cannot show, I have replaced with the word "something".
parameters:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
AppBundle\:
resource: '../../src/AppBundle/*'
exclude: '../../src/AppBundle/{Entity,Repository,Tests}'
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
AppBundle\EventListener\ApiExceptionSubscriber:
arguments:
- ['%something.something%']
tags:
- {name: 'kernel.event_subscriber'}
routing.yml (relevant section of their file)
app_api:
resource: "#AppBundle/Controller/Api"
type: annotation
defaults:
_format: json
routing.yml (my full file) - NOTE: I had to add the "_format: json" directly to the "app" section here rather than "app_api" because in our REST API, all of our URLs are at the root level, and do NOT have to be prefixed with "api" like http://localhost/api/someMethod/someMethodParameter as in the tutorial's code
app:
resource: '#AppBundle/Controller/'
type: annotation
defaults: {_format:json}
yml_route:
path: /yml-route
defaults:
_controller: AppBundle:Default:yml
awesome_route:
path: /awesome-route/{ic}
defaults:
_controller: AppBundle:Rest:awesomeRoute
src/AppBundle/RestController.php (edited to show just the basic structure)
<?php
namespace AppBundle\Controller;
use FOS\RestBundle\Controller\Annotations as Rest;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\HttpFoundation\Request;
class RestController extends Controller {
public function doSomething($id)
{
// Code to populate $result with data from database is here
return new JsonResponse($result);
}
// This file contains many different functions similar to doSomething() which retrieve the request for the API caller.
// For example, doSomething() can be accessed via http://localhost/app_dev.php/doSomething/123
}
src/AppBundle/Api/ApiProblem.php (edited to show just the basic structure)
namespace AppBundle\Api;
use Symfony\Component\HttpFoundation\Response;
class ApiProblem
{
// Code relevant to this class is in here
}
src/AppBundle/Api/ApiProblemException.php (edited to show just the basic structure)
<?php
namespace AppBundle\Api;
use Symfony\Component\HttpKernel\Exception\HttpException;
class ApiProblemException extends HttpException
{
private $apiProblem;
public function __construct($severalParametersWhichICannotListHereBecauseCodeIsPrivate) {
// Does some stuff and then calls the parent:__construct
// Includes a getter for $apiProblem
}
}
src/AppBundle/EventListener/ApiExceptionSubscriber.php (edited to show just the basic structure)
<?php
namespace AppBundle\EventListener;
use AppBundle\Api\ApiProblem;
use AppBundle\Api\ApiProblemException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\KernelEvents;
class ApiExceptionSubscriber implements EventSubscriberInterface
{
// Code relevant to this subscriber is here
}
The Issue
I'm getting the following error when I try to run any type of command from the command-line bin/console:
C:\htdocs\projects\myproject>php bin/console debug:container
PHP Fatal error: Uncaught Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: T
here is no extension able to load the configuration for "AppBundle\EventListener\ApiExceptionSubscribe
r" (in C:\htdocs\projects\myproject\app/config\services.yml). Looked for namespace "AppBundle\EventListe
ner\ApiExceptionSubscriber", found "framework", "security", "twig", "monolog", "swiftmailer", "doctrin
e", "sensio_framework_extra", "demontpx_parsedown", "doctrine_cache", "doctrine_migrations", "debug",
"web_profiler", "sensio_distribution", "web_server" in C:\htdocs\projects\myproject\vendor\symfony\symfo
ny\src\Symfony\Component\DependencyInjection\Loader\YamlFileLoader.php:644
Stack trace:
#0 C:\htdocs\projects\myproject\vendor\symfony\symfony\src\Symfony\Component\DependencyInjection\Loader\
YamlFileLoader.php(614): Symfony\Component\DependencyInjection\Loader\YamlFileLoader->validate(Array,
'C:\\htdocs\\proje...')
#1 C:\htdocs\projects\myproject\vendor\symfony\symfony\src\Symfony\Component\DependencyInjection\Loader\
YamlFileLoad in C:\htdocs\projects\myproject\vendor\symfony\symfony\src\Symfony\Component\Config\Loader\
FileLoader.php on line 179
Fatal error: Uncaught Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: There
is no extension able to load the configuration for "AppBundle\EventListener\ApiExceptionSubscriber" (i
n C:\htdocs\projects\myproject\app/config\services.yml). Looked for namespace "AppBundle\EventListener\A
piExceptionSubscriber", found "framework", "security", "twig", "monolog", "swiftmailer", "doctrine", "
sensio_framework_extra", "demontpx_parsedown", "doctrine_cache", "doctrine_migrations", "debug", "web_
profiler", "sensio_distribution", "web_server" in C:\htdocs\projects\myproject\vendor\symfony\symfony\sr
c\Symfony\Component\Config\Loader\FileLoader.php on line 179
Symfony\Component\Config\Exception\FileLoaderLoadException: There is no extension able to load the con
figuration for "AppBundle\EventListener\ApiExceptionSubscriber" (in C:\htdoc
fig\services.yml). Looked for namespace "AppBundle\EventListener\ApiExceptio
work", "security", "twig", "monolog", "swiftmailer", "doctrine", "sensio_fra
arsedown", "doctrine_cache", "doctrine_migrations", "debug", "web_profiler",
eb_server" in C:\htdocs\projects\myproject\app/config\services.yml (which is b
ocs\projects\myproject\app/config\config.yml"). in C:\htdocs\projects\myproject\
\Symfony\Component\Config\Loader\FileLoader.php on line 179
Call Stack:
1.4353 6149416 1. Symfony\Component\Debug\ErrorHandler->handleExcep
myproject\vendor\symfony\symfony\src\Symfony\Component\Debug\ErrorHandler.php:
What I've Tried
I've been debugging this for the last several days and have read many related discussions on Stack Overflow. I'm fairly new to services, subscribers, and dependency injection and I've tried editing both the routes.yml and services.yml numerous times. I was also getting some circular reference exceptions before but think I've fixed those now. I was hoping someone could please provide me with some direction and hopefully help me turn this into a working example on Symfony 3.3 that I can learn from. If you need any additional detail, please let me know.
From what I've learned, autowiring/autoconfiguring of services in Symfony 3.3 seems to be new and I think that may be impacting things but I'm not sure. I did try turning both of those settings off in services.yml though but with no luck.

Cumulocity - update fragment of managedObject

In our application, when we create a new object via the API, we send SIM and GSM module-related information within the c8y_Mobile fragment. The object models an embedded device with limited capabilities, so we make use of the HTTPS API directly.
PUT /inventory/managedObjects/myid HTTP/1.1
Host: mytenant.cumulocity.com
Authorization: Basic ....
Content-Type: application/vnd.com.nsn.cumulocity.managedObject+json
Accept: application/vnd.com.nsn.cumulocity.managedObject+json
{
"c8y_Mobile": {
"imei": 1234567890123456,
"imsi": 23456789011234567890,
"iccid": 01234567890123456789
...
}
}
The managed object shows the new fragment as expected:
...
"c8y_IsDevice": {},
"c8y_Mobile": {
"imei": 1234567890123456,
"imsi": 23456789011234567890,
"iccid": 01234567890123456789
...
},
...
When a user changes the SIM card on the embedded unit, IMSI and ICCID properties should be updated within the managedObject c8y_Mobile fragment. But if we send only those properties the whole fragment is overridden:
PUT /inventory/managedObjects/myid HTTP/1.1
Host: mytenant.cumulocity.com
Authorization: Basic ....
Content-Type: application/vnd.com.nsn.cumulocity.managedObject+json
Accept: application/vnd.com.nsn.cumulocity.managedObject+json
{
"c8y_Mobile": {
"imsi": 23456789011234567890,
"iccid": 01234567890123456789
}
}
So the managed object shows this:
...
"c8y_IsDevice": {},
"c8y_Mobile": {
"imsi": 23456789011234567890,
"iccid": 01234567890123456789
},
...
Please note that the imei property and others have been lost and are not longer present in the managed object.
In order to save data and minimise transactions I would like to know if there is a way to update fragments without having to send all the desired properties again.
I've tried to use HTTP POST instead of PUT, but that gives me a method not allowed error, as stated in the documentation.
There is no direct way to do that (but a workaround).
In general when you do a PUT on any object it will be merged only on the root level of the JSON meaning if your PUT contains c8y_Mobile it will replace the current c8y_Mobile (regardless of what is contained).
Here is what you can do:
First you invent so new fragments that you use as a temporary fragment:
PUT /inventory/managedObjects/myid HTTP/1.1
Host: mytenant.cumulocity.com
Authorization: Basic ....
Content-Type: application/vnd.com.nsn.cumulocity.managedObject+json
Accept: application/vnd.com.nsn.cumulocity.managedObject+json
{
"c8y_Mobile_imsi": "23456789011234567890",
"c8y_Mobile_iccid": "01234567890123456789"
}
Additionally you create an event processing rule that when you update for example "c8y_Mobile_imsi" it will merge this value into the existing c8y_Mobile fragment (preserving the other sub-fragments).
Important:
You either send the PUT as transient (so these values are not persisted in the device object) or your rule removes the temporary fragment immediately (in the same update operation like the merge with c8y_Mobile).
This is important because in CEP you do not know which fragment was updated when you listen to ManagedObjectUpdated. So if you would keep the temporary fragment in the device object the rule would trigger in an endless loop (which would lead to an automatic undeploy of the rule).

What's the easiest way to display content depending on the URL parameter value in DocPad

I'd like to check for an URL parameter and then display the confirmation message depending on it.
E.g. if I a GET request is made to /form?c=thankyou docpad shows the form with thank you message
I think there is two basic ways to do this.
look at the url on the server side (routing) and display differing content according to URL parameters
Look at the parameter on the client side using JavaScript and either inject or show a dom element (eg div) that acts as a message box.
To do this on the server side you would need to intercept incoming requests in the docpad.coffee file in the serverExtend event. Something like this:
events:
# Server Extend
# Used to add our own custom routes to the server before the docpad routes are added
serverExtend: (opts) ->
# Extract the server from the options
{server} = opts
docpad = #docpad
# As we are now running in an event,
# ensure we are using the latest copy of the docpad configuraiton
# and fetch our urls from it
latestConfig = docpad.getConfig()
oldUrls = latestConfig.templateData.site.oldUrls or []
newUrl = latestConfig.templateData.site.url
server.get "/form?c=thankyou", (req,res,next) ->
document = docpad.getCollection('documents').findOne({relativeOutPath: 'index.html'});
docpad.serveDocument({
document: document,
req: req,
res: res,
next: next,
statusCode: 200
});
Similar to an answer I gave at how to handle routes in Docpad
But I think what you are suggesting is more commonly done on the client side, so not really specific to Docpad (assumes jQuery).
if (location.search == "?c=thankyou") {
$('#message-sent').show();//show hidden div
setTimeout(function () {
$('#message-sent').fadeOut(1000);//fade it out after a period of time
}, 1000);
}
This is a similar answer I gave in the following Docpad : show error/success message on contact form
Edit
A third possibility I've just realised is setting the document to be dynamically generated on each request by setting the metadata property dynamic = true. This will also add the request object (req) to the template data passed to the page. See Docpad documentation on this http://docpad.org/docs/meta-data.
One gotcha that gets everyone with setting the page to dynamic is that you must have the docpad-plugin-cleanurls installed - or nothing will happen. Your metadata might look something like this:
---
layout: 'default'
title: 'My title'
dynamic: true
---
And perhaps on the page (html.eco):
<%if #req.url == '/?c=thankyou':%>
<h1>Got It!!!</h1>
<%end%>

Ember-auth signin test fails with json

I am having some issues with testing my signin/signout and related features of my app. The app works, but the test fail.
For testing, I use a QUnit with testem (I also tried teaspoon)
test "after signin, should redirect user back to previous page", ->
visit '/library'
fillIn '.signin-email', 'example#example.com'
fillIn '.signin-password', 'examplepass'
click '.signin-btn'
andThen ->
equal(testing().path(), 'library', "Should redirect back to library (was #{testing().path()})")
After running the test, I get a failure:
(screenshot here )
Authentication: visiting restricted page as non authenticated user: after signin, should redirect user back to previous page (2, 0, 2)Rerun544 ms
{user_id: 2, auth_token: wVveiyDLuXBXu69pQ2XQwg}
Source:
at Test.QUnitAdapter.Test.Adapter.extend.exception (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:50149:5)
at superWrapper [as exception] (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:13374:16)
at Object.onerror (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:50009:22)
at onerror (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20453:16)
at EventTarget.trigger (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20286:22)
at null.<anonymous> (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20439:14)
at EventTarget.trigger (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20286:22)
at http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20588:17
Should redirect back to library (was signin)
Expected:
"library"
Result:
"signin"
Diff:
"library" "signin"
Source:
at http://localhost:7357/public/assets/spec/javascripts/integration/authentication_pages_spec.js.js:22:14
at andThen (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:50258:20)
at http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:49817:21
at isolate (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:49989:14)
at http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:49972:12
at invokeCallback (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20463:19)
at null.<anonymous> (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20513:11)
Also, auth.coffee:
App.Auth = Em.Auth.extend
request: 'jquery'
response: 'json'
strategy: 'token'
session: 'cookie'
modules: [
'emberData',
'authRedirectable',
'actionRedirectable'
]
signInEndPoint: '/signin'
signOutEndPoint: '/signout'
tokenKey: 'auth_token'
tokenIdKey: 'user_id'
tokenLocation: 'param'
emberData:
userModel: 'user' # create user model on login
authRedirectable:
route: 'signin'
actionRedirectable:
signInRoute: 'library'
# signInSmart: true
# signInBlacklist: ['signin']
signOutRoute: 'index'
I am unable to find the source of the error, so maybe it is something to do with ember-auth. Any ideas would be very appreciated.
Update 1 [Jan 4th]:
I've written an additional test, which passes only halfway. The test is simpler than the previous in that it does not check a redirect, but only checks that the user name appears in the UI after signin.
test "after signin, TEST", ->
visit '/library'
fillIn '.signin-email', 'user#example.com'
fillIn '.signin-password', 'foobargaz'
click '.signin-btn'
andThen ->
ok exists('.menu-signout'), "signout button exists"
The assertions passes, but I get an additional error reporting the returned JSON as seen in this screenshot. The screenshot basically shows:
[Fail] {user_id: 2, auth_token: wVveiyDLuXBXu69pQ2XQwg}
[Pass] signout button exists
Additionally, I've also run the tests by mocking the ajax requests with mockjax, but with the same failure.
Third, I should note that I had to patch "ember-auth-request-jquery.js" to make it work with ember testing as suggested here
I'm pretty sure you're failing to wait on the first visit to happen, so here's how I read it (I'm no CS person)
You're telling Ember to go to library
Before being sure it's finished navigating you're trying to fill in 2 fields and click a button (all of which probably doesn't exist)
then you check to see if it's library, but while waiting after you thought you clicked, really the page finishes rendering the login page from the visit
Here's what js2coffe says it'd kind of look like (my main point is the then after the visit).
test "after signin, should redirect user back to previous page", ->
visit("/library").then ->
fillIn ".signin-email", "example#example.com"
fillIn ".signin-password", "examplepass"
click(".signin-btn").then ->
equal testing().path(), "library", "Should redirect back to library (was " + (testing().path()) + ")"
Update 1/4: Documentation changed on me
Now we move to educated guess time. Looking through the Ember-auth code it might not be creating any timers/promises that Ember is aware of, in affect Ember thinks it's finished the signin process immediately. So the click promise is resolved immediately and you run your test immediately (andThen waits on the global testing promise to resolve). To test the theory you can do some terrible timeout and see if it does indeed redirect after some time
test "after signin, should redirect user back to previous page", ->
visit "/library"
fillIn ".signin-email", "example#example.com"
fillIn ".signin-password", "examplepass"
click ".signin-btn"
stop()
Ember.run.later this, (->
start()
equal testing().path(), "library", "Should redirect back to library (was " + (testing().path()) + ")"
), 5000
It turns out my coffeescript was not the best in the world.
The module function in QUnit should NOT compile to:
module('Authentication: visiting restricted page as non authenticated user', function() {
return setup(function() {
return Em.run(App, App.advanceReadiness);
});
});
but to:
module('Authentication: visiting restricted page as non authenticated user', {
setup: function() {
Ember.run(App, App.advanceReadiness);
},
// This is also new
teardown: function() {
App.reset();
}
});
Additionally, in my spec_helper.coffee file I had something like this:
QUnit.testStart(function() {
// FIXME: this below made it fail every time
// Ember.run(function() {
// return App.reset();
// });
Ember.testing = true;
});
QUnit.testDone(function() {
Ember.testing = false;
});
QUnit.done(function() {
return Ember.run(function() {
return App.reset();
});
});
which seems to have caused some issues, so I just deleted it and the tests now pass.