version: 2
title: Temporal Locality Example
contributor: https://github.com/maglietti
summary: Relate email messages sent or received by a specific user within a 4-6 minute window.
description: |-
  This recipe looks for emails sent or received by cto@company.com within a 4-6 minute
  window as a means of highlighting a technique for matching on temporal locality of nodes.

ingestStreams:
  - name: email-ingest
    source:
      type: File
      path: $in_file
      format:
        type: Json
    query: |-
      MATCH (sender), (message)
      WHERE id(sender) = idFrom('email', $that.from)
        AND id(message) = idFrom('message', $that)

      SET sender.email = $that.from,
          sender: Email,
          message.from = $that.from,
          message.to = $that.to,
          message.subject = $that.subject,
          message.time = datetime({ epochMillis: $that.time}),
          message: Message

      CREATE (sender)-[:SENT_MSG]->(message)

      WITH $that as t, message
      UNWIND t.to AS rcv
      MATCH (receiver)
      WHERE id(receiver) = idFrom('email', rcv)

      SET receiver.email = rcv,
          receiver: Email

      CREATE (message)-[:RECEIVED_MSG]->(receiver)

standingQueries:
  - name: cto-message-window
    pattern:
      type: Cypher
      mode: MultipleValues
      query: |-
        MATCH (n)-[:SENT_MSG]->(m)-[:RECEIVED_MSG]->(r)
        WHERE n.email="cto@company.com" OR r.email="cto@company.com"
        RETURN id(n) as ctoId, id(m) as ctoMsgId, m.time as mTime, id(r) as recId
    outputs:
      - name: withinFourToSixMinuteWindow
        resultEnrichment:
          query: |-
            MATCH (n)-[:SENT_MSG]->(m)-[:RECEIVED_MSG]->(r), (thisMsg)
            WHERE id(n) = $that.data.ctoId
              AND id(r) = $that.data.recId
              AND id(thisMsg) = $that.data.ctoMsgId
              AND id(m) <> id(thisMsg)
              AND duration("PT6M") > duration.between(m.time,thisMsg.time) > duration("PT4M")

            CREATE (m)-[:IN_WINDOW]->(thisMsg)
            CREATE (m)<-[:IN_WINDOW]-(thisMsg)

            WITH n, m, r, "http://localhost:8080/#MATCH" + text.urlencode(' (n)-[:SENT_MSG]->(m)-[:RECEIVED_MSG]->(r) WHERE strId(n)="' + strId(n) + '"AND strId(r)="' + strId(r) + '" AND  strId(m)="' + strId(m) + '" RETURN n, r, m') as URL

            RETURN URL
          parameter: that
        destinations:
          - type: StandardOut

nodeAppearances:
  - predicate:
      propertyKeys:
        - email
      knownValues:
        email: "cto@company.com"
      dbLabel: Email
    icon: ion-android-person
    color: "#F44336"
    label:
      type: Property
      key: email
  - predicate:
      propertyKeys: []
      knownValues: {}
      dbLabel: Email
    icon: ion-android-person
    color: "#2ECC71"
    label:
      type: Property
      key: email
  - predicate:
      propertyKeys: []
      knownValues: {}
      dbLabel: Message
    icon: ion-ios-email-outline
    color: "#2ECC71"
    label:
      type: Property
      key: subject

quickQueries:
  - predicate:
      propertyKeys: []
      knownValues: {}
    quickQuery:
      name: "[Node] Adjacent Nodes"
      querySuffix: MATCH (n)--(m) RETURN DISTINCT m
      sort:
        type: Node
  - predicate:
      propertyKeys: []
      knownValues: {}
    quickQuery:
      name: "[Node] Refresh"
      querySuffix: RETURN n
      sort:
        type: Node
  - predicate:
      propertyKeys: []
      knownValues: {}
    quickQuery:
      name: "[Text] Local Properties"
      querySuffix: RETURN id(n), properties(n)
      sort:
        type: Text
  - predicate:
      propertyKeys: []
      knownValues: {}
      dbLabel: Message
    quickQuery:
      name: "[Node] Messages in Window"
      querySuffix: MATCH (n)-[:IN_WINDOW]-(m) RETURN n,m
      sort:
        type: Node
  - predicate:
      propertyKeys: []
      knownValues: {}
      dbLabel: Message
    quickQuery:
      name: "[Text] Table of Messages in Window"
      querySuffix: MATCH (n)-[r:IN_WINDOW]-(m) RETURN DISTINCT n.time AS MSG1_TIME, n.subject AS MSG1_SUBJECT, m.time AS MSG2_TIME, m.subject AS MSG2_SUBJECT, toString(abs(duration.between(n.time,m.time).seconds/60)) + " Minutes " + toString(abs(duration.between(n.time,m.time).seconds)-abs(duration.between(n.time,m.time).seconds/60)*60) + " Seconds" AS DELTA_TIME
      sort:
        type: Text

sampleQueries: []
