Flextensions

Standing Up the Application

This guide walks you through setting up the Flextensions app on your local machine and on your Heroku server and preparing it for development and deployment.

Set Up in Your Local Environment

Clone the Repository

git clone git@github.com:berkeley-cdss/flextensions.git
cd flextensions

Set Up Ruby Environment

(If you are on Windows, please use WSL instead).

Install mise, such as brew install mise or any other ruby language manager.

Either Ruby 3.3 or 3.4 will work. CI runs 3.4, but the deployed Elastic Beanstalk platform runs Ruby 3.3, so the app must remain compatible with both (the Gemfile allows >= 3.3, < 3.5):

mise use ruby@3.4

Install dependencies:

bundle install --without production

Install PostgreSQL

On macOS:

brew install postgresql
# Optionally, if it does not start by default
brew services start postgresql@16
/opt/homebrew/opt/postgresql@16/bin/postgres -D /opt/homebrew/var/postgresql@16

On Linux / WSL:

sudo apt install postgresql
# Create a postgres user.
sudo su-postgres #(to get into postgres shell)
createuser --interactive --pwprompt #(in postgres shell)0
Save DB_USER and DB_PASSWORD fields in the .env file.
#Start postgres if necessary.
pg ctlcluster 12 main start
#Note: if you are using WSL2 on windows, the command to start postares is
sudo seryice posteresal start

Install Overmind

In order to stand up the server you must first install Overmind. Development has been tested with overmind 2.4.0

With Overmind, you can run $make dev or $make

Environment Variables

In the root directory of Flextensions app, run

make env

Rails Database

run rails db:setup

To start the server locally, run rails server . You should be able to land to the login page.

Hypershield

Hypershield is a tool which allows admins to query data, without relevaing sensitive tokens.

You may need to run:

rake hypershield:refresh:dry_run

Background and Scheduled Jobs

Background jobs run on GoodJob, backed by the app’s Postgres database. In production and staging GoodJob runs inside the Puma process (config.good_job.execution_mode = :async), so Elastic Beanstalk does not need a worker tier, a Procfile worker entry, or an EC2 crontab.

Recurring jobs are defined once, in config/application.rb under config.good_job.cron, and executed by GoodJob’s own cron thread wherever config.good_job.enable_cron is true (production and staging). Times use an explicit America/Los_Angeles timezone field, so they do not depend on the server clock, and GoodJob’s unique index on (cron_key, cron_at) means an occurrence is enqueued once even if several processes are running.

Cron key Schedule Job
pending_digests_hourly Top of every hour PendingRequestsNotificationJob('hourly')
pending_digests_daily 4:00 PM PT daily PendingRequestsNotificationJob('daily')
pending_digests_weekly 4:00 PM PT Thursdays PendingRequestsNotificationJob('weekly')

Each run emails the courses whose Pending Request Notifications setting matches that frequency and that currently have pending requests.

Admins can inspect queues, schedules and past runs at /admin/good_job. To send a digest by hand (locally, or to backfill after downtime):

bundle exec rake 'notifications:send_pending_digests[hourly]'

Set GOOD_JOB_ENABLE_CRON=false on an instance to stop it from enqueueing recurring jobs — for example when moving them to a dedicated worker started with bundle exec good_job start --enable-cron.


Deployment (Elastic Beanstalk)

Staging and production run on the Ruby 3.3 on Amazon Linux 2023 platform, built by CodeBuild (buildspec.yml) and deployed by CodePipeline.

There is deliberately no Procfile

The repository intentionally ships no root Procfile. When one is absent, Elastic Beanstalk generates the default for the Ruby platform:

web: bundle exec puma -C /opt/elasticbeanstalk/config/private/pumaconf.rb

That platform-provided pumaconf.rb binds Puma to the Unix socket /var/run/puma/my_app.sock, which is what the platform’s nginx config proxies to (upstream my_app { server unix:///var/run/puma/my_app.sock; }). Note that this is not the generic PORT / TCP-5000 convention used by the Go and Java SE platforms — the Ruby platform does not set a PORT environment variable, so a Procfile line like web: bundle exec rails server -p $PORT starts Rails with an empty --port argument and the web process dies at boot with Thor::MalformattedArgumentError: No value provided for option '--port'.

If you ever do need a custom Procfile, two platform behaviours are worth knowing:

Because GoodJob runs in-process (see Background and Scheduled Jobs), the single platform-managed web process is all this app needs.


Standing Up the Application on Heroku

  1. Setup the following ENV variables in heroku, with the same values in your local .env file.
APP_HOST
CANVAS_CLIENT_ID
CANVAS_URL
# Active Record Encryption Values
# SMTP Email Settings
  1. Pushing branch Iter4 to flextensions heroku
heroku login
git remote add golden https://git.heroku.com/flextensions.git
git push golden main
  1. https://sp25-02-flextensions-4f5b4fbccd7f.herokuapp.com

Testing

Test Commands

Test Type Command
RSpec Tests (no a11y) bundle exec rspec --tag '~a11y'
Cucumber Tests (no a11y) bundle exec cucumber --tags 'not @a11y and not @skip'
All Regular Tests bundle exec rspec --tag '~a11y' && bundle exec cucumber --tags 'not @a11y and not @skip'
Accessibility Tests (RSpec) bundle exec rspec --tag a11y
Accessibility Tests (Cucumber) bundle exec cucumber --tags @a11y
All Tests (including a11y) bundle exec rspec && bundle exec cucumber --tags 'not @skip'
Lint Code (RuboCop) bundle exec rubocop
Auto-fix Lint Issues bundle exec rubocop -A
Validate Swagger API npx @redocly/cli lint app/assets/swagger/swagger.json --extends=minimal

Test Tags

Tag Description
@javascript Tests requiring JS execution in browser (uses Selenium/headless browser), without the tag, it will run in rack, which is exponentially faster to test
@a11y Accessibility tests using axe-core to verify WCAG compliance
@skip Temporarily skipped tests (known failures)
@wip Work In Progress tests still under development

Accessibility (a11y) after-hooks

Accessibility auditing is wired up as an after-hook in both test frameworks, so any test opted in with the a11y/@a11y tag has its final rendered page audited with axe-core automatically – individual tests do not need to call the axe matcher themselves.

Tips

Conventions

  1. Testing convention css selector - <a class="nav-link testid-username" href="#"> Tashrique </a>

Notice the testid-usernameWe will be using this style in class to grab elements from DOM to test.

Please don’t remove any class that starts with testid-

Notes

For how Flextensions reads Canvas assignment due dates and reads/writes assignment overrides (and the gotchas around override_assignment_dates, the 25-date all_dates limit, and /date_details), see Canvas Dates API notes (docs/Canvas_Dates_API.md).

There are now two separate instances of Canvas, each with it’s own triad of prod/test/beta environments:

  1. bcourses.berkeley.edu
  2. ucberkeleysandbox.instructure.com

We recommend developing in this order:

  1. ucberkeleysandbox.instructure.com (no risk) - this is the one for which this repo currently has oauth2 keys (secrets)
  2. bcourses.test.instructure.com (no risk of impacting courses, but contains real data)
  3. bcourses.berkeley.edu