Coder Social home page Coder Social logo

ravenx's Introduction

Ravenx

Current Version Build Status

Notification dispatch library for Elixir applications (WIP).

Installation

  1. The package can be installed as simply as adding ravenx to your list of dependencies in mix.exs:
  def deps do
    [{:ravenx, "~> 1.1.3"}]
  end
  1. Add Ravenx to your list of applications in mix.exs. This step is only needed if you are using a version older than Elixir 1.4.0 or you already have some applications listed under the applications key. In any other case applications are automatically inferred from dependencies (explained in the Application inference section):
def application do
  [
    applications: [
      ...,
      :ravenx
    ]
  ]
end

Strategies

From version 2.0, strategies come in separate packages, so the dependencies needed are not added by default.

To define strategies, just add their packages to your mix.exs file and add them to Ravenx configuration as follows:

config :ravenx,
  strategies: [
    email: Ravenx.Strategy.Email
    slack: Ravenx.Strategy.Slack
    my_strategy: MyApp.Ravenx.MyStrategy
  ]

We currently maintain two strategies:

Also, 3rd party strategies are supported and listed below.

3rd party strategies

Amazing people created 3rd party strategies to use Ravenx with more services:

Anyone can create a strategy that works with Ravenx, so if you have one, please let us know to add it to this list.

Custom strategies

Maybe there is some internal service you need to call to send notifications, so there is a way to create custom strategies for yout projects.

First of all, you need to create a module that meet the required behaviour, like the example you can see here.

Then you can define custom strategies in application configuration:

config :ravenx,
  strategies: [
    my_strategy: YourApp.MyStrategy
  ]

and start using your strategy to deliver notifications using the atom assigned (in the example, my_strategy).

Single notification

Sending a single notification is as simply as calling this method:

iex> Ravenx.dispatch(strategy, payload)

In which strategy is an atom indicating one of the defined strategies and the payload is a map with information to dispatch the notification.

For example:

iex> Ravenx.dispatch(:slack, %{title: "Hello world!", body: "Science is cool!"})

Optionally, a third parameter containing a map of options (like URLs or secrets) can be passed depending on strategy configuration needs.

Multiple notifications

You can implement notification modules that Ravenx can use to know which strategies should use to send a specific notification.

To do it, you just need to use Ravenx.Notification and implement a callback function:

defmodule YourApp.Notification.NotifyUser do
  use Ravenx.Notification

  def get_notifications_config(user) do
    # In this function you can define which strategies use for your user (or
    # whatever you want to pass as argument) and return something like:

    [
      slack: {:slack, %{title: "Important notification!", body: "Wait..."}, %{channel: user.slack_username}},
      email_user: {:email, %{subject: "Important notification!", html_body: "<h1>Wait...</h1>", to: user.email_address}},
      email_company: {:email, %{subject: "Important notification about an user!", html_body: "<h1>Wait...</h1>", to: user.company.email_address}},
      other_notification: {:invalid_strategy, %{text: "Important notification!"}, %{option1: value2}},
    ]
  end
end

As seen above, strategies can be used multiple times in a notification list (to send multiple e-mails that have different payload, for example).

Note: each notification entry in the returned list should include:

  1. Atom defining the notification ID.
  2. A two or three element tuple containing:
    1. Atom defining which strategy should be used.
    2. Payload map with the data of the notification.
    3. (Optional) Options map for that strategy.

And then you can dispatch your notification using:

iex> YourApp.Notification.NotifyUser.dispatch(user)

or asynchronously:

iex> YourApp.Notification.NotifyUser.dispatch_async(user)

Both will return a list with the responses for each notification sent:

iex> YourApp.Notification.NotifyUser.dispatch(user)
[
  slack: {:ok, ...},
  email_user: {:ok, ...},
  email_company: {:ok, ...},
  other_notification: {:error, {:unknown_strategy, :invalid_strategy}}
]

Configuration

Strategies usually needs configuration options. To solve that, there are three ways in which you can configure a notification dispatch strategy:

  1. Passing the options in the dispatch call:
iex> Ravenx.dispatch(:slack, %{title: "Hello world!", body: "Science is cool!"}, %{url: "...", icon: ":bird:"})
  1. Specifying a configuration module in your application config:
config :ravenx,
  config: YourApp.RavenxConfig

and creating that module:

defmodule YourApp.RavenxConfig do
  def slack (_payload) do
    %{
      url: "...",
      icon: ":bird:"
    }
  end
end

Note: the module should contain a function called as the strategy yopu are configuring, receiving the payload and returning a configuration Keyword list.

  1. Specifying the configuration directly on your application config file:
config :ravenx, :slack,
  url: "...",
  icon: ":bird:"

Mixing configurations

Configuration can also be mixed by using the three methods:

  • Static configuration on application configuration.
  • Dynamic configuration common to more than one scenario using a configuration module.
  • Call-specific configuration sending a config Keyword list on dispatch method.

Contribute

All contributions are welcome, and we really hope this repo will serve for beginners as well for more advanced developers.

If you have any doubt, feel free to ask, but always respecting our Code of Conduct.

To contribute, create a fork of the repository, make your changes and create a PR. And remember, talking on PRs/issues is a must!

ravenx's People

Contributors

odarriba avatar crbelaus avatar mkaravaev avatar maratgaliev avatar ponty96 avatar

Stargazers

Hien Phan avatar Alex avatar Alexander Buch avatar  avatar Alexander Shapiotko avatar Daniel Blendea avatar Nikita avatar Mayel de Borniol avatar Dennis Muthomi  avatar Tomasz Cichocinski avatar Medson Oliveira avatar George Lima avatar Jan Schlosser avatar Jordan P.R. avatar Fernando Mendes avatar Sviatoslav Flud avatar James Stephens avatar Jonathan Urzua avatar Mikal avatar Sam Gaw avatar Hugo Gonçalves avatar Zdenko Nevrala avatar Johannes avatar Sérgio Hilgert avatar Ketan Anjaria avatar Nitin Misra avatar Alex Wolf avatar Dan Barbarito avatar @cdcme avatar Joakim Nylén avatar  avatar Andreas Riemer avatar Chris avatar Justin Lane avatar Felipe Skinner avatar Jean-Philippe Cugnet avatar Aliou Diallo avatar Phillipp Ohlandt avatar Bachir avatar  avatar Paulo Renato avatar Hannes Stöven avatar Petr Stepchenko avatar Victor Martinez avatar  avatar Daniel Sam avatar Ben Smith avatar Austin Ziegler avatar Railsmechanic avatar  avatar Mauricio Cousillas avatar Shane Thomas avatar Miki Oracle avatar Alessandro Iob avatar Jonatan Männchen avatar Timur Yanberdin avatar Xavier Van de Woestyne avatar Michael Crumm avatar Bernat Jufré Martínez avatar Simbarashe Musarurwa avatar Rodrigo Álvarez avatar Eduardo Pereira avatar César Guzmán avatar James Hrisho avatar  avatar Josep avatar Stuart Bain avatar Shubham Gupta avatar  avatar Lin He avatar  avatar Pablo Martínez avatar Sean Powell avatar Erik van der Wal avatar Frédéric Boyer avatar David Devlin avatar Bob Waycott avatar Arvid Kahl avatar Charles FD avatar Reza Rasouli avatar Sachin Joshi avatar Bogdan Habic avatar John Hamelink avatar  avatar Mrinal Wadhwa avatar Eugene Oskin avatar Eder Sosa avatar Tejumola Pelumi avatar Aeryn Riley Dowling-Toland avatar Gökhan Girgin avatar Juergen Braungardt avatar Hitoshi Hayakawa avatar  avatar Shuhei Hayashibara avatar Byungjik Roh avatar Jim Gray avatar Bertrand Dubaut avatar Алексей Трофимов avatar  avatar Roger Leite avatar

Watchers

Ruben Sierra avatar Lasse Larsen avatar  avatar Victor Ortiz avatar James Cloos avatar  avatar Bernat Jufré Martínez avatar

ravenx's Issues

Add extra timeout for Tasks

On Tasks (which is the process type currently used on Ravenx) the default timeout is 5 seconds, which couldn't be enough if we are dealing with external providers.

At least, we should increase the timeout up to ¿10 seconds?

Also, it would be interesting to supervise these tasks in case they die, but this should be defined as it could be an overdesign.

Notification functionality

Maybe, it would be good to create some kind of modules that allows the end user to define a kind of notification (for example, "An ausence request has been approved").

The idea behind this that, due to the heterogeneity of different strategies' options and payloads, it's difficult to send notifications in multiple ways for one subject.

I have thought in something like this:

defmodule MyApp.Notifications do
  use Ravenx.Notification

  def ausence_approved(ausence) do
    user = ausence.approved

    # TODO: Check which notification strategies can be used to the user
    # and create a list like this:
    notifications = [
      [:slack, payload, options],
      [:email, payload, options],
      [:push, payload, options],
      [:push, payload, options]
    ]

    dispatch(notifications) # or dispatch_async(notifications)
  end
end

What do you think about it? I think it will be helpful for projects in which multiple notifications sent to user using multiple strategies at once.

Configuration reading from application

The main idea is to be able to read default configuration for a service from two places:

  1. App configuration in other Elixir projects that make use of the library
  2. Using a configuration module (like a Plug) that makes the configuration personalised depending on the context of the notification.

The second one is specially important, as it can allow to (for example) define a custom slack channel for a specific user.

Email strategy not reading all configuration options from Ravenx options

There are some configuration options that, due to Bamboo's structure, are readed from payload instead of configuration.

They really are a payload thing (from, to, bcc or subject for example), but it would be cool to being able to set a "default" value in configuration (specially for the from field, which in most cases isn't going to change).

Error when using tuples as e-mail contacts

If tuples are used for from and to contacts, no pattern matches when calling the send_email private function.

Also, an error about adapter configuration missing is returned, when the adapter is set correctly

Ravenx logo design

Hello!

I am a graphic designer. I like to contribute by designing a logo for open source software. Do you want me to design a logo for ravenx? I have a logo idea but I would like to hear if you have an idea.

you can see many logo design examples on my github profile.
https://github.com/reallinfo

Consider following plug conventions and implementing pipeline/pipe macros

Consider following plug conventions:init/1 and call/2. Also would be more natural to have pipeline/pipe macros. I.e.:

defmodule MyApp.MyEvent do
  use Ravenx.Pipeline
  use MyApp.PhoenixSlackPipe, view: MyApp.SlackView, template: "new_slack_event.text"
  use MyApp.PhoenixEmailPipe, view: MyApp.EmailView, template: "new_email_event.text"

  pipe :slack, channel: "events", async: true
  pipe :email, contact: & &1.assigns.user.email, subject: &"Welcome #{&1.assigns.user.name}"

  def call(pipeline, _opts, payload) do
    user = MyApp.Repo.preload(payload, :friends)
    pipeline.assign(:user, user)
  end
end

and for a pipe:

defmodule MyApp.PhoenixSlackPipe do
  use Ravenx.Pipe
  use MyApp.PhoenixSlack # `channel/2`, `render_title/2`, `render_body/3`, `send/1`

  def init(_pipeline, options) do
    options
  end

  def call(pipeline, options) do
    pipeline
    |> channel(options[:channel])
    |> render_title(options[:title])    
    |> render_body(options[:view], options[:template]) # renders with `pipeline.assigns`
    |> send
  end
end

This type of design would make it so you don't have to load strategies in your config (which doesn't seem necessary), instead you just use them in your Pipe files.

Circular dependencies among applications

Update ravenx to version 1.1.0 and get this error on release build stage.

==> Release failed, during .boot generation:
        Circular dependencies among applications: [{ravenx,"1.1.0"},
                                               {bamboo_smtp,"1.2.1"},
                                               {bamboo,"0.7.0"}]

UPD: downgrade to 1.0.0 version and see the same, so it's not caused by Ravenx

Release failed, during .boot generation:
        {apps,[{circular_dependencies,[{ravenx,"1.0.0"},{bamboo_smtp,"1.2.1"}]},
           {undefined_applications,[bamboo]}]}

Separate the strategies as individual libraries

The idea is to move the strategies codes (and dependencies) to separate libraries in order to avoid needing to have all code and dependencies to use only a strategy or having to increment too much the version numbers only to release small fixes.

I suppose this will need a new major version, as it will make disappear functionality and adding extra needs (to add the strategies individual libs)

Identifiers on notification modules

It would be good to include identifiers for multiple notifications using the notification module functionality.

The idea is to beign able to identify which notification has failed to being able to act depending on that.

Add tests

In order to reach v 1.0.0, we need to create tests and add CI to the project.

Create interface for custom methods of special strategies

It can be interesting to allow some strategies (like Mailchimp, thanks @vortizhe for the idea) to have some extra methods (lika add subscribers to a list, for example).

As Ravenx now has two methods to dispatch messages:

dispatch/3
dispatch_async/3

Maybe another option can be implemented with a spec similar to this:

def exec(strategy, method, args)
def exec_async(strategy, method, args)

So special methods of some strategies can be called in a sync or async mode, and these useful functionalities can have a place at the library.

Pusher Integration

Hi there @odarriba!

First of all thank you for you library I discovered it through your post on Medium and I really liked the idea and I wanted to give it a try with a small part of a side project.

With good spirit I created a new Strategy to integrate Pusher into the mix.
You can take a look here RavenxPusher.
And so I was hoping that we can add said strategy to your README in order to reflect the implementation in case anyone needs it.

Again thank you for the amazing work and keep 'em coming!

Not standard configuration object.

In Elixir, configuration objets usually came in form of List instead of Maps.

As Ravenx actually uses Maps, the configuration lists cannot be defined in different files (and merged automatically), so it would be good to convert them to Lists.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.