Code Monkey home page Code Monkey logo

seal's Introduction

Seal

Build Status

What is it?

This is a Slack bot that publishes a team's pull requests to their Slack Channel, once provided the organisation name and the team members' github names. It is my first 20% project at GDS.

image image

How to use it?

Config

Fork the repo and add/change the config files that relate to your github organisation. For example, the alphagov config file is located at config/alphagov.yml and the config for scheduled daily visits can be found in bin

Include your team's name, the Github names of your team members, and the Slack channel you want to post to.

In your shell profile, put in:

export SEAL_ORGANISATION="your_github_organisation"
export GITHUB_TOKEN="get_your_github_token_from_yourgithub_settings"
export SLACK_WEBHOOK="get_your_incoming_webhook_link_for_your_slack_group_channel"
export GITHUB_API_ENDPOINT="your_github_api_endpoint" # OPTIONAL If you are using a Github Enterprise instance

Env variables

Another option, which is 12-factor-app ready is to use ENV variables for basically everything. In that case you don't need a config file at all.

Divider is ',' (comma) symbol.

In your shell profile, put in:

export SEAL_ORGANISATION="your_github_organisation"
export GITHUB_TOKEN="get_your_github_token_from_yourgithub_settings"
export GITHUB_API_ENDPOINT="your_github_api_endpoint" # OPTIONAL If you are using a Github Enterprise instance
export SLACK_WEBHOOK="get_your_incoming_webhook_link_for_your_slack_group_channel"
export SLACK_CHANNEL="#whatever-channel-you-prefer"
export GITHUB_MEMBERS="netflash,binaryberry,otheruser"
export GITHUB_USE_LABELS=true
export GITHUB_EXCLUDE_REVIEWED=true
export GITHUB_EXCLUDE_LABELS="[DO NOT MERGE],Don't merge,DO NOT MERGE,Waiting on factcheck,wip"
export GITHUB_EXCLUDE_REPOS="notmyproject,someotherproject" # Ensure these projects are *NOT* included
export GITHUB_INCLUDE_REPOS="definitelymyproject,forsuremyproject" # Ensure *only* these projects will be included
export SEAL_QUOTES="Everyone should have the opportunity to learn. Don’t be afraid to pick up stories on things you don’t understand and ask for help with them.,Try to pair when possible."

Bash scripts

In your forked repo, include your team names in the appropriate bash script. Ex. bin/morning_seal.sh

Local testing

To test the script locally, go to Slack and create a channel or private group called "#angry-seal-bot-test" (the Slack webhook you set up should have its channel set to "#angry-seal-bot-test" in the Integration Settings). Then run ./bin/seal.rb your_team_name in your command line, and you should see the post in the #angry-seal-bot-test channel.

If you don't want to post github pull requests but a random quote from your team's quotes config, run ./bin/seal.rb your_team_name quotes

Slack configuration

You should also set up the following custom emojis in Slack:

  • :informative_seal:
  • :angrier_seal:
  • :seal_of_approval:
  • :happyseal:
  • :halloween_informative_seal:
  • :halloween_angrier_seal:
  • :halloween_seal_of_approval:
  • :festive_season_informative_seal:
  • :festive_season_angrier_seal:
  • :festive_season_seal_of_approval:
  • :manatea:

You can use the images in images/emojis that have the corresponding names.

When that works, you can push the app to Heroku and add the GITHUB_TOKEN and SLACK_WEBHOOK environment variables to heroku.

Use the Heroku scheduler add-on to create repeated tasks - I set the seal to run at 9.30am every morning (the seal won't post on weekends). The scheduler is at https://scheduler.heroku.com/dashboard and the command to run is bin/seal.rb your_team_name

Any questions feel free to contact me on Twitter - my handle is binaryberry

Deploy to Heroku

Deploy

How to run the tests?

Just run rspec in the command line

Docker container

You can build your own docker container, Dockerfile is provided.

docker build -t seal .

And then run it (assuming you already set all the env variables)

#!/bin/bash
docker run -it --rm --name seal \
  -e "SEAL_ORGANISATION=${SEAL_ORGANISATION}" \
  -e GITHUB_TOKEN=${GITHUB_TOKEN} \
  -e GITHUB_API_ENDPOINT=${GITHUB_API_ENDPOINT} \
  -e SLACK_WEBHOOK=${SLACK_WEBHOOK} \
  -e DYNO=${DYNO} \
  -e SLACK_CHANNEL=${SLACK_CHANNEL} \
  -e GITHUB_MEMBERS=${GITHUB_MEMBERS} \
  -e GITHUB_EXCLUDE_REVIEWED=${GITHUB_EXCLUDE_REVIEWED} \
  -e GITHUB_USE_LABELS=${GITHUB_USE_LABELS} \
  -e "GITHUB_EXCLUDE_LABELS=${GITHUB_EXCLUDE_LABELS}" \
  -e "SEAL_QUOTES=${SEAL_QUOTES}" \
  seal

Diary of how this app was built

10th April:

  • Finalised what the app does:

    • Checks if any pull requests are over 2 days old (angry seal)
    • at 9.45am prepares an overview of all pull requests that need reviewing (informative seal)
    • when a team member posts a new pull request, publishes it on slack (notification seal) (this feature was then abandoned, as I thought it's best for this not to be automated, it's more motivating when real people ask for a review, not a bot)
  • Researched what technologies and gems I could use and what the app structure would be like

  • Started the Rails app

17th April:

  • researched github_api

24th April:

  • got github_api working, first test with dummy data passing

1st of May

  • got github_listener to take into account organisations

8th of May:

  • tried stubbing Github API

15th of May:

21st of May:

  • Building Sinatra app for stubbing
  • Attempts to filter the 3,400 repo list by date the repo was modified - tests (and app) are still very slow

28th of May:

  • Due to new team structure that assigns a list of repos to each team, decided the manually enter the list of repos that belong to a team instead of scanning through all of them. That should sort the speed problem, the stubbing problem, a lot of things.
  • moved to a non-Rails app to make it lighter
  • got Github to list repos; works for my personal pull requests, but doesn't work with the GOV.UK stuff yet, need to figure out why

5th of June:

  • Github fetcher class now works with organisations, hence GOV.UK stuff

11th of June:

  • Stubbed Github API

12th of June:

  • Github Fetcher now also returns the link to the pull request
  • Started Slackbot poster class, it can post to Slack
  • Started Message builder class, not finished

13th of June:

  • finished Slackbot poster class
  • finished Message builder class
  • added name of repo to info sent by GithubFetcher
  • added options to the Slackbot poster class so each post comes from informative seal
  • Works locally in test room. YEAY!

17th of June:

  • refactoring of Github fetcher
  • pushed to Heroku

18th of June:

  • started Heroku scheduler and launched on core-formats team room!

22nd of June

  • Ensure Angry seal doesn't get triggered at the weekend

26th of June:

  • launched on finding-stuff team
  • investigated adding comments

3rd of July:

  • launched on custom team
  • added comments
  • investigating angry seal - stuck by a strange syntax error line 34 when trying to run the script

10th of July

  • Extracted config information to yaml files
  • fixed informative seal - mood_hash was causing issues

17th of July

  • Angry Seal!

21st of July

  • fix API stubbing

22nd of July

  • Add age of pull requests

24th of July

  • Optionally display labels for a pull request, if they exist (use_labels config option)
  • Optionally hide pull requests with certain labels (exclude_labels config list)
  • Optionally hide pull requests with certain phrases in the title (exclude_titles config list)

19th of August

  • Seal will bark at all teams in the config if no team is specified as an argument to ./bin/{angry,informative}_seal.rb, making Heroku scheduling easier.

4th of September

  • Seal now displays thumbs up when present in comments
  • Angry seal now looks angrier

13th of September

  • Seal now has only one script to be triggered - will start either the angry seal, the informative seal or the seal of approval each day.

6th of January

  • Seal can now post random quotes from the team's list of quotes in the config. Can be used as a reminder or for inspirational quotes!

7th of February

  • Seal can exclude pull requests that have been already reviewed by at least one peer, regardless of approval status

Tips

How to list your organisation's repositories modified within the last year:

In irb, from the folder of the project, run:

require 'octokit'
github = Octokit::Client.new(:access_token => ENV['GITHUB_TOKEN'], auto_pagination: true)
response = github.repos(org: ENV['SEAL_ORGANISATION'])
repo_names = response.select { |repo| Date.parse(repo.updated_at.to_s) > (Date.today - 365) }.map(&:name)

CRC

Github fetcher:

  • queries Github's API

Message Builder:

  • produces message from Github API's content

Slack Poster:

  • posts message to Slack

Trello board: https://trello.com/b/sgakJmlY/moody-seal

License

See the LICENSE file for license rights and limitations (MIT).

seal's People

Contributors

36degrees avatar benilovj avatar benlovell avatar binaryberry avatar boffbowsh avatar bushong1 avatar carvil avatar cbaines avatar chao-xian avatar deanwilson avatar dependabot[bot] avatar emmabeynon avatar h-lame avatar issyl0 avatar jackscotti avatar johnsyweb avatar kevindew avatar matthewford avatar maxgds avatar netflash avatar nickcolley avatar peteleaman avatar rubenarakelyan avatar sihugh avatar stig avatar surminus avatar suzannehamilton avatar thehenster avatar thomasleese avatar tijmenb 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  avatar  avatar  avatar

seal's Issues

Quotes broken?

Hi,

I'm trying to get the quotes feature to work for our team, but I keep getting the following error:

/Users/stig/tmp/seal/lib/message_builder.rb:69:in `bark_about_quotes': undefined method `sample' for nil:NilClass (NoMethodError)
	from /Users/stig/tmp/seal/lib/message_builder.rb:12:in `build'
	from /Users/stig/tmp/seal/lib/seal.rb:37:in `bark_at'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `block in bark'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `each'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `bark'
	from ./bin/seal.rb:5:in `<main>'

I spent a long time trying to debug my config before I started wondering if this worked at all, and at least for me it does not. Witness this session, checking out master and building it:

16:18:46 ~/tmp> git clone https://github.com/binaryberry/seal
Cloning into 'seal'...
remote: Counting objects: 1792, done.
remote: Total 1792 (delta 0), reused 0 (delta 0), pack-reused 1792
Receiving objects: 100% (1792/1792), 684.11 KiB | 353.00 KiB/s, done.
Resolving deltas: 100% (916/916), done.
16:18:54 ~/tmp> cd seal
16:18:57 ~/t/seal (master)> bundle install --path vendor/bundle
Your Ruby version is 2.2.0, but your Gemfile specified 2.2.6
16:19:04 ~/t/seal (master) [18]> rbenv shell 2.2.6
16:19:14 ~/t/seal (master)> bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/...........
Fetching version metadata from https://rubygems.org/..
Fetching dependency metadata from https://rubygems.org/.
Installing rake 12.0.0
Installing public_suffix 2.0.5
Installing byebug 9.0.6 with native extensions
Installing coderay 1.1.1
Installing daemons 1.2.4
Installing diff-lcs 1.3
Installing eventmachine 1.2.1 with native extensions
Installing multipart-post 2.0.0
Installing ffi 1.9.17 with native extensions
Installing formatador 0.2.5
Installing rb-fsevent 0.9.8
Installing lumberjack 1.0.11
Installing nenv 0.3.0
Installing shellany 0.0.1
Installing method_source 0.8.2
Installing slop 3.6.0
Installing thor 0.19.4
Installing guard-compat 1.2.1
Installing rspec-support 3.5.0
Installing multi_xml 0.6.0
Installing oj 2.18.1 with native extensions
Installing trollop 2.1.2
Installing rack 1.6.5
Installing tilt 2.0.6
Installing timecop 0.8.1
Using bundler 1.14.6
Installing addressable 2.5.0
Installing faraday 0.11.0
Installing rb-inotify 0.9.8
Installing notiffany 0.1.1
Installing pry 0.10.4
Installing rspec-core 3.5.4
Installing rspec-expectations 3.5.0
Installing rspec-mocks 3.5.0
Installing httparty 0.14.0
Installing jsonlint 0.2.0
Installing rack-protection 1.5.3
Installing thin 1.7.0 with native extensions
Installing sawyer 0.8.1
Installing listen 3.1.1
Installing pry-byebug 3.4.2
Installing rspec 3.5.0
Installing slack-poster 1.0.1
Installing sinatra 1.4.7
Installing octokit 4.6.2
Installing guard 2.14.0
Installing guard-rspec 4.7.3
Bundle complete! 11 Gemfile dependencies, 47 gems now installed.
Bundled gems are installed into ./vendor/bundle.
Post-install message from httparty:
When you HTTParty, you must party hard!
16:19:51 ~/t/seal (master)> set -xg SLACK_CHANNEL platform-team^C
16:20:06 ~/t/seal (master)> bundle exec bin/afternoon_seal.sh 
/Users/stig/tmp/seal/lib/message_builder.rb:69:in `bark_about_quotes': undefined method `sample' for nil:NilClass (NoMethodError)
	from /Users/stig/tmp/seal/lib/message_builder.rb:12:in `build'
	from /Users/stig/tmp/seal/lib/seal.rb:37:in `bark_at'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `block in bark'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `each'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `bark'
	from ./bin/seal.rb:5:in `<main>'
/Users/stig/tmp/seal/lib/message_builder.rb:69:in `bark_about_quotes': undefined method `sample' for nil:NilClass (NoMethodError)
	from /Users/stig/tmp/seal/lib/message_builder.rb:12:in `build'
	from /Users/stig/tmp/seal/lib/seal.rb:37:in `bark_at'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `block in bark'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `each'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `bark'
	from ./bin/seal.rb:5:in `<main>'
/Users/stig/tmp/seal/lib/message_builder.rb:69:in `bark_about_quotes': undefined method `sample' for nil:NilClass (NoMethodError)
	from /Users/stig/tmp/seal/lib/message_builder.rb:12:in `build'
	from /Users/stig/tmp/seal/lib/seal.rb:37:in `bark_at'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `block in bark'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `each'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `bark'
	from ./bin/seal.rb:5:in `<main>'
/Users/stig/tmp/seal/lib/message_builder.rb:69:in `bark_about_quotes': undefined method `sample' for nil:NilClass (NoMethodError)
	from /Users/stig/tmp/seal/lib/message_builder.rb:12:in `build'
	from /Users/stig/tmp/seal/lib/seal.rb:37:in `bark_at'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `block in bark'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `each'
	from /Users/stig/tmp/seal/lib/seal.rb:20:in `bark'
	from ./bin/seal.rb:5:in `<main>'
16:20:14 ~/t/seal (master) [1]> 

Show @username mentions beside the PR instead of plain text

Would require some sort of mapping config so that the github names can be linked to slack usernames.

We have a dev channel, where everything gets posted, but some ignore it unless @username mentions happen. Having the @username would be great so they are alerted

Use GitHub search to power the seal

I noticed our team is missing a few repos in the config, but instead of adding them I thought it would be nice to let the seal pick up new ones automatically.

Currently, the seal loops through all repos and fetches all pull requests. We could replace this with the issue search: https://developer.github.com/v3/search/#search-issues

require 'octokit'
client = Octokit::Client.new
client.search_issues("is:pr is:open user:alphagov")

This will return all open PRs in all alphagov repos. It uses the same search syntax as github.com (like this).

This would mean 1) we can get rid of the repos in the configuration, 2) new repos are added automatically, 3) we save a bunch of requests.

Thoughts?

How to make PRs for repos go to only some channels now?

We have a private fork because we don't want to confuse everyone with our config files. We've found it useful to have multiple slack channels / teams getting different list of PRs based on the repos in the config file. Yesterday I upgraded to current master because I want to be able to send the different quotes to each team channel. Unfortunately this PR broke our seal.

I believe this commit is the culprit: 12f37c6 (Yeah. It's a while since we upgraded...) Now the seal moans about every PR in every channel, ignoring each of our teams' repos list. Is there a way to get the seal to filter PRs to only the configured repos again? (Without reverting these changes, which I understand are good for other reasons.)

Is this something the members setting is meant to address? members is much more fluid than repos for us: We rarely add repos, but members move around a lot. Therefore we specify members: [] everywhere and would rather not change that.

If I have to add code to do this, would something like #94 but for including repos rather than excluding them be on the table?

undefined method `[]' for nil:NilClass (NoMethodError) if not setting `SLACK_CHANNEL=`

If I follow the README and try to specify the slack channel on the command line I get this error:

bundle exec bin/seal.rb '#seal-test' 
/Users/stig/work/seal/lib/seal.rb:34:in `bark_at': undefined method `[]' for nil:NilClass (NoMethodError)
    from /Users/stig/work/seal/lib/seal.rb:16:in `block in bark'
    from /Users/stig/work/seal/lib/seal.rb:16:in `each'
    from /Users/stig/work/seal/lib/seal.rb:16:in `bark'
    from bin/seal.rb:5:in `<main>'

If I set SLACK_CHANNEL in the environment and just leaves out the argument it works:

env SLACK_CHANNEL='#seal-test' bundle exec bin/seal.rb 
{"icon_emoji":":seal_of_approval:","username":"Seal of Approval","channel":"#angry-seal-bot-test","text":"Good morning team! It's a beautiful day! :happyseal: :happyseal: :happyseal:\n\nNo pull requests to review today! :rainbow: :sunny: :metal: :tada:"}

(Although because I'm doing this locally, and the DYNO environment variable is not set, it's not using the SLACK_CHANNEL variable.)

SLACK_CHANNEL env var is not optional

Hi,

Thanks for the great tool!

The docs make it sound like SLACK_CHANNEL can optionally be provided as an env var, but without it I get:

~ $ ./bin/seal.rb qut
/app/lib/seal.rb:38:in `bark_at': undefined method `[]' for nil:NilClass (NoMethodError)
	from /app/lib/seal.rb:20:in `block in bark'
	from /app/lib/seal.rb:20:in `each'
	from /app/lib/seal.rb:20:in `bark'
	from ./bin/seal.rb:5:in `<main>'

This is despite having the channel set in my config file.

I don't know Ruby at all, but I can't help but notice it's the only dereferencing of ENV that uses double quotes instead of single. Could that be the reason?

Disable no prs to review today

Is there anyway to disable the no prs to review today... We have like 10 channels that get hit with this message when there is no prs to review, I rather just disable.

Seal ignores PRs with the same title

If you have several pull requests with the same title (e.g. because you've fixed the same issue in different projects), the seal only reports one of them.

I suspect this is because GithubFetcher returns a hash of the PRs with the title as the key. Line 26:

pull_requests[pull_request.title] = present_pull_request(pull_request, repo_name)

NoMethodError traceback on slack_poster.rb:32:in `send_request'

Hiya,

I built a docker image per the instructions, from current master (108b95f).

I'm getting an exception on trying to post to Slack:

/usr/src/app # bundle exec bin/seal.rb 
{"icon_emoji":":angrier_seal:","username":"Angry Seal","channel":
<stripped>
\n\nRemember each time you forget to review your pull requests, a baby seal dies.\n    \n\n"}
bundler: failed to load command: bin/seal.rb (bin/seal.rb)
NoMethodError: undefined method `success?' for "ok (200)":String
Did you mean?  succ!
  /usr/src/app/lib/slack_poster.rb:32:in `send_request'
  /usr/src/app/lib/seal.rb:40:in `bark_at'
  /usr/src/app/lib/seal.rb:20:in `block in bark'
  /usr/src/app/lib/seal.rb:20:in `each'
  /usr/src/app/lib/seal.rb:20:in `bark'
  bin/seal.rb:5:in `<top (required)>'

This is for querying from a GH Enterprise setup, but I can confirm that I do get open PRs properly in the body. Editing https://github.com/binaryberry/seal/blob/master/lib/slack_poster.rb#L32 as follows gets things working:

        raise SlackResponseError, "Posting to webhook failed with: #{response.status} #{response.reason_phrase} #{response.body}" unless response.include?("200")

So it appears to be and issue in trying to handle a string response as a response type object?

Make this repo organisation-neutral

Currently this repo is oriented towards GDS since it contains configuration with our teams. We now have a fork at https://github.com/alphagov/seal which should contain our configuration. With that, this repo can now be made organisation-neutral so that there isn't any confusion about whether others can run it or not.

Add Dockerfile

I would be nice to have a Dockerfile so folks can run it in docker infra.

:happyseal: image is missing

Just had this message:

Good morning team! It's a beautiful day! :happyseal: :happyseal: :happyseal:

The README only mentions:

You should also set up the following custom emojis in Slack:

:informative_seal:
:angrier_seal:
:seal_of_approval:

You can use the images in images/emojis that have the corresponding names.

and the images/emoji directory doesn't contain any happy seal images :-(

Filter out any PR that has reviews

Just opening this issue to link this PR - not sure how you prefer but I find other projects prefer an issue paired with a PR.

This allows one to have the list only include PRs that have no reviews at all yet.

#259

Allow deployment outside of Heroku

We don't deploy on Heroku and right now I have to manually set the DYNO env variable.

It would be great if you would refactor this to something like TEST=true/false or APP_ENV=test/production.

Create Helm Chart

To make it more convenient to run this bot inside the Kubernets cluster it would be nice to have a helm chart.

For those who doesn't familiar with what helm charts is - https://github.com/kubernetes/helm/blob/master/docs/charts.md

I already have my opinionated solution, which I gonna polish and pull-request somewhere next week.

Any ideas and/or thoughts are highly appreciated!

Feature request: add ability to trigger the bot

Hey there,

We've been using seal for some time and it helped us in our managing our git-workflow. One feature that we found useful (and which I implemented in a fork) is being able to trigger the bot.

This can be done fairly easily with slack-ruby-bot gem that does all the web socket and Real time messaging heavy lifting. Would you be interested to add this functionality? If so, I would be happy to submit a PR.

Quotes broken?

Is the quote feature broken? It only works for me, if I set the SEAL_QUOTES environment variable but it seems not to take the quotes from the YAML file.

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.