Template flick when modifying subscription - meteor

I have a Meteor template that renders the documents of a collection. The subscription is set up in the template's autorun:
Template.userSearch.rendered = ->
Session.set "userSearchLimit", 5
#autorun ->
Meteor.subscribe "userSearch", "something to find", Session.get "userSearchLimit"
When the user pressed a "Load more" button I increment the userSearchLimit session variable, which causes the autorun to rerun. The subscription changes since I ask for more data, so the old subscription will be torn down and a new one created, however the first part of the actual data will be the same.
The problem is that the entire list is being redrawn, causing a horrible flicker. When I put debug logging into the find() method as suggested here, then I see that the documents are first being removed, then added back again.
I would have expected the server to avoid resending data that already exists on the client.
How can I solve this?
To further confuse the issue I also tried using ddp-analyzer to see what data was being sent. As soon as I use that then only the new data gets sent and the flicker is eliminated. As soon as I stop using it the problem comes back.

I've solved this by manually waiting for the new subscription to be ready before taking down the previous one:
currentMatchingSub = null
prevMatchingSub = null
subscribeToUserSearch = (limit) ->
prevMatchingSub = currentMatchingSub
currentMatchingSub = Meteor.subscribe "userSearch", "john baker", limit, ->
# Only once the new subscription is ready should be take down the previous one. This ensure's
# that there's no flicker...
prevMatchingSub.stop() if prevMatchingSub?
prevMatchingSub = null
Template.userSearch.rendered = ->
limit = 5
Session.set "userSearchLimit", limit
subscribeToUserSearch limit
Template.userSearch.destroyed = ->
prevMatchingSub.stop() if prevMatchingSub?
currentMatchingSub.stop() if currentMatchingSub?

Related

Deleting user account triggers deletion of another nodes

Db structure:
--followers
-followedUser1
-user1
-followedUser2
-user1
-user2
--users
-user1
-followed
-followedUser1
-followedUser2
-user2(followedUser1)
-followed
-followedUser2
-user3(followedUser2)
Everytime user follows(onCreate) & unfollows(onDelete) under followers/{followedUser}/{followerUser} path, it triggers function which increment or derement count and assigning or detaching posts from follower. It's working via fanout method, and there're no problems. Now, worse part comes when some user deletes account completely together with detaching his followers from himself(because his account will be a ghost), i've set trigger onDelete to indicate whenever it'll happen, then iterating through this user's (i.e.user3) followers removes himself from corresponding followers plus his account, it looks like this then:
--followers
-followedUser1
-user1
-followedUser2
-user1
-user2
--users
-user1
-followed
-followedUser1
-user2(followedUser1)
Now, problematic part - when promise returns i'd like to also remove whole follower/followedUser2(because it's a ghost now) path but... there is a trigger which unfortunately executes for every follower under(onDelete). So, is there any chance to remove path above(levelup) deletetion trigger without triggering children itself? Or any other approach would be great, thanks
edit: Dont get me wrong, it'll work but if number of followers under followedUser would be "a lot" server will die after 100... trigger
At this time, there is no way to filter what deletion events will trigger the function, so you are correct, the function will be triggered once for each user the deleted user was following. I recognize this is one of many use cases where such functionality would be useful, so if you get a chance, please fill out a feature request here.
It looks like your multiple-update problem can be fixed with a multi-location updates
In very quickly hacked together and not tested typescript:
export const cleanupFollowers = functions.auth.user().onDelete(event => {
const user = event.data.userId;
const followersNode = admin.database().ref(`followers/${user}`);
const followers = _.keys(await followersNode.once('value'));
// Every follower also has a reverse node for this user. Get the list of keys:
const reverseNodesToDelete = followers.map(follower => `followers/${follower}/${user}`);
// Model this update as a map of deep key -> null to delete all at once
let cleanup = _.keyBy(reverseNodesToDelete, null);
// add one more update: deleting full node for the deleted user.
cleanup[`followers/${user}`] = null;
// do all deletions as one database request:
return admin.database().ref().update(cleanup);
}
Note that this will still fire your counting function, but that should be fine to run in parallel. It probably makes your app simpler to have each invariant captured separately.

Cannot add new correlated record to new created record

When I create new record in Google AppMaker and then try to add correlated record to this new one I get this warning in console:
com.google.apps.appmaker.client.datasource.AbstractModelDataSource
WARNING: Could not find element with key RecordKey{key=private$7,
model key=...
Both datasources are set to:
Manual save mode
Automatically load data
The problem doesn't appear when I refresh the page or try to add correlated record to other existing record.
Anybody knows what could be a reason for this error?
App Maker doesn't allow to have unsaved changes on both ends of relation, most likely it is a reason, why you recieve error message in the first case. But in theory it should work once you save one of the relation ends (first save one of the records and then link them and save again):
var countryDs = app.datasources.Country;
var capitalDs = app.datasources.Capital;
countryDs.createItem();
countryDs.item.Name = 'United States';
countryDs.saveChanges(function() {
capitalDs.createItem();
capitalDs.item.Name = 'Washington, D.C.';
capitalDs.item.Country = countryDs.item;
capitalDs.saveChanges();
});
OK, I fixed it.
I have two forms. First to create item. Second to edit data. In the first form page need to be set to:
On Detach: Clear Changes To Datasource.
Datasources need to be set to autosave.

Anywhere re-orders the sequence of the transactions

We noticed that Anywhere is grouping and ordering the transactions such that the transactions for the parent [i.e.: Work Order] were sent first and then the transaction for the child records [e.g.: Specifications]
Scenario:
Step 1. Alter the description on the WO
Step 2. Enter Specification values
Step 3. Change the WO Status to COMP
The resulting transactions are sent as follows
Step1 and Step3 are grouped and sent to Maximo
On success
Step 2 is sent to Maximo
We want the messages to be sent in the same order that they happened and the reason for this is the validations we have in place in Maximo
e.g.: We validate if the child table has records [in our case, we check if the specifications are populated] before we Complete a WO
Due to the re-order of the events\transactions we are unable to COMP a WO from the device as the child transaction never gets to Maximo because the Parent transaction failing due to missing child data [catch 22]
We found the piece of code in the [/MaximoAnywhere/apps/WorkExecution/common/js/platform/model/PushingCoordinatorService.js] JS file that does this re-order and we commented out the reorder
//if (!transaction.json[PlatformConstants.TRANSACTION_LOCK_FORUPDATE])
//{
// Logger.trace("[PUSHING] Trying to shrink/merge transactions and lock transactions");
// var self = this;
// var promise = this._shrinkSubTransactions(metadata, transaction);
//
// Logger.trace("[PUSHING] going to perform async operations");
// promise.then(function() {
// self._pushSubTransactions(transaction, deferred);
// });
//}
//else
//{
Logger.trace("[PUSHING] going to perform async operations");
this._pushSubTransactions(transaction, deferred);
//}
Once this was done we were able to COMP the WO from the device as the events/transaction are now sent in the same order as they occurred
However, we have noticed that this has created another undesirable problem where on an error the device ends up with two Work Orders the one with the error and the one it refetched from Maximo
Scenario: We have an active timer running on the WO and we click on the clock. This will bring up the Stop Timer View and we select [Complete Work]
So there are two things that should happen the timer should be stopped and the status should be changed.
Due to some validation error from Maximo this transaction fails. The result is that we end up with the same wok order twice one with the new status and the error message and one it re-fetched from Maximo
Once we go into the record with the error and undo the change we end up with two identical WOs on the device
Apart from the above issue, there needs to be a way to clear the local data from the device without having to delete the app
You could try putting some Model.save()'s in, or in the app.xml you can force a save when you show/hide a view.
Without the saves I think that everything gets put into one change ... sent as one message ... and you lose control over how it gets unpicked.
Diggging this one up from the grave, but you can create something called a "priority transaction" that will capture all changes and package them in an isolated request and send it back to the server.
westarAssignmentStatusChange:function(workOrder){
workOrder.openPriorityChangeTransaction();
workOrder.set(ATTRIBUTE,VALUE);
workOrder.closePriorityChangeTransaction();
};
This will send an update to the server to change the ATTRIBUTE of the WORKORDER to VALUE.
We used this to isolate changes of specific items and made sure they processed in the appropriate order.

Meteor 1.0 - Hosting a lobby and redirecting other users

I have a problem for which I do not know the solution. I am creating game that involves multiple players. One person "hosts" the game and sends "invitations" to other users to join the "lobby". If you've ever played Call of Duty or any similar game, it's the same concept.
Much of this process currently works properly.
I created a collection called Lobby to keep track of all the open and closed lobbies. When a user wants to host a lobby, he clicks on the button, which creates a new lobby and redirects the user to the proper url:
Template.lobby.events
'click button#host-lobby': (e,t) ->
lobbyId = Lobby.insert
host: Meteor.user()._id
hostName: Meteor.user().username
status: true
players: []
Router.go("currentLobby", _id: lobbyId)
Then, the user can invite other users to the lobby (url) through a modal, which adds a notification object to the invited user's profile. Probably not the best way to do it, so I'm open to suggestions on this front.
Template._playerItem.events
'click button': (e,t) ->
lobbyId = Session.get "currentLobby"
Meteor.call "sendNotification", #_id, lobbyId, Meteor.user().username
Lobby.update
_id: lobbyId
,
$addToSet:
invitedPlayers: #_id
And the method:
Meteor.methods
sendNotification: (userId, lobbyId, hostName) ->
sendTo = Meteor.users.findOne(_id: userId)
Meteor.users.update
_id: userId
,
$push:
invite:
hostName: hostName
lobbyId: lobbyId
So at this point, the user can either accept or decline the invitation. If he accepts, he is routed to the lobby and is added to the players array in the lobby object. The user shows up in the list of players as one would expect.
My issue starts when I attempt to "start" the game. When the button is clicked, the game is created properly and the host (who pressed the button) is routed to the url for the new game:
Template.currentLobby.events
'click #start-game': (e,t) ->
playerIds = [#host]
#players.forEach (player) ->
playerIds.push(player.id)
Meteor.call 'createGame', playerIds
Router.go('home')
The problem is that the other users in the lobby are not redirected. They have access to the game if they manually go to the url, but they aren't taken there. They have no way of knowing that the game has actually started...
One solution is to add a "Game has started" badge, with a link to the game, but I think a more elegant solution is to route all of the users at the current lobby url to the game that was just started.
Is this functionality possible? Is there a better way to host a lobby?
EDIT
Thank you Chet for the solution. This is how I eventually implemented it:
Template.currentLobby.rendered = ->
#autorun ->
data = Template.currentData()
if data.url
Router.go data.url
#autorun had some context difficulties, so I just used the lobby data. Then, when someone clicks on the "start game" button, the current lobby is updated with the url of the new game (The Meteor.call 'createGame' returns the _id of the new game).
Template.currentLobby.events
'click #start-game': (e,t) ->
playerIds = [#host]
lobbyId = Template.currentData()._id
#players.forEach (player) ->
playerIds.push(player.id)
Meteor.call 'createGame', playerIds, (err, res) ->
Lobby.update
_id: lobbyId
,
$set:
url: "/game/#{res}"
Works like a charm. Thanks!
OK. First, to address your notifications. This is a valid solution. However, you'll probably want to sort them by date! You'll also want to create a separate Notifications collection. Anytime you update a document, the whole thing is send over DDP to the client. Thus any new notification added to the profile will result the entire profile being sent to the client. You'll also want some way of marking a notification as being read so it can be deleted.
To address you question about redirecting the lobby, create property called url. Initially it is set to false. Once the host is ready to start the game, they set the url property to the url.
When a user enters a lobby, start an autorun for the redirect.
Template.lobby.rendered = ->
#autorun ->
if game.url
Router.go(game.url)
You'll have to make sure that the game.url is a reactive datasource. If you pass the game as the data context using iron router, then you should be able to use #data.url but I'm not 100% sure it will be reactive. Just to be safe, try Games.findOne(#data._id) -- that will certainly be reactive.
EDIT:
Just to be clear, try this:
Template.lobby.rendered = ->
#autorun ->
game = Games.findOne(#data._id)
if game.url
Router.go(game.url)

WordPress Write Cache Issue with Multiple Sessions

I'm working on a content dripper custom plugin in WordPress that my client asked me to build. He says he wants it to catch a page view event, and if it's the right time of day (24 hours since last post), to pull from a resource file and output another post. He needed it to also raise a flag and prevent other sessions from firing that same snippet of code. So, raise some kind of flag saying, "I'm posting that post, go away other process," and then it makes that post and releases the flag again.
However, the strangest thing is occurring when placed under load with multiple sessions hitting the site with page views. It's firing instead of one post -- it's randomly doing like 1, 2, or 3 extra posts, with each one thinking that it was the right time to post because it was 24 hours past the time of the last post. Because it's somewhat random, I'm guessing that the problem is some kind of write caching where the other sessions don't see the raised flag just yet until a couple microseconds pass.
The plugin was raising the "flag" by simply writing to the wp_options table with the update_option() API in WordPress. The other user sessions were supposed to read that value with get_option() and see the flag, and then not run that piece of code that creates the post because a given session was already doing it. Then, when done, I lower the flag and the other sessions continue as normal.
But what it's doing is letting those other sessions in.
To make this work, I was using add_action('loop_start','checkToAddContent'). The odd thing about that function though is that it's called more than once on a page, and in fact some plugins may call it. I don't know if there's a better event to hook. Even still, even if I find an event to hook that only runs once on a page view, I still have multiple sessions to contend with (different users who may view the page at the same time) and I want only one given session to trigger the content post when the post is due on the schedule.
I'm wondering if there are any WordPress plugin devs out there who could suggest another event hook to latch on to, and to figure out another way to raise a flag that all sessions would see. I mean, I could use the shared memory API in PHP, but many hosting plans have that disabled. Can't use a cookie or session var because that's only one single session. About the only thing that might work across hosting plans would be to drop a file as a flag, instead. If the file is present, then one session has the flag. If the file is not present, then other sessions can attempt to get the flag. Sure, I could use the file route, but it's kind of immature in my opinion and I was wondering if there's something in WordPress I could do.
The key may be to create a semaphore record in the database for the "drip" event.
Warning - consider the following pseudocode - I'm not looking up the functions.
When the post is queried, use a SQL statement like
$ts = get_time_now(); // or whatever the function is
$sid = session_id();
INSERT INTO table (postcategory, timestamp, sessionid)
VALUES ("$category", $ts, "$sid")
WHERE NOT EXISTS (SELECT 1 FROM table WHERE postcategory = "$category"
AND timestamp < $ts - 24 hours)
Database integrity will make this atomic so only one record can be inserted.
and the insertion will only take place if the timespan has been exceeded.
Then immediately check to see if the current session_id() and timestamp are yours. If they are, drip.
SELECT sessionid FROM table
WHERE postcategory = "$postcategory"
AND timestamp = $ts
AND sessionid = "$sid"
The problem goes like this with page requests even from the same session (same visitor), but also can occur with page requests from separate visitors. It works like this:
If you are doing content dripping, then a page request is probably what you intercept with add_action('wp','myPageRequest'). From there, if a scheduled post is due, then you create the new post.
The post takes a little bit of time to write to the database. In that time, a query on get_posts() may not see that new record yet. It may actually trigger your piece of code to create a new post when one has already been placed.
The fix is to force WordPress to flush the write cache appears to be this:
try {
$asPosts = array();
$asPosts = # wp_get_recent_posts(1);
foreach($asPosts as $asPost) {break;}
# delete_post_meta($asPost['ID'], '_thwart');
# add_post_meta($asPost['ID'], '_thwart', '' . date('Y-m-d H:i:s'));
} catch (Exception $e) {}
$asPosts = array();
$asPosts = # wp_get_recent_posts(1);
foreach($asPosts as $asPost) {break;}
$sLastPostDate = '';
# $sLastPostDate = $asPost['post_date'];
$sLastPostDate = substr($sLastPostDate, 0, strpos($sLastPostDate, ' '));
$sNow = date('Y-m-d H:i:s');
$sNow = substr($sNow, 0, strpos($sNow, ' '));
if ($sLastPostDate != $sNow) {
// No post today, so go ahead and post your new blog post.
// Place that code here.
}
The first thing we do is get the most recent post. But we don't really care if it's not the most recent post or not. All we're getting it for is to get a single Post ID, and then we add a hidden custom field (thus the underscore it begins with) called
_thwart
...as in, thwart the write cache by posting some data to the database that's not too CPU heavy.
Once that is in place, we then also use wp_get_recent_posts(1) yet again so that we can see if the most recent post is not today's date. If not, then we are clear to drip some content in. (Or, if you want to only drip in like every 72 hours, etc., you can change this a little here.)

Resources