Giter Site home page Giter Site logo

openfn / lightning Goto Github PK

View Code? Open in Web Editor NEW
107.0 7.0 32.0 160.6 MB

OpenFn/Lightning ⚡️ is the newest version of the OpenFn DPG and provides a web UI to visually manage complex workflow automation projects.

Home Page: https://openfn.github.io/lightning/

License: GNU Lesser General Public License v3.0

Elixir 87.44% CSS 0.74% JavaScript 0.81% HTML 6.20% Shell 0.22% Dockerfile 0.12% Batchfile 0.01% TypeScript 4.47%
dpg ict4d integration interoperability tech4good workflow-automation global-good dpi elixir phoenix-framework

lightning's Introduction

OpenFn/Lightning CircleCI codecov Docker Pulls

Lightning ⚡ (aka "OpenFn v2") is a workflow automation platform that's used to automate critical business processes and integrate information systems. From last-mile services to national-level reporting, it boosts efficiency & effectiveness while enabling secure, stable, scalable interoperability and data integration at all levels.

Use it online at app.openfn.org

Explore in a sandbox on demo.openfn.org

Or learn more at docs.openfn.org

OpenFn Lightning is:

  • the latest version of OpenFn: first launched in 2014, it's been tried and tested by NGOs and governments in 40+ countries
  • fully open source: there's no "community edition" and "premium edition", you get the same product whether you are self-hosting or using the OpenFn.org software-as-a-service
  • the leading DPGA certified Digital Public Good for workflow automation
  • a Digital Square certified Global Good for Health

image

Contents

Demo

Watch a short demo video or explore a public sandbox with the login details below, but please note that this deployment is reset every night at 12:00:00 UTC and is 100% publicly accessible. Don't build anything you want to keep, or keep private!

username: [email protected]
password: welcome123

Features

Build

Plan and build workflows using Lightning's visual interface to quickly define when, where and what you want your automation to do.

image

Use our CLI to quickly build, edit and deploy projects from the comfort of your own code editor.

Monitor

Monitor all workflow activity in one place.

image

  • Filter and search runs to identify issues that need addressing and follow how a specific request has been processed
  • Configure alerts to be notified on run failures
  • Receive a project digest for a daily/weekly/monthly summary of your project activity

Manage

Manage users and access by project.

image

Roles and permissions

Authorization is a central part of Lightning. As such, users are given different roles which determine what level of access they have for resources in the application. For more details about roles and permissions in Lightning, please refer to our documentation.

Roadmap

View our public GitHub project to see what we're working on now and what's coming next.

Getting Started

Run via Docker

  1. Install the latest version of Docker
  2. Clone this repo using git
  3. Setup PostgreSQL database with: docker compose build && docker compose run --rm web mix ecto.migrate
  4. Run Lightning and PostgresSQL with: docker compose up

By default the application will be running at localhost:4000.

See "Problems with Docker" for additional troubleshooting help. Note that you can also create your own docker-compose.yml file, configuring a postgres database and using a pre-built image from Dockerhub.

Deploy on external infrastructure

Head to the Deploy section of our docs site to get started.

For technical guidelines, see deployment considerations for more detailed information.

Dev on Lightning locally

Clone the repo and optionally set ENVs

git clone [email protected]:OpenFn/Lightning.git # or from YOUR fork!
cd Lightning
cp .env.example .env # and adjust as necessary!

Take note of database names and ports in particular—they've got to match across your Postgres setup and your ENVs. You can run lightning without any ENVs assuming a vanilla postgres setup (see below), but you may want to make adjustments.

Database Setup

If you're already using Postgres locally, create a new database called lightning_dev, for example.

If you'd rather use Docker to set up a Postgres DB, create a new volume and image:

docker volume create lightning-postgres-data

docker create \
  --name lightning-postgres \
  --mount source=lightning-postgres-data,target=/var/lib/postgresql/data \
  --publish 5432:5432 \
  -e POSTGRES_PASSWORD=postgres \
  postgres:15.3-alpine

docker start lightning-postgres

Elixir & Ecto Setup

We use asdf to configure our local environments. Included in the repo is a .tool-versions file that is read by asdf in order to dynamically make the specified versions of Elixir and Erlang available. You'll need asdf plugins for Erlang, NodeJs Elixir and k6.

We use libsodium for encoding values as required by the Github API. You'll need to install libsodium in order for the application to compile.

For Mac Users:

brew install libsodium

For Debian Users:

sudo apt-get install libsodium-dev

You can find more on how to install libsodium here

asdf install  # Install language versions
mix local.hex
mix deps.get
mix local.rebar --force
mix ecto.create # Create a development database in Postgres
mix ecto.migrate
[[ $(uname -m) == 'arm64' ]] && CPATH=/opt/homebrew/include LIBRARY_PATH=/opt/homebrew/lib mix deps.compile enacl # Force compile enacl if on M1
[[ $(uname -m) == 'arm64' ]] && mix compile.rambo # Force compile rambo if on M1
mix lightning.install_runtime
mix lightning.install_schemas
mix lightning.install_adaptor_icons
npm install --prefix assets

Run the app

Lightning is a web app. To run it in interactive Elixir mode, start the development server by running with your environment variables by running:

iex -S mix phx.server

or if you have set up custom environment variables, run:

env $(cat .env | grep -v "#" | xargs ) iex -S mix phx.server

Once the server has started, head to localhost:4000 in your browser.

Run the tests

Before the first time running the tests, you need a test database setup.

MIX_ENV=test mix ecto.create

And then after that run the tests using:

MIX_ENV=test mix test

We also have test.watch installed which can be used to rerun the tests on file changes.

Security and Standards

We use a host of common Elixir static analysis tools to help us avoid common pitfalls and make sure we keep everything clean and consistent.

In addition to our test suite, you can run the following commands:

  • mix format --check-formatted Code formatting checker, run again without the --check-formatted flag to have your code automatically changed.
  • mix dialyzer Static analysis for type mismatches and other common warnings. See dialyxir.
  • mix credo --strict --all Static analysis for consistency, and coding standards. See Credo.
  • mix sobelow Check for commonly known security exploits. See Sobelow.
  • MIX_ENV=test mix coveralls Test coverage reporter. This command also runs the test suite, and can be used in place of mix test when checking everything before pushing your code. See excoveralls.

For convenience there is a verify mix task that runs all of the above and defaults the MIX_ENV to test.

For more guidance on security best practices for workflow automation implementations, check out OpenFn Docs: docs.openfn.org/documentation/getting-started/security

Contribute to this project

First, thanks for being here! You're contributing to a digital public good that will always be free and open source and aimed at serving innovative NGOs, governments, and social impact organizations the world over! You rock. ❤️

FYI, Lightning is built in Elixir, harnessing the Phoenix Framework. Currently, the only unbundled dependency is a PostgreSQL database.

If you'd like to contribute to this projects, follow the steps below:

Pick up an issue

Read through the existing issues, assign yourself to the issue you have chosen. Leave a comment on the issue to let us know you'll be working on it, and if you have any questions of clarifications that would help you get started ask them there - we will get back to you as soon as possible.

If there isn't already an issue for the feature you would like to contribute, please start a discussion in our community forum.

Open a pull request

  1. Clone the Lightning repository, then fork it.

  2. Run through setting up your environment and make your changes.

  3. Make sure you have written your tests and updated /CHANGELOG.md (in the 'Unreleased' section, add a short description of the changes you are making, along with a link to your issue).

  4. Open a draft pull request by clicking "Contribute > Open Pull Request" from your forked repository. Fill out the pull request template (this will be added automatically for you), then make sure to self-review your code and go through the 'Review checklist'. Don't worry about the QA checkbox, our product manager Amber will tick that once she has reviewed your PR. You can leave any notes for the reviewer in a comment.

  5. Once you're ready to submit a pull request, you can mark your draft PR as 'Ready for review' and assign @stuartc or @taylordowns2000.

Generate the docs pages

You can generate the HTML and EPUB documentation locally using:

mix docs and opening doc/index.html in your browser.

Server Specs for Self-Hosting

For recommend server specifications for self-hosting of Lightning, head to the deployment planning section of the documentation or check out this self-hosting thread on our community forum.

Benchmarking

We are using k6 to benchmark Lightning. Under benchmarking folder you can find a script for benchmarking Webhook Workflows.

See Benchmarking for more detailed information.

Troubleshooting

Problems with environment variables

For troubleshooting custom environment variable configuration it's important to know how an Elixir app loads and modifies configuration. The order is as follows:

  1. Stuff in config.exs is loaded.
  2. That is then modified (think: overwritten) by stuff your ENV-specific config: dev.exs, prod.exs or test.exs.
  3. That is then modified by runtime.exs which is where you are allowed to use System.env()
  4. Finally init/2 (if present in a child application) gets called (which takes the config which has been set in steps 1-3) when that child application is started during the parent app startup defined in application.ex.

Problems with Postgres

If you're having connecting issues with Postgres, check the database section of your .env to ensure the DB url is correctly set for your environment — note that composing a DB url out of other, earlier declared variables, does not work while using xargs.

Problems with Debian

If you're getting this error on debian

==> earmark_parser
Compiling 1 file (.yrl)
/usr/lib/erlang/lib/parsetools-2.3.1/include/yeccpre.hrl: no such file or directory
could not compile dependency :earmark_parser, "mix compile" failed. You can recompile this dependency with "mix deps.compile earmark_parser", update it with "mix deps.update earmark_parser" or clean it with "mix deps.clean earmark_parser"

You need to install erlang development environment sudo apt install erlang-dev refer to this issue

Problems with Docker

Versions

The build may not work on old versions of Docker and Docker compose. It has been tested against:

Docker version 20.10.17, build 100c701
Docker Compose version v2.6.0

Starting from scratch

If you're actively working with docker, you start experiencing issues, and you would like to start from scratch you can clean up everything and start over like this:

# To remove any ignored files and reset your .env to it's example
git clean -fdx && cp .env.example .env
# You can skip the line below if you want to keep your database
docker compose down --rmi all --volumes

docker compose build --no-cache web && \
  docker compose create --force-recreate

docker compose run --rm web mix ecto.migrate
docker compose up

Problems with Rambo

When running mix compile.rambo on Apple Silicon (an Apple M1/M2, macarm, aarch64-apple-darwin) and encountering the following error:

** (RuntimeError) Rambo does not ship with binaries for your environment.

    aarch64-apple-darwin22.3.0 detected

Install the Rust compiler so a binary can be prepared for you.

    lib/mix/tasks/compile.rambo.ex:89: Mix.Tasks.Compile.Rambo.compile!/0
    lib/mix/tasks/compile.rambo.ex:51: Mix.Tasks.Compile.Rambo.run/1
    (mix 1.14.2) lib/mix/task.ex:421: anonymous fn/3 in Mix.Task.run_task/4
    (mix 1.14.2) lib/mix/cli.ex:84: Mix.CLI.run_task/2

You can resolve this error by installing the Rust compiler using Homebrew. Run the following command in your terminal: brew install rust

If you have already compiled Rambo explicitly via mix compile.rambo, and you are still seeing the following error:

sh: /path_to_directory/Lightning/_build/dev/lib/rambo/priv/rambo: No such file or directory
sh: line 0: exec: /path_to_directory/Lightning/_build/dev/lib/rambo/priv/rambo: cannot execute: No such file or directory

You can try renaming deps/rambo/priv/rambo-mac to deps/rambo/priv/rambo.

If neither of the approaches above work, please raise an issue.

Support

If you have any questions, feedback, or issues, please:

lightning's People

Contributors

aleksa-krolls avatar amberrignell avatar avinashdhauni avatar benedictus-dev avatar benedictus-yevu avatar davemenninger avatar delcroip avatar dependabot[bot] avatar elias-ba avatar josephjclark avatar jyeshe avatar kianmeng avatar magp18 avatar midigofrank avatar mtuchi avatar parlgy avatar piische-tph avatar rorymckinley avatar sigu avatar stuartc avatar taylordowns2000 avatar wanecode avatar zacck avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

lightning's Issues

authorization - regular users cannot access the users management page

When a user is not either an :admin or :superuser, accessing the User Management page (list of all users, editing a user that isn't themselves) should result in a Permission Denied page.

We need to determine a clean way of handling authorisation failures, with the equivalent of a 401 in LiveView.

We've had some success using BodyGuard, and think it makes sense to implement it here.

We can start with a simple UserPolicy module which has :view_all matcher, for more info see the BodyGuard docs.

If Stu picks this up, please talk it through with Elias when you're done.

As a superuser, I should be able to access the User management page (create, edit, etc).
As a regular user, I cannot see the user management page.

turn login elements into components

Given we have created components for all the form elements.
Also change the login controller layout to match the liveview layout/aka items are in the same locations on the page (using the top bar and main section).

The login pages do not use live view - but the partial templates should be compatible

Add CSP to router.ex

Something like, but not quite:

plug :put_secure_browser_headers, %{"content-security-policy" => "default-src 'self'"}

ToDo:

  • Add a sensible CSP to the router
  • Remove the exception for this rule from sobelow and

Notes:

First attempt: nccgroup/sobelow#61 (comment)
Stu says: "The issue with this policy is that all inline styles, svgs etc are blocked. This also includes any DOM changes that LiveView sends that do things inline styles like display: none; also don't work."

Scrape/collect a list of remotely available adaptors

A script (elixir, but maybe node) that maintains a list of remotely
available adaptors. The list is a file stored in (by default) the
current working directory of the application/process (e.g. ./adaptors.json)

Strategies:

  • queries NPM for all @openfn/*-adaptor and versions
  • queries github for all *-adaptor, and tags

Shape:

{ <name>: { 
  repo: "", 
  versions: [{version: "v1.0.0", sha1sum: ""}, ...],
  npm: "linky linky"
}}

Interface:
Create the AdaptorsList module with fetch()

AdaptorsList.fetch()

allow first user to create a superuser account

When the app boots and there are no users in the users table
present them with a setup page requiring password and confirm password.
Then create a superuser, and log them in (don’t prompt for password).

All user accessible routes (i.e. not webhooks) should have a plug added to the pipeline to perform this check and redirect to a 'First Setup' view.

While not explicitly included in this, the consideration for caching should be made.
This can be solved by a process that is added to the process tree on startup which checks the database and stores the result in memory - and the plug that checks if the first setup flow should be rendered will get it's answer from that.

However we solve this, there should be a function exposed from a Lightning.Instance namespace (i.e. Instance.has_superuser?/1)

Display the results of a Run

Both before and after a Run is executed the user should be able to see the current status.

By accessing the Runs list, pending, in progress and finished runs should be visible.

When selecting a specific run, all the associated details should be visible:

  • Started time
  • Finished time
  • Elapsed time
  • Log output
  • Exit code represented as both it's real exit code (number) as as a symbol (green for ok, red for anything else)

This information is found between the Run and the InvocationEvent that triggered it.

Links to the dataclip that was used for the initial state should be provided,
and also a link to the job.

NOTE while we to use the term Run a lot here, internally we may be basing most of our queries off Invocation.Event records.

add Engine as a dependency

Add the github repo url to mix.exs.

  ...,
  {:engine, github: "OpenFn/engine", tag: "v0.3.2"}

Ensure that mix openfn.install.runtime works.

Runs model

Create runs model using a generator.

image.png

started_at
finished_at
exit_code
log
job_id
------------
message_id // initial_state (???) // source // [ input ]
------------
triggering_event

triggering_event samples:

{ "type": "webhook", "state": {"data": {a:1} } }, // this is an event that's generated/captured from an external system 
{ "type": "cron", "state": {"cursor" :123 } } // this is an event that's generated by the `cron_service`

From BPMN: A Process (JOB) can be executed or performed many times, but each time is expected to follow the steps laid out in the Process model (JOB EXPRESSION, OPERATIONS). For example, the Process in Figure 10.1 will occur every Friday, but each instance (RUN) is expected to perform Task “Receive Issue List,” then Task “Review Issue List,” and so on, as specified in the model. Each instance (RUN) of a Process is expected to be valid for the model, but some instances might not, for example if the Process has manual Activities, and the performers have not had proper instruction on how to carry out the Process.

Turn dashboard and form elements into components

  • form
  • formfield (the element that wraps a form input, should take an element/input as a child slot)
  • text input
  • select input
  • checkbox
  • hint
  • button (submit) - already done
  • text area (code input - the one from the job form)

All live views should use these components.

ATNA spike

http://openhim.org/docs/5.2.x/user-guide/auditing/

https://github.com/jembi/openhim-core-js/search?p=2&q=atna

https://www.google.com/url?sa=i&url=https%3A%2F%2Fwiki.ohie.org%2Fdownload%2Fattachments%2F28838583%2FOpenHIM%2520Product%2520Overview%2520-%2520Presentation.pdf%3Fapi%3Dv2&psig=AOvVaw3A1LBzOcfbqE3gJ_2dywjO&ust=1649835909165000&source=images&cd=vfe&ved=0CAoQjRxqFwoTCMiT1-2DjvcCFQAAAAAdAAAAABAD

link to their API - http://openhim.org/docs/api/audits/overview/

What does OpenHIM have ? Audit repository, audit logs viewer, audit events on application start, stop, and user authentication

Provide a plan (or set of plans) for implementing (high-level) an ATNA repository.
Discuss afterwards how this may meet the requirements of the grant.

DICOM? Do we need to support it from the beginning?

users can change their own password

  • using a separate ‘User Settings’ page
  • must enter existing password (once) and then a new password + confirm (twice)

The singleton root should be /profile

Select an adaptor and version for a Job

Using the adaptors list, present two dropdowns containing:

  • the adaptor's name
  • a list of versions starting with latest as the default

These cannot be blank, and the selected option must be present in the
adaptors list.

AdaptorsList.versions_for("@openfn/language-http")
AdaptorsList.valid?("@openfn/[email protected]")

Add authentication

We should be using phoenix_gen_auth, this was a community library that has since been merged into Phoenix since 1.6.

As an unauthenticated user, accessing the web frontend I will get a login screen page. Once I enter my email or username (one input) and correct password, I am redirected to the original address I tried to access.
If I enter my details incorrectly, I am presented with a flash message.

This appears to be a very thorough article:

https://www.leanpanda.com/blog/authentication-and-authorisation-in-phoenix-liveview/

Make sure to skip over the authorisation parts and implement only the parts required to enforce that a user must be logged in to access any page.

Prepare state

When a Run is about to be processed, the expression and state for the job need to be persisted to disk.

There are some examples of this inside Engine here https://github.com/OpenFn/engine/blob/v0.3.2/lib/engine/run_dispatcher.ex#L111

However the above code is only used when Engine is also taking care of the queuing as well.

So in order to keep focus on a PoC for now, lets install Engine as a supervised process.

See: #34 for more detail on invocation.

Big hand wave here

While going through the source code for microservice and engine, it appears that credentials being merged into state only happens if jobs are configured via YAML file - which is how Microservice works. However we will likely need a place to process a message using Engines queue but still maintain control over what jobs and credentials look like.

or we don't use the queuing facilities and write our own file storage code based on the example mentioned at the top.

Add audit functionality to credentials

add audit table for credentials
create audit module for credentials
call audit functions on all credentials change functions: job create, edit, delete, read

Decision: separate tables for audit credentials and jobs because complexity needed to pull everything together into one table down the line is lower than having one big table to unpick

Add validation to changing a job name

In order to distinguish jobs, a user needs to be able to set the jobs name.
The name can be up to 100 characters long and must be unique.
It cannot be blank and cannot contain non-url safe characters(?)

Validation failures should appear on the form when any
of the rules are not satisfied.

Validation goes here:
https://github.com/OpenFn/lightning/blob/main/lib/lightning/jobs/job.ex#L19

Live view here:
https://github.com/OpenFn/lightning/blob/main/lib/lightning_web/live/job_live/edit.ex

Create audit records for jobs & credentials

We need to offer auditing for all sensitive models in the system, tracking changes for when, who, why and how.

Instead of using a library like Papertrail we are opting for a roll your own approach in order to give us more flexibility in terms of database structure and an easier to understand implementation.

Add audit functionality to jobs

  • add audit table for jobs
  • create audit module for jobs
  • call audit functions on all Job change functions: job create, edit, delete, read

Invocation event model

Invocation events represent a 'request' to run a job.

inserted_at
type
dataclip_id

type can be one of cron, webhook or retry - this represents how the invocation was triggered.

Edit and change a trigger via the Job page

In order to allow a job to be triggered via a webhook, a user must be able to specify
that the job is invoked via a webhook.

This option cannot be blank (disabling a job is the preferred way to 'stop' it).

Initially we present a dropdown with two options:

  • None (blank)
  • Webhook

When a webhook is selected, a trigger record is created in the database associated
with the job.

The job show/edit page must show a URL for the webhook.
<hostname>/webhook/<job.id>

Invoke a Job using Engine

See: https://github.com/OpenFn/engine/#running-without-a-supervisor

When using Engine directly, we need to provide a %Engine.RunSpec{} struct (more detail)

This implies the following:

  • The adaptors are installed and available
  • The state has been created and persisted (see #38)

One a RunSpec has been assembled calling the handler that was setup in #38 (Something.start(run_spec)).

Also required:

  • Nodejs is installed in our test runner.
  • We have a way to install language packs

Dataclips model

In order to create a run (via an invocation) we need to be able to have a dataclip.
A dataclip is a persisted chunk of data that can be used (at present) to be fed into a run.

inserted_at
updated_at
body
type

Can create, edit and delete a user

  • has a menu item that takes you to the user list with a create user form too
  • using generators we get a list and all create/edit/delete functionality

create user form initial fields:

  • first name
  • last name
  • email
  • password

Add strength policy for the password

As a user concerned about security, I want to ensure all users have strong/secure passwords.

It should be at least 8 characters long.

Here is an interesting article from Microsoft around password policies: https://docs.microsoft.com/en-us/microsoft-365/admin/misc/password-policy-recommendations?view=o365-worldwide

They claim that overly strict policies have negative effects as users work around them using repetition or easy to guess replacements (a to @, i|l to !).

Preferred mechanisms to support security is common word dictionaries and 2FA.

Create a credentials model and form

Create a credentials model via the live view context editor which has a

name (string)
body (map/jsonb)
The name cannot be blank, and the body must at least be {} (empty map'ish)
Create a credentials form which contains

name
body (is a textarea in json for now)
We are limiting this to the bare minimum for now, so different types of credentials etc is beyond the scope of this issue.

implementation notes

  1. text area input comes from the front-end as a string, but it must be saved in ecto as a map.

Users can have different roles

Using Ecto.Enum allow a users role field can be one of:

  • :superuser
  • :admin
  • :user

By default it should a :user, and cannot be blank.

Add a Roles dropdown on the user form component.

Add credentials section/menu item

Add a credentials section/menu item to the top menu.
When this is clicked, I should see a list of existing credentials and a create button which takes me to the credentials form

Generic webapp setup

@stuartc , gave this a "13" per yesterday + today. feel free to create sub-issues and close this at EOD today, or add a comment and keep in progress.

Allow for custom Node

In order to present customised blocks to users, the BlockEditor needs to be able to be given a set of components that can resolve certain patterns (an AST node containing certain types and properties).

Possibly the best way to start this would be to create a component for something built into ECMAScript, like a for loop. By creating the component inside the project, but not including it in Node's nodeComponents list and passing it in via somewhere upstream we can exercise the resolving of the component (determining which component to render) and the ability to extend the list of components from which to choose from.

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.