Coder Social home page Coder Social logo

hacktoberfest / hacktoberfest-2020 Goto Github PK

View Code? Open in Web Editor NEW
496.0 28.0 147.0 73.97 MB

Hacktoberfest - App to manage the annual open-source challenge, used for the 2019 & 2020 seasons.

Home Page: https://hacktoberfest.digitalocean.com

License: Other

Ruby 60.50% JavaScript 1.34% HTML 24.47% Shell 0.69% SCSS 12.79% Dockerfile 0.14% Makefile 0.07%
hacktoberfest ruby

hacktoberfest-2020's Introduction

Hacktoberfest

GitHub stars GitHub issues GitHub forks GitHub language count GitHub top language
Snyk Vulnerabilities for GitHub Repo

Hacktoberfest-2020 Logo

Features

  • Static pages
  • Airtable backed events and FAQs
  • Issue discovery by language
  • Log in with GitHub
  • Multi-step registration
  • Pull request timeline
  • Challenge completion validation
  • Prize distribution

Components

There are three major components that are entirely separate from one another:

  1. Issue discovery - Fetches issues from GitHub with the label hacktoberfest and persists them in the database to be featured on the homepage based on a randomized quality filter.

  2. Content pages - Primarily static pages that are supplemented with dynamic data from Airtable.

  3. Participant management - Allows users to register to participate in Hacktoberfest, tracks user progress, and distributes prizes based on availability. The majority of the business logic is implemented with a state machine, validating that various conditions are met before a user may be transitioned to a new state and with certain actions being triggered on successful state transitions.

Getting Started

These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.

Prerequisites

  • Ensure your os is the latest MacOS

  • Have brew installed (Run the following command in a mac os terminal to install):

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Application Setup

  1. Install and Setup
  2. Local Setup With Docker
  3. Setup Oauth Token
  4. Configure remaining environment variables
  5. Create first user
  6. Import Projects

Installing

Clone the repo:

git clone https://github.com/digitalocean/hacktoberfest

In your local repository, run script/setup, which will install all necessary dependencies for you:

script/setup

Local Setup With Docker

If you would like to use Docker to work with the application, first make sure that you have Docker and Docker Compose on your local machine.

Next clone the repo as described in Step 1.

From there, create a local .env file, and add fill out the following values, using steps 3 and 4 to guide you:

HACKTOBERFEST_DATABASE_HOST=database
HACKTOBERFEST_DATABASE_USERNAME=hacktoberfest
HACKTOBERFEST_DATABASE_PASSWORD=sekret
HACKTOBERFEST_API_KEY=sekret
REDIS_HOST=redis
REDIS_PORT=6379
DALLI_SERVER=memcached
GITHUB_CLIENT_ID=<fill-in-for-dev-setup>
GITHUB_CLIENT_SECRET=<fill-in-for-dev-setup>
START_DATE=<fill-in-for-dev-setup>
RULES_DATE=<fill-in-for-dev-setup-same-as-start>
END_DATE=<fill-in-for-dev-setup>
AIRTABLE_API_KEY=<fill-in-for-dev-setup>
AIRTABLE_APP_ID=<fill-in-for-dev-setup>
SEGMENT_WRITE_KEY=<leave-blank>
TEST_USER_GITHUB_TOKEN=<leave-blank>

Note: Use the following values when setting up your Oauth token:

> Homepage URL: `http://localhost`\
> Authorization callback URL: `http://localhost/auth/github/callback`

The local Docker setup uses a webserver, in the same way that the application does in staging and production, so it will be reachable on port 80.

Run the startup script, ./script/docker-startup.sh to start your services.

Note: You do not need to use the other startup scripts in the repo if you are using Docker to run the application locally. When using Docker, follow the steps in this section of the README.

Inspecting and Troubleshooting

You can inspect whether or not your services have started successfully by running the check script: ./script/check.sh. You will see the following output if the services are all running:

         Name                         Command               State           Ports         
-------------------------------------------------------------------------------------------
hacktoberfest_app_1         ./script/docker-entrypoint.sh    Up      3000/tcp              
hacktoberfest_database_1    docker-entrypoint.sh postgres    Up      0.0.0.0:5432->5432/tcp
hacktoberfest_redis_1       docker-entrypoint.sh redis ...   Up      6379/tcp              
hacktoberfest_sidekiq_1     ./script/sidekiq-entrypoint.sh   Up      3000/tcp              
hacktoberfest_webserver_1   nginx -g daemon off;             Up      0.0.0.0:80->80/tcp    

In cases where you need to investigate an exit status, you can get the logs of the service with docker-compose logs <service-name>.

To check that the application is ready to accept traffic, run docker-compose logs app. You should see the following output:

app_1        | ==> Hacktoberfest is now ready to go!
app_1        | => Booting Puma
app_1        | => Rails 5.2.3 application starting in development 
app_1        | => Run `rails server -h` for more startup options
app_1        | Puma starting in single mode...
app_1        | * Version 4.1.1 (ruby 2.5.8-p224), codename: Fourth and One
app_1        | * Min threads: 5, max threads: 5
app_1        | * Environment: development
app_1        | * Listening on tcp://0.0.0.0:3000
app_1        | Use Ctrl-C to stop

Once the app is running, you can connect to it by navigating to localhost. Please note that trying to connect to the app at localhost before it is ready will result in 502 Bad Gateway error, so be sure to check the logs first.

Testing

If you would like to run commands against your app service, you can do that with the following command (using rubocop as an example):

./script/test-command.sh bundle exec rubocop app config db lib spec --safe-auto-correct

Or to run a particular spec:

./script/test-command.sh bundle exec rspec <your-spec-file>

Running migrations

In cases where you want to create a migration in the context of your current development, you can use the following command:

docker-compose exec app rails g migration <your migration>

To run the migration, type:

docker-compose exec app bundle exec rake db:migrate

In both cases, the relevant files and changes will be available on your host, as well as on your container.

If the app is currently stopped and you need to run migrations, you can use the restart-app script, which will restart the app and run any pending migrations. See the explanation below for more detail.

Reloading the server

There are cases where you will need to stop and restart the Rails server, in order for things like configuration changes to take effect.

To do this, run the following script to stop and restart the app: ./script/restart-app.sh.

This will restart the app and run any pending migrations.

Adding a new gem to the project

Another task you may need to accomplish is adding a new gem to the project. Because this local Docker setup depends on a gem volume (to speed up development and boot times), you need to both stop the application and remove this volume for your changes to take effect.

To do this, run the following script: ./script/rebuild-app.sh.

Taking the setup down

To stop your services and remove the network, you can run docker-compose down.

Or, if you would like to remove your build cache and volumes, you can use the stop-and-clean script: ./script/stop-and-clean.sh.

Setup Oauth Token

Hacktoberfest uses GITHUB_CLIENT_ID & GITHUB_CLIENT_SECRET variables to configure OmniAuth.

This allows users to be authorized for Hacktoberfest via Github.

For this, you will have to create a Github OAuth App (https://developer.github.com/apps/building-oauth-apps)

Be sure your OAuth app is configured with the following URLs

Oauth Config

Homepage URL: http://localhost:3000
Authorization callback URL: http://localhost:3000/auth/github/callback

The Client ID and Client Secret are right above this configuration. Use them to set the following ENV variables:

GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

ENV Variables

Start Date & End Date

Hacktoberfest is officially active from October 1st - October 31st (in any timezone)

The app can be in three different states:

  • Pre-launch
    • Users can sign up and all pages are reachable, but the profile page is not yet tracking pull requests
  • Active
    • All pages are active and the profile is tracking PRs
  • Finished
    • Hacktoberfest has declared its winners

So your dates can look something like this if you're developing in October 2019 and you want the app in the Active state.

 START_DATE="2019-09-30 10:00:00 UTC"
 END_DATE="2019-11-01 12:00:00 UTC"

(These timestamps account for the furthest positive UTC offset (+14 in Kiribati), where they’ll see 1st Oct 00:00 on 30th Sept 10:00 UTC and the furthest negative UTC offset (-12 in the US Outlying Islands), where they’ll see 1st Nov 00:00 on 1st Nov 12:00 UTC).

If you want to work on the app in the Pre-Launch state, set the start date to a future date. If you want to work on the app in the Finished state, set the end date to a past date.

Airtable API Key & Airtable App ID

Hacktoberfest uses Airtable as a CMS to hold useful data such as:

  • Events
  • FAQ
  • Spam Repositories

For your convenience we have created two options:

Create an Airtable Database:

We created a read-only copy of what the Airtable database should look like.

With this you can create your own schema by following this format: (https://airtable.com/shrqM142bVC1Gj2t8)

After you’ve created and configured the schema of an Airtable base from the graphical interface,

your Airtable base will provide its own API to create, read, update, and destroy records.

You should update these variables accordingly in your .env

Use Placeholder Airtable Service

If configuring your own Airtable schema does not sound like your cup of tea - don't fret.

We have created placeholder service objects that will render test data if your Airtable keys are not set.

This service will be used as default.

You can find this service in app/services/airtable_placeholder_service.rb

Create First User

  1. Spin up the server by running script/server

  2. Now, open your browser of choice and visit localhost:3000

  3. Click START HACKING on the top right of the navigation bar

start hacking

  1. Log in with your github account

  2. Agree to the terms and conditions and continue

Import Projects

This task imports repositories to the hacktoberfest app(these are displayed on the homepage). If you don't run the task, there simply won't be any repositories on the homepage aside from the hard-coded climate change repos.

  1. Spin up sidekiq:
script/sidekiq
  1. In a separate terminal window, run the import script:
bin/rails github:fetch_popular_languages_projects

Running the project

There are two commands you will need for running the project.

First, spin up the rails server locally:

script/server

If you will be running any background jobs through sidekiq, run the following command in a separate terminal window from script/server which will spin up both redis and sidekiq:

script/sidekiq

Contributing

Hacktoberfest is open source and we welcome contributions. See CONTRIBUTING.md for more info.

Code of Conduct

This project uses the Contributor Covenant Code of Conduct. See CODE_OF_CONDUCT.md for more info.

Credits

The Hacktoberfest app is built and maintained for DigitalOcean by developers from Raise.dev.

License

Licensed under the MIT License. The full license text is available in LICENSE.md.

hacktoberfest-2020's People

Contributors

clkent avatar dependabot[bot] avatar detachhead avatar drph4nt0m avatar flyf-sh avatar fridaland avatar hackerbone avatar igufi avatar j-arens avatar jagnani73 avatar jhaff avatar johndbritton avatar jonverson avatar katjuell avatar lpercivaldev avatar lukeocodes avatar lupinitylabs avatar mattipv4 avatar mkcode avatar piperchester avatar quezler avatar raihan71 avatar razanjoshi avatar reginaalyssa avatar ruf-io avatar sagunji avatar sanketsaurav avatar shivaylamba avatar srivastavaayu avatar timonvs 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hacktoberfest-2020's Issues

List of participating repositories and issues

Hacktoberfest issues and repo displays will be personalized based on the user

TO-DO:

  • "Hacktoberfest” repo metadata - should be randomized, displaying different results each time you visit the page.

    • Balance of displayed repos- we should not show repos more than once.
  • Some ranking based on attributes like number of stars, watchers, and/or forks could- potentially be used to filter low quality/activity projects

    • Determine whether we will use Github trending API
  • Logged in users should receive personalized results specifically highlighting issues that contain:

    • code in languages with which they are familiar
  • Issues should be filterable by language

Repo / issue import takes query string arguent

Steps:

  • Move the content of our github:fetch_and_import_hacktoberfest_projects rake task into a service object.
  • Allow that service object to accept a query string param which is passed to the graphql search query
  • Create job that calls that service object
  • Create a rake task which does a search query for every programming language

Transactional emails

Allow users to sign up for a notification for when Hacktoberfest begins.

  • User can opt-in for marketing emails

  • Background job for emails or client side

  • Store user information

    • Determine what user information to store
  • Implement the ability to send an email to the users

    • Determine if the app should send the email
    • Integrate with mailing list API
  • Users who don't opt-out of emails will receive emails with Marketo tool
    Note: mail merge

Sign Up

Users will register for Hacktoberfest via GitHub oAuth
Sign-up flow includes:

  • Confirmations that they’ve read the rules / values

  • Email opt-in for Hacktoberfest and Digital Ocean open source topics

  • Determine if we will capture email or email from github.

Meetups/Events: Switch `Event Start Date/Time` to `Event Start Date`

There's a potential edge case issue where Airtable's inference of time zone in the datetime input could cause an event input as 2019-10-20 20:00 to be rendered as Oct 21. In order to avoid this, Kamal has already switched the Airtable form input to ask for Event Start Date (new column) instead of Event Start Date/Time (old column), and all previously submitted events have had the Event Start Date populated.

Now we need to switch it in the code.

What needs to change
On the meetups/events page, switch:

from OLD Airtable Column/Attribute:

  • Name: Event Start Date/Time
  • Type: DateTime

to NEW Airtable Column/Attribute:

  • Name: Event Start Date
  • Type: Date

Preserve FAQ order from Airtable

Currently, the FAQ items in each section appear to be pretty randomly ordered, which looks odd.

It'd be great if we could preserve the order that they are in, in Airtable, so that we have full control over the order they're presented in :)

Webinars section

Maintainer/Organizer/Participant Webinars

Have ability to schedule and accept RSVPS for webinars targeted for Maintainers/Organizers/Participants

  • Display upcoming webinars

  • List of webinars will come from AirTable

  • Webinar details to be displayed include:

    • title
    • key-takeaways (max of 5 bullet points)
    • date & time
    • CTA button/URL to register
  • Register for Webinars

    • Determine what form for webinar will contain
    • Determine where data will be stored
  • CTA button/URL to register

  • Email registration will post to Marketo with webinar id

  • Marketo will send notifications

  • Determine whether this will be a section or page

Segment User updating

Along with the events we will be sending, we should be updating the user information via identify method calls.

The following info should be sent to segment for users:

id
github name
open pull request count
state

Issue filtering by language

Add a dropdown button on the homepage that will filter the projects by programming language.

  • Add dropdown button with programming languages
  • Ajax page update to replace the projects section without a full page reload
  • Show any currently applied filter
  • Remove filter button when a filter is applied

Refactor segment Service

We need to change this to an after_transition so it's only called when the transition is successful: https://github.com/raise-dev/hacktoberfest/blob/master/app/models/user.rb#L46

Then this method should moved and called from a new service that is written in a before_transition so it updates always, in the case the transition was not successful and only in the appropriate states. This should be done a new service called UserPullRequestSegmentUpdator. https://github.com/raise-dev/hacktoberfest/blob/master/app/services/user_state_transition_segment_service.rb#L31-L33

In short, we need to send segment updated PR counts every time a transition is attempted (before_transition) and the tracking events, only when a successful transition is made (after_transition)

[Airbrake] [Production] Message must include a Sidekiq::Worker class, not class name: [TransitionAllUsersJob, ApplicationJob, ActiveJob::Base, Airbrake::Rails::ActiveJob, ActiveJob::Translation, ActiveJob::Logging, ActiveJob::Exceptions, ActiveJob::Cal...

Airbrake error: #1473
Airbrake project: Hacktoberfest

Error type: ArgumentError
Error message: Message must include a Sidekiq::Worker class, not class name: [TransitionAllUsersJob, ApplicationJob, ActiveJob::Base, Airbrake::Rails::ActiveJob, ActiveJob::Translation, ActiveJob::Logging, ActiveJob::Exceptions, ActiveJob::Callbacks, ActiveSupport::Callbacks, ActiveJob::Execution, ActiveSupport::Rescuable, ActiveJob::Enqueuing, ActiveJob::QueuePriority, ActiveJob::QueueName, ActiveJob::QueueAdapter, ActiveJob::Core, ActiveSupport::ToJsonWithActiveSupportEncoder, Object, PP::ObjectMixin, Act...
Where: sidekiq
Occurred at: Sep 27, 2019 01:00:04 UTC
First seen at: Sep 24, 2019 01:00:02 UTC
Occurrences: 30 (0 since last deploy on <no information>)
Severity: error

URL: <no information>
File: /GEM_ROOT/gems/sidekiq-5.2.7/lib/sidekiq/client.rb

Backtrace:

/GEM_ROOT/gems/sidekiq-5.2.7/lib/sidekiq/client.rb:236:in normalized_hash
/GEM_ROOT/gems/sidekiq-5.2.7/lib/sidekiq/client.rb:224:in normalize_item
/GEM_ROOT/gems/sidekiq-5.2.7/lib/sidekiq/client.rb:70:in push
/GEM_ROOT/gems/sidekiq-5.2.7/lib/sidekiq/client.rb:132:in push

Useful resources section

As a site admin, we should be able to add link and useful resources.

  • Fields will probably be a title and a link coming from CMS

Profile page chronological

@fridaland - As we were discussing, we want the profile page to show the PRs in chronological order. Lets make sure that the PRs are sorted, and that each PR will have the following info:

PR

  • Datetime opened
  • title
  • truncated description
  • All labels
  • Hacktoberfest state (eligible, invalid, spammy)

Setup rails cache

We must setup redis to be the rails cache since we are relying on our cache for Airtable requests to not hit the rate limit.

The Redis config should be extracted from the sidekiq initializer and used to both setup the rails cache and sidekiq.

Fix copy on register page

Change copy to:
"To win a shirt, you must make four pull requests by October 31st. Everyone who participates receives limited-edition Hacktoberfest stickers - regardless if you complete the four pull requests or not."

Hacktoberfest Meetups page

List of meetups

A user will be able to see a list of Hacktoberfest meetups and have the ability to create a new event

  • Create page to list Hacktoberfest meetups
    • Determine if we will have a preview and page or just partial.
  • Ability to create a Hacktoberfest meetup
    • Determine if this will be done with AirTable
  • Store all meetup data

Open Registration

Contest Active

  • Allow site admin to switch site onto "open registration"
    • ENV variable to switch on and off

TO-DO:

  • App is deployed to prod without domain
  • Switch DNS - cloud flare update to proper domain

Meetup Organizers

Meetup Organizers:

As a meetup organizer, I will be able to view helpful resources that will be updated via AirTable

  • Resources

  • Resources for organizers

  • Instructions for organizers

[Airbrake] [Production] undefined method `pullRequests' for nil:NilClass

Airbrake error: #7785
Airbrake project: Hacktoberfest

Error type: NoMethodError
Error message: undefined method `pullRequests' for nil:NilClass
Where: users#update
Occurred at: Sep 25, 2019 16:18:50 UTC
First seen at: Sep 25, 2019 16:18:05 UTC
Occurrences: 3 (0 since last deploy on <no information>)
Severity: error

URL: https://hacktoberfest.digitalocean.com/register
File: /PROJECT_ROOT/app/services/github_pull_request_service.rb

Backtrace:

/PROJECT_ROOT/app/services/github_pull_request_service.rb:38:in pull_requests
/PROJECT_ROOT/app/services/pull_request_service.rb:31:in github_pull_requests
/PROJECT_ROOT/app/services/pull_request_service.rb:15:in all
/PROJECT_ROOT/app/services/pull_request_service.rb:21:in eligible_prs

User transition processing

Each of the following will be done in separate PRs

  • Write TryUserTransitionFromRegistered service with tests #32
  • Write TryUserTransitionFromWaiting service with tests #33
  • Write TryUserTransition service with tests #34
  • Use TryUserTransition service from profile page #41
  • Write rake task to process all users in registered state #35
  • Write rake task for processing all users in waiting state #35
    1. Query to select all users in xyzzy state
  • Setup activejob #39
  • Write UserTransitionJob#39
  • Change rake task to use the job rather than the service #35

Publish FAQs

Publish FAQ's

FAQ's section will be available for pre-launch site.

  • Display FAQ's
    -FAQs content will come from Airtable

Spammy repos

Steps:

  • Use the data from Repository.banned? to mark PRs as spammy and ineligible.
  • Ensure this is conveyed on the profile page

Refactor PullRequest Service

  1. Remove all_by_state and score method
  2. Add mature? method to PullRequest model
  3. Use mature? method in matured_prs method
  4. Add eligible_prs and matured_prs method to be consistent with method names in the User model (minus count)
  5. Update User model to use new methods

Update tests accordingly

Logged-In user profile

As a participant in hacktoberfest, I want to be able to view my progress in near real-time.

TO-DO:

  • How many out of 4 PRs?
  • Install octokit ruby gem (octokit logic in UserScoreboard lib)
  • create a UserScoreboard library
  • call score library from profile action
  • implement GH API calls in UserScoreboard library

Event Kit page fixes

  • Fix spacing between copy & button in “Promote your event”

  • Remove outer padding on “Promote your event” and pad each section (so image background doesn’t have a darker bg border)

  • Consider applying the tilt/popout effect to the sample image in “Promote your event”

Import quality update code

Steps:

  • Import the code for calculating and updating the quality of issues/repos.
  • Setup a periodic job to run this

Maintainers: Resources, instructions + Appreciation

Maintainers resources section

Major Hacktoberfest Contributors should get major appreciation

  • Resources for maintainers (AirTable)

  • Appreciation (TBD) :

  • If you are a maintainer that has been highly active supporting the community of a popular repository, you will be eligible to also receive a maintainer appreciation swag (likely pin).

  • Determine how many people get appreciation swag

  • Determine how we obtain eligible maintainers

Maintainers can apply to showcase their projects at a webinar via AirTable form.

  • CTA for maintainers to showcase/pitch their projects through webinars or
    events

  • Apply to showcase your project

    • Determine if we will use Airtable for form
    • Determine fields for showcase form

Maintainers can apply to showcase at webinar

Maintainers can apply to showcase their projects at a webinar via a form.

  • CTA for maintainers to showcase/pitch their projects through webinars or
    events

  • Apply to showcase your project

    • Determine if we will use Airtable for form
    • Determine fields for showcase form

Make sure missing data doesn't error out page renders

Current Behavior
This morning, new events in Airtable were published without "Event Start Date/Time" values being present.

As you can see from the controller, this will break it:
https://github.com/raise-dev/hacktoberfest/blob/master/app/controllers/pages_controller.rb#L24

Expected Behavior:
Make sure this controller, and all other page controllers and helper methods throughout the repository, set sane defaults for expected data structures or handles missing / malformed data packets more gracefully.

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.