Onboarding - brownfield-team/anacapa-github-linker Wiki

Purpose/Backbground

Original Purpose: link course roster with organization

The original purpose of the anacapa-github-linker was to solve one problem, and one problem only:

  • Getting the students on a course roster enrolled in a GitHub organization efficiently, reliably, and securely.

The intent was to be sure that all the students on the course roster could get enrolled, and only the students on that roster.

At the time, GitHub classroom offered a way to get students into an organization, but it lacked two crucial features:

  • The ability to restrict the ability to enroll only to students on the course roster
  • The ability to connect a GitHub username to a specific course roster student.

The capability above remains the core feature of the product, and 80% of the instructors that use the product only use these features.

BP-Pro: Brownfield Dashboard

As part of the NSF grant Exploring Brownfield Programming Assignments we proposed a "Brownfield Dashboard", or BP-Pro for short. We have used the anacapa-github-linker as a platform on which to prototype and build features of the BP-Pro tool.

To date, we have built features only to support:

  • a limited number of instructors convenience functions (mainly for creating repos at scale)
  • a limited number of researcher facing functions.

We propose to start building features to support students, and some additional features for instructors for assessment (formative and summative)

Formative and Summative Assessment

  • Formative Assessment "informs" students about their learning progress. It is primarily for feedback, rather than for "grades". If it is incorporated into grades at all, it's usually "low stakes", i.e. small percentage of the grade. Purely formative assessment is not for grading at all, but only for feedback.
  • Summative Assessment is a "summary of what was learned". It is typically something happens near the end of a unit, and is "high stakes" in terms of the final course grade.

Dev Environment

In order to do development for this application, you will need a development environment with the following tools.

As I've listed each tool, I've included a link to the main web page for that tool. However, I don't necessarily recommend following the default installation instructions for any of those; instead, we have specific advice for each platform (Mac, Windows, Linux, etc.), so hold off on installing until you get to those instructions.

  • rvm: Ruby Version Manager.
    • This tool helps you maintain version of the Ruby programming language environment for different versions.
    • This helps in case you need to work on different projects that require different versions of Ruby
    • It helps with migrating the code basefrom one version of Ruby to another over time
  • bundler The bundler is a piece of software that helps to manage Ruby gems, which are the dependencies of a Ruby project.
    • bundler is itself a gem, so once you've established a working ruby environment via rvm, you will use the gem command to install bundler, like this:
      gem install bundler
      
  • node: Node
    • The node runtime system is the basis for working with React
    • While node started as a framework for using JavaScript as a backend programming language, the node ecosystem now also incorporates the ability to manage dependencies for frontend code.
  • yarn is a package/build manager for node; it is an alternative to the default npm which is installed along with `node. It needs to be installed separately.
  • postgres: This is SQL-based database management system. We use Postgres, specifically, (as opposed to, for example, sqlite, MySQL, or others) because Postgres is the default "free tier" database on Heroku; this makes it much easier to work with test and QA deployments.

Overall Architecture

Originally, this was a pure Ruby on Rails app.

At a later stage, we began converting it, slowly, to use React for certain portions of the user interface, using the 'React on Rails' gem (in Ruby, a gem is a software module that can be used as a dependency, i.e. a third-party software package incorporated into the build.)

For any given page in the app, before you can make changes to it, you need to determine how the page is rendered:

  • Old style Embedded Ruby template files, i.e. an .erb file.
  • A React component i.e. a .jsx file.

There may also be cases where there is hybrid of the two.

In the next section, we'll describe how you can investigate his for a given page, so you know where to start modifying code.

Does this page come from an .erb or a .jsx?

First lets look at the difference:

Intro to Embedded Ruby: .erb files

In a "classic" pure Ruby on Rails app (i.e. one that doesn't use a separate frontend framework such as React, Angular, etc.), the frontend is provided by .erb files found under the directory app/views.

Here are some examples of the syntax you'll find in an .erb file. Notice a few things:

  • The default language of the file is HTML
  • You can escape into a ruby expression with the syntax <%= expression %>; this will evaluate the ruby expression, and put the result in the rendered page. In this case @course is an instance variable for a course object, and .name is a field (data member) in that object.
<p>
  <strong>Name:</strong>
  <%= @course.name %>
</p>

You can also find ruby executable code inside <% code %> tags, typically for conditionals (if/else) or loops.

Here is an example of a conditional.

   <% if !student.username.nil? and !student.username.empty? %>
     <td><a href="https://github.com/<%= student.username %>"><%= student.username %></td>
   <% else %>
     <td></td>
   <% end %>

Here is an example of a loop; loops are often expressed in a "functional programming" style in Ruby. The first line also shows how to put a comment in Embedded ruby syntax, with <%# comment %>. In this case, the variable flash is a list of messages, each of which has a method or field called name and msg. The |name, msg| syntax pulls those members from each instance of the objects in the list flash, and makes them available to the body of the loop. For each member of the array, we get a <div> element containing a <button> element, and then the respective flash message as content.

<%# Rails flash messages styled for Bootstrap 3.0 %>
<% flash.each do |name, msg| %>
  <% if msg.is_a?(String) %>
    <div class="alert alert-dismissible alert-<%= name.to_s == 'notice' ? 'success' : 'danger' %>">
      <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
      <%= content_tag :div, sanitize(msg), :id => "flash_#{name}" %>
    </div>
  <% end %>
<% end %>

.jsx files

This section is not intended to be a full JSX tutorial; it's meant to just give you enough information to get started and oriented to the codebase. For more on JSX, refer to the React Documentation at https://beta.reactjs.org/learn.

In this code base, .jsx files will be found under the directory path app/javascript/components.

The name comes from the fact that each .jsx file typically defines a component, i.e. a reusable user interface element. For example, if we define a component such as <TeamInfo>, we can use that component anywhere in the web app that we want to provide information about a team, with the result that all of these have a similar appearance, and we only have to maintain the details in one place in the code.

The JSX syntax is:

  • mostly Javascript (or more technically, the dialect ECMAScript 6)
  • allows elements expressed in an XML/HTML syntax to be treated as objects

As an example, the following is legal JSX. Because the value is a single XML/HTML element, it is a legal value for an expression in JSX (this would not permitted in pure JavaScript).

const heading = 
  <h1>
    This is my heading
  </h1>;

Typically, you'll find the HTML near the bottom of the file in a section such as the example below. Note that inside the HTML expressions, we can escape back into JavaScript expressions by using { expression }; the value of expression will be inserted into the HTML at that point.

You will sometimes see {{}}, and this can be confusing. What's typically happening here is that the outer {} are the escape into JavaScript, and the inner {} represent an object. Note that when you see {{}}, if you look inside, you'll see key value pairs separated by commas such as: {{key1: "value1", key2: "value2}}

Also notice that tags that come from pure HTML are in lowercase (e.g. <div>) while tags that represent other React components are in upper case, e.g. <Panel>, <DropdownButton>. Those components can either be third-party components that are imported from various modules, or that can be components that are defined in other files in the codebase.

   return (
            <div>
                {this.renderError()}
                <Panel>
                    <Panel.Heading>
                            <Panel.Title>{this.state.teamName}
                                <div style={{ display: "flex", alignItems: "center", justifyContent: "flex-end", padding: "0px", margin: "0px" }}>
                                    Select Team: <ButtonToolbar>
                                        <DropdownButton title={this.state.teamName} id="dropdown-size-medium">
                                            {teams.map((object, index) => {
                                                return(<MenuItem key={object["name"]} onClick={() => this.onButtonClick(object)}>{object["name"]}</MenuItem>);
                                            })}
                                        </DropdownButton>
                                    </ButtonToolbar>
                                </div>
                            </Panel.Title>
                    </Panel.Heading>
                    <Panel.Body>
                        <CourseOrgTeam
                            org_team_id={org_team_id}
                            course_id={this.props.course_id.toString()}
                        />
                    </Panel.Body>
                </Panel>
            </div>
        );

JSX class style vs. functional style (hooks)

To make things even more confusing: there are two generations of React code:

  • an older object-oriented class-based style
  • a newer functional style (based on a concept called hooks).

Much of the earliest React code in our codebase uses the older object-oriented style. However, we are slowly migrating to the new functional style.

It should be noted it's fine to mix the two styles in the case codebase, but not in the same component.

That is, within each component, you should stick 100% to the class-based style, or 100% to the functional/hooks based style.

The Ruby ecosystem: ruby Gems, Gemfile, Gemfile.lock and bundler

TODO

The Node ecosystem: node modules, yarn, package.json and package-lock.json

TODO

Database setup

TODO

The rails s and rails c commands

TODO

Database migrations

TODO

OAuth Setup

OAuth is a protocol that allows an application to delegate authentication (verifying the identify of a user via username/password) to another organization. Websites that provide an option to "login via Google", "login via Facebook", etc. on a website, are using OAuth.

In our case, we use OAuth to implement "login via GitHub".

This requires us to get a "client id" and "client secret" from GitHub. That is done via in GitHub via "Settings => Developer Settings => OAuth App". The information you need to provide includes the "callback url", which for a localhost deployment is:

http://localhost:3000/users/auth/github/callback

For Heroku deployments, you would substitute https://my-app-name.herokuapp.com in place of http://localhost:3000, keeping the /users/auth/github/callback part.

What you get back is a client id and client secret. Those are then placed into a file called .env. The format of that file is shown in the repo in a file called dotenv.example. The intention is that you copy dotenv.example to .env and then edit the .env file. Never commit a .env file to github and be sure to protect the client secret from being exposed to the public.

Here is the part of the .env file where the client id and client secret go. The name OMNIAUTH comes from the ruby gem (i.e. the software package) that is used to implement OAuth in our application.

OMNIAUTH_PROVIDER_KEY=<your omniauth provider key>
OMNIAUTH_PROVIDER_SECRET=<your omniauth provider secret>

Machine User and Personal Access Token

The web app takes certain actions on behalf of the user; you can think of it as a kind of GitHub "bot" that, for example, can invite users to an organization, create repos, create teams, etc. The GitHub "user" that does all of these things on behalf of the user is called the "machine user".

To bring up an instance on localhost, you need to provide a GitHub username and a "personal access token" created by that GitHub user. Please note that a personal access token, in the hands of a knowledgeable adversary, can be used just like a password to do things on GitHub in your name. So take care to protect the personal access token; treat it with the same care that you would your GitHub password.

You may wonder why we don't just use a password; some key differences from a GitHub password:

  • It's hard to remember if you just glance at it
  • It typically is configured with an automatic expiration date
  • You don't have to keep it in sync with the password you use as a human; you can invalidate it at any time without changing your human password, and if you change your human password, it doesn't invalidate the personal access token(s).
  • It can be limited in scope; rather than allowing access to all GitHub functions, you can limit it to certain ones.

The machine user name and personal access token are put into the .env file in this section:

# Provide a github user, and an access token for that github user
# The scopes should be admin:org,admin:org_hook,repo

MACHINE_USER_NAME=<your machine user's name>
MACHINE_USER_KEY=<your machine user's key>

Deployment

The production deployment is here: https://ucsb-cs-github-linker.herokuapp.com/

Creating a new Course

Before creating a new course you must:

  • Create a GitHub organization
  • Add the "machine user" as an "owner" of that organization
⚠️ **GitHub.com Fallback** ⚠️