Secret Santa example

In this example, we use the Expressions extension to randomly draw names from a list, and send the names to Secret Santas through the message application Slack. Each Secret Santa has to buy a present for the person whose name she has received.

Figure: Secret Santa example - The mix


Tip: Get the code from the SPARKL public repository.
Table 1. Secret Santa example - The markup
Markup Description
<folder name="Santa">
  <service name="Sequencer"
    provision="sequencer"/>
  <field name="XMAS"/>
  <field name="giver" 
    type="term"/>
  <field name="recipient" 
    type="term"/>
  <field name="DONE"/>
  <mix name="Mix">
    <notify name="Start" 
      service="Sequencer" 
      fields="XMAS"/>
    <folder name="GetSanta">
      ...
    </folder>
    <folder name="SendSanta">
      ...
    </folder>
  </mix>
</folder>
The Secret Santa example comprises:
  • Sequencer, a service provisioned using the Sequencer extension
  • Four fields:
    • XMAS, a FLAG
    • giver, a term used for carrying tuples
    • recipient, a term used for carrying tuples
    • DONE, a FLAG
  • Start, a notify operation that starts the transaction by outputting the XMAS field
  • Two folders, each containing a service and the operations on it:
    • GetSanta contains components that select the giver and the recipient of a present
    • SendSanta contains components that send private messages to the giver and the recipient upon selection
Table 2. Secret Santa example - GetSanta folder
Markup Description
<service name="Santas" 
  provision="expr">
  <prop name="expr.init.People" 
    content-type="text/x-erlang"><![CDATA[
[
  {"Jacoby", "https://hooks.slack.com/..."},
  {"Yev", "https://hooks.slack.com/..."},
  {"Miklos", "https://hooks.slack.com/..."},
  {"Emily", "https://hooks.slack.com/..."},
  {"Mark", "https://hooks.slack.com/..."},
  {"Andrew", "https://hooks.slack.com/..."}
]
  ]]></prop>
  <prop name="expr.state" 
    MustGive="NewMustGive" 
    MustReceive="NewMustReceive"/>
  <prop name="expr.src" 
    content-type="text/x-erlang"><![CDATA[
NewMustGive = People,
NewMustReceive = People.
  ]]></prop>
</service>
Santas, a service provisioned using the Expressions extension, contains a list of tuples.

Each tuple comprises:

  • The name of one of the participants
  • A Slack webhook URL that posts messages to the private address of the participant
Note: Slack incoming webhooks can be set up as custom integrations. See Slack API documentation.
Two state variables are defined on the Santas service:
  • MustGive, updated by NewMustGive
  • MustReceive, updated by NewMustReceive

At service start-up, both states change to contain the content of the People variable - the list of tuples.

<request name="ChooseGiver" 
  service="Santas" 
  fields="XMAS">
  <prop name="expr.bind.out" 
    Giver="giver"/>
  <prop name="expr.src" 
    content-type="text/x-erlang"><![CDATA[
case MustGive of
  [] ->
    "Done";
  [Giver | NewMustGive] ->
    "Next"
end.
  ]]></prop>
  <reply name="Next" 
    fields="giver"/>
  <reply name="Done" 
    fields="DONE"/>
</request>
ChooseGiver, a request operation implemented by Santas, has two alternative replies:
  • If the list bound to the MustGive variable is empty, the Done reply is sent. This happens if all the names were drawn.

    In this case, SPARKL has the XMAS, giver and DONE fields in the field set. Since it cannot satisfy any goal with it, the field set is discarded and the transaction ends.

  • If the list bound to the MustGive variable contains one or more tuples, the list is split in two:
    • The first tuple of the list is bound to the variable Giver, which in turn is bound to the giver field
    • The rest of the tuples - if any left - create a new list, which is used to update the MustGive state variable
When invoked for the first time, ChooseGiver sends the first tuple, then the second, and so on, until all the tuples are removed from the list kept in the MustGive state variable.
<request name="ChooseRecipient" 
  service="Santas" 
  fields="giver">
  <prop name="expr.bind.in" 
    Giver="giver"/>
  <prop name="expr.bind.out" 
    Recipient="recipient"/>
  <prop name="expr.src" 
    content-type="text/x-erlang"><![CDATA[
Candidates =
  lists:delete(Giver, MustReceive),
Random =
  rand:uniform(length(Candidates)),
Recipient =
  lists:nth(Random, Candidates),
NewMustReceive =
  lists:delete(Recipient, MustReceive),
"Ok".
  ]]></prop>
  <reply name="Ok" 
    fields="recipient"/>
</request>
ChooseRecipient, a request operation implemented by Santas, picks a random name from the list - the recipient of the present.

The expressions in the operation:

  1. Make sure no one can draw himself by deleting Giver, a variable bound to the input field, from the candidates. The candidates are the tuples listed in the MustReceive state variable, minus the giver.
  2. Return a random number and bind it to the Random variable. It can be any integer between one and the current number of the candidates.
  3. Pick the candidate who is the nth in the list, where n equals the value of Random
  4. Update the MustReceive list by removing the selected recipient from the list
  5. Send the reply Ok, with the recipient field, which contains a tuple - the one selected from the candidates
Table 3. Secret Santa example - SendSanta folder
Markup Description
<service name="Slack" 
  provision="expr">
  <prop name="expr.src" 
    content-type="text/x-erlang"><![CDATA[
MessageSender =
  fun(Url, Term) ->
    Body = 
      sse_json:from_term(Term, simple),
    Request =
      {Url, [], "application/json", Body},
    httpc:request(post, Request, [], [])
  end,
AlertGiver =
  fun(Url, GiverName, RecipientName) ->
    Message = #{
      text =>
        "Hey " ++
        GiverName ++
        "! Please be Santa and buy a present for " ++
        RecipientName
    },
    MessageSender(Url, Message)
  end,
AlertRecipient =
  fun(Url, RecipientName) ->
    Message = #{
      text =>
        "Guess what " ++
        RecipientName ++
        "! You'll be getting a present from Santa!"
    },
    MessageSender(Url, Message)
  end.
  ]]></prop>
</service>
Slack, a service provisioned using the Expressions extension, contains three Erlang functions bound to the following variables:
MessageSender
The function bound to this variable:
  1. Specifies the variables Url and Term as its arguments
  2. Takes Term, which can be any Erlang term, and converts it into a Json map binding the result to the Body variable
  3. Binds the Request variable to a tuple comprising:
    • The Url variable
    • The media type application/json
    • The Body variable
  4. Sends an http post request, which specifies the Request as its argument
AlertGiver
The function bound to this variable:
  1. Specifies the variables Url, GiverName and RecipientName as its arguments
  2. Specifies the Message variable and binds it to an Erlang map, which contains some text and the GiverName and RecipientName variables
  3. Calls the MessageSender function with the Url and Message variables as its arguments
AlertRecipient
The function bound to this variable:
  1. Specifies the variables Url and RecipientName as its arguments
  2. Specifies the Message variable and binds it to an Erlang map, which contains some text and the RecipientName variable
  3. Calls the MessageSender function with the Url and Message variables as its arguments
<consume name="SendMessage" 
  service="Slack" 
  fields="giver recipient">
  <prop name="expr.bind.in" 
    Giver="giver" 
    Recipient="recipient"/>
  <prop name="expr.src" 
    content-type="text/x-erlang"><![CDATA[
{GiverName, GiverUrl} = Giver,
{RecipientName, RecipientUrl} = Recipient,
AlertGiver(GiverUrl, GiverName, RecipientName),
AlertRecipient(RecipientUrl, RecipientName),
"Ok".
  ]]></prop>
  <reply name="Ok" 
    fields="XMAS"/>
</consume>
SendMessage, a consume operation implemented by the Slack service:
  1. Binds its input fields giver and recipient to the variables Giver and Recipient
  2. Binds the content of the tuples it received:
    • The name and URL from the giver field to GiverName and GiverUrl
    • The name and URL from the recipient field to RecipeintName and RecipientUrl
  3. Calls the AlertGiver function with the arguments:
    • GiverUrl, the URL from the giver field
    • GiverName, the name from the giver field
    • RecipientName, the name from the recipient field
  4. Calls the AlertRecipient function with the arguments:
    • RecipientUrl, the URL from the recipient field
    • RecipientName, the name from the recipient field
  5. Sends the Ok reply, with the field XMAS
Every time SendMessage sends Ok, it starts a new sequence in which the ChooseGiver operation selects a new giver.

Figure: Secret Santa Slack message