Is it possible to access and modify a control variable while performing data aggregation?

I have simplified my specific issue to make it more understandable. The data I am looking to aggregate pertains to user events on a video player page. Here is a sample of the data:

{_id:"5963796a46d12ed9891f8c80",eventName:"Click Freature 1",creation:1499691279492},
{_id:"59637a5a46d12ed9891f8e0d",eventName:"Video Play",creation:1499691608106},
{_id:"59637a9546d12ed9891f8e90",eventName:"Click Freature 1",creation:1499691664633},
{_id:"59637c0f46d12ed9891f9146",eventName:"Video Pause",creation:1499692055335}

These events occur in a consistent chronological order. Let's say I want to track the number of times a user clicked feature 1, but only while the video is playing.

To achieve this, I believe I need a control variable like "isVideoPlaying," which would be set to true when a "Video Play" event occurs and false when a "Video Pause" event happens. Then, I can increment the count for "Click Feature 1" events only when this variable is true.

Is there a method to implement this functionality?

Answer №1

Is it possible to access and modify a control variable while performing the aggregation process?

Unfortunately, it is not feasible to track previous or next elements during the execution of the aggregation pipeline.

The concept involves transforming events into arrays based on their respective time values.

You have two choices.

Breakdown

Video Play :             [1,5,7]
Video Pause :            [3,6,10]
Features :               [2,4,8,9]

Play-Features :            2           8,9          
Video play-pause pair  : [1,3],[5,6],[7,10]
Pause-Features :          4             
Video pause-play pair :  [3,5],[6,7],[10,-]

Desired Outcome

{count:3}

First Option: (Perform all actions within the aggregation pipeline )

Add additional stages to convert documents into an event-array structure.

Consider the following documents

db.collection.insertMany([
{eventName:"Video Play",creation:1},
{eventName:"Click Features 1",creation:2},
{eventName:"Video Pause",creation:3}, 
{eventName:"Click Features 1",creation:4},
{eventName:"Video Play",creation:5},
{eventName:"Video Pause",creation:6},
{eventName:"Video Play",creation:7},
{eventName:"Click Features 1",creation:8},
{eventName:"Click Features 1",creation:9},
{eventName:"Video Pause",creation:10}
]);

You can utilize the following aggregation

This aggregation involves two $group stages to convert events into their respective time arrays, followed by a $project stage to project ($let) each event creations array as variables.

To understand the logic inside $let, refer to option 2 below

db.collection.aggregate([
  {
    "$sort": {
      "eventName": 1,
      "creation": 1
    }
  },
  {
    "$group": {
      "_id": "$eventName",
      "creations": {
        "$push": "$creation"
      }
    }
  },
  {
    "$group": {
      "_id": "null",
      "events": {
        "$push": {
          "eventName": "$_id",
          "creations": "$creations"
        }
      }
    }
  },
  {
    "$project": {
      "count": {
        "$let": {
          "vars": {
            "video_play_events": {
              "$arrayElemAt": [
                "$events.creations",
                {
                  "$indexOfArray": [
                    "$events.eventName",
                    "Video Play"
                  ]
                }
              ]
            },
            "click_features_event": {
              "$arrayElemAt": [
                "$events.creations",
                {
                  "$indexOfArray": [
                    "$events.eventName",
                    "Click Features 1"
                  ]
                }
              ]
            },
            "video_pause_events": {
              "$arrayElemAt": [
                "$events.creations",
                {
                  "$indexOfArray": [
                    "$events.eventName",
                    "Video Pause"
                  ]
                }
              ]
            }
          },
          "in": {*}
        }
      }
    }
  }
])

*You now have events creations array for each event. Insert the following aggregation code and replace $video_play_events with $$video_play_events and so forth to access variables from the $let stage.

Second Option: (Store events in distinct arrays)

db.collection.insert([
  {
    "video_play_events": [
      1,
      5,
      7
    ],
    "click_features_event": [
      2,
      4,
      8,
      9
    ],
    "video_pause_events": [
      3,
      6,
      10
    ]
  }
])

You can manage array growth by including a "count" field to limit the number of events stored in a single document.

Multiple documents can be created for a specified time interval.

This simplifies the aggregation process to the below query.

The following aggregation iterates through video_play_events and filters all features between each play and pause pair (pl and pu).

$size is used to count the number of feature elements between every play and pause pair, followed by $map + $sum to determine the total number of feature events across all play-pause pairs.

db.collection.aggregate([
  {
    "$project": {
      "count": {
        "$sum": {
          "$map": {
            "input": {
              "$range": [
                0,
                {
                  "$subtract": [
                    {
                      "$size": "$video_play_events"
                    },
                    1
                  ]
                }
              ]
            },
            "as": "z",
            "in": {
              "$let": {
                "vars": {
                  "pl": {
                    "$arrayElemAt": [
                      "$video_pause_events",
                      "$$z"
                    ]
                  },
                  "pu": {
                    "$arrayElemAt": [
                      "$video_play_events",
                      {
                        "$add": [
                          1,
                          "$$z"
                        ]
                      }
                    ]
                  }
                },
                "in": {
                  "$size": {
                    "$filter": {
                      "input": "$click_features_event",
                      "as": "fe",
                      "cond": {
                        "$and": [
                          {
                            "$gt": [
                              "$$fe",
                              "$$pl"
                            ]
                          },
                          {
                            "$lt": [
                              "$$fe",
                              "$$pu"
                            ]
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
])

Notes:

There is a risk of reaching the 16 MB document limit depending on the number of documents being aggregated in both scenarios.

Utilize the async module for running parallel queries with appropriate filters to contain the data being aggregated, followed by client-side processing to calculate all components.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

search through an object to find specific data

Here is a JavaScript object that I have: var data = { "type": [ "car", "bike" ], "wheels": [ "4", "2" ], "open": [ "Jan", "Jan" ] ...

Is there a way to implement Bootstrap 5 toast in Vue.js and navigate to a different page, such as after submitting a form?

Is there a way to incorporate the bootstrap5 toast in vuejs while also redirecting to another page? For example, after submitting a form and being redirected to a new page, I would like a toast message to appear with the statement "Your form has been sub ...

Ways to activate the JavaScript/AJAX script upon the dropdown value modification

I am currently working on a Django project where I want to execute a JavaScript/AJAX script when the value of a dropdown changes. Interestingly, the same code functions properly for a button but not for the dropdown. Below is the HTML code containing the ...

Steps for setting up and shutting down the server during integration testing with Express and Supertest on NodeJS

One issue that continues to plague me is the "Address already in use::3000" error which pops up whenever I run my tests. This is what I currently have set up: package.json "scripts": { "test": "jest --watchAll --verbose --runInBand --maxWorkers=1" ...

Mongoose's "select" Query Method and its Puzzling Variability

Recently while working with Node.js, MongoDB, and Mongoose, I encountered an interesting behavior with the select method in Mongoose. It appears that there are two distinct scenarios where the behavior varies: One situation arises when attempting to save ...

The Angular2 Heroes tutorial showcases a strange glitch where POST requests are duplicated when used alongside a node

I successfully set up the Heroes example up to step 10 using Http Client. The example runs smoothly, but now I am in the process of replacing the "dummy" server with a Node.JS server running on port 3002. I updated the _HeroesURL to "http:/localhost:3002/h ...

Combining AngularJS with ng file upload and Sails JS for seamless file uploading

When I upload a file, I also need to send some additional information along with it. The instructions mention using the data parameter for this purpose, but I'm having trouble accessing it in my Sails controller action. Frontend: Upload.upload({ url ...

Element.style prevents customization of Rev Slider dimensions

I'm currently attempting to adjust the height of my rev slider from 100% to 80%, but I can't seem to locate the necessary code. Upon inspecting in Chrome, I noticed the following: element.style { max-height: 1080px; margin-to ...

How can I pull the account creation date stored in MongoDB and display it using Handlebars?

Currently in my development, I am utilizing MongoDB, NodeJS, and Handlebars. My challenge is to convert the user.id into a timestamp and then display this timestamp on my HTML page. At present, I can display the user.id by using {{ user.id }} in my code, ...

What is the best way to integrate an array from an external JavaScript file into a Vue.js component?

I am struggling to import an array into a Vue component: This is my simplified component: <script type="text/babel"> const codes = require('./codes.js'); export default { props: [], data() { return { ...

Is it possible to interact with a particular point or element within a canvas using languages like javascript, jquery, or selenium?

I am trying to figure out how to simulate a click at a specific position within a canvas. I have tried using coordinates, but so far haven't found a way to make it work. Any other suggestions would be greatly appreciated. It's important to note t ...

What is the best way to store the result from a JavaScript FileReader into a variable for future reference?

I am currently facing an issue uploading a local .json file to my web application. I have managed to display the file in the dev tools, but I am unable to make it accessible for further use. It seems like the problem lies in how I handle (or fail to handle ...

Arrange items within an array using keys that can change dynamically

I'm currently working on sorting an array of dynamic key / value pairs (Objects) based on a specific property within the object. Can anyone provide me with an example of how to accomplish this? I will attempt to replicate a similar structure for my is ...

"Three.js rendering issue: Particle sphere appearing as a straight line instead of a sphere

Exploring the world of THREE.js library has led me to attempt creating a particle sphere within my React App. Drawing inspiration from this project, I've made some progress but seem to have hit a roadblock. Strangely, despite similarities in code - e ...

An issue occurs when trying to use the Redux Toolkit error object as a child element

I am facing an issue while trying to extract a task from the Tasks file and add it to Slice. There seems to be a problem in the 'addTask' method in the task section. Any assistance would be greatly appreciated. import React from 'react&a ...

Activate loading state when input changes with VueJS and Vuetify by using @change attribute and setting loading="true"

I am attempting to develop a function that is activated by the @change event on multiple input fields. My goal is to set loading="true" for the targeted input until the axios request (PATCH) is completed. PLEASE NOTE: There are several v-select componen ...

Content within Drawer featuring Material UI Scrollable Tabs

I am currently utilizing a material-ui drawer with the mini variant drawer structure. Inside this drawer, I have incorporated a series of tabs utilizing the prevent scroll buttons tabs design. The version of material-ui being used is 4.12.3. Regrettably, ...

Building Relationships in LoopBack version 4 Using the Include Key

Learn more about HasMany relation in LoopBack 4 here After following the necessary steps, I attempted to retrieve data using the include option but encountered a 500 error. 500 Error: Invalid "filter.include" entries: {"relation":"ranks"} My objective i ...

Mongoose - run a function on a designated date within the document

Currently, I am developing a cryptocurrency-based betting game project. My database of choice is mongodb and I utilize mongoose for interaction with it. Within this database, I have a collection that houses documents detailing various betting games. Each d ...

When checking if el.text() is equal to "string", it will return false, regardless of the actual content of the element being "string"

Looking at the code snippet below, it seems that the else block is always being executed instead of the if block. The alert confirms that the variable state has the value 'payment' as expected. var state = $('.check-state').text(); ale ...