GitHub - openaustralia/righttoknow: Theme for, and issues specific to, Right To Know. · GitHub
Skip to content

About

Theme for, and issues specific to, Right To Know.

Topics

Resources

Contributing

Stars

30 stars

Watchers

5 watching

Forks

 
 

Latest commit

 

History

1,856 Commits

Folders and files

Repository files navigation

Right To Know

Right To Know lets you make and browse Freedom of Information (FOI) requests in Australia. It is powered by the open source FOI request platform Alaveteli.

This repository contains the theme package for Alaveteli for the Australian deployment. If you find a problem with Right to Know, please report it to this repository's issue tracker.

Development

At present, we use a fork of Alaveteli which contains minor changes to the core to support us. Our plan is to transition to using upstream as soon as possible.

To find out which version of Alaveteli a site is currently running, ask the site rather than this README, which would only go stale:

If there is a fix or enhancement that is not specific to Right to Know/Australia changes should be submitted to the upstream Alaveteli repository via a pull request. In the vast majority of cases we will not deploy a fix until it's been accepted upstream. This ensures we're all using the same code as much as possible.

The one exception for now is announcement styles and rich text, which this theme carries until upstream accepts pull request 9644. It adds two migrations, which run with the host's (rake db:migrate or cap deploy:migrate). See lib/announcement_patches.rb for what to delete when upstream releases it.

However if you'd like to adjust the look and feel of Right To Know, or to update copy like that found on the help pages, this is the place to make those changes.

Development environment

This repository is an Alaveteli theme — it runs inside our fork of Alaveteli. You don't need to check anything else out, though: the development environment below clones Alaveteli for you (the staging branch by default) and wires this theme into it, along with a working development configuration.

The stack matches Alaveteli's own Docker environment: Rails app, Sidekiq, PostgreSQL, Redis, and MailHog to catch outgoing email (at http://localhost:1080).

Option 1: Dev Container (recommended)

Works with VS Code (needs Docker installed) or GitHub Codespaces (needs nothing installed at all).

  1. Open this repository in VS Code and choose Reopen in Container when prompted (or create a Codespace on GitHub).

  2. Wait for setup to finish — the first run clones Alaveteli, installs gems, and migrates and seeds the databases, so it takes a while.

  3. Start the server from the integrated terminal:

    cd /alaveteli && bin/rails server -b 0.0.0.0
  4. Open http://localhost:3000. Log in as the development admin with the username and password from config/general-righttoknow.yml.example (adminxxxx / passwordx).

Inside the container, Alaveteli lives at /alaveteli and this theme is mounted at /alaveteli-themes/righttoknow (your workspace folder). Theme changes take effect on reload; changes to Ruby files under lib/ need a server restart.

Option 2: Docker + Make

The same stack without VS Code — just Docker and make:

make setup    # first run: build, clone Alaveteli, migrate, seed (takes a while)
make server   # start the site at http://localhost:3000

Other useful targets (make help lists them all):

make console  # Rails console
make test     # run this theme's specs
make seed     # load realistic AU test data (see "Seeding test data")
make shell    # shell in the app container
make reset    # destroy containers and data, set up from scratch

To develop against a different Alaveteli branch or fork, set ALAVETELI_BRANCH / ALAVETELI_REPO before the first make setup. If ports 3000 or 1080 are already in use on your machine, set RAILS_PORT / MAILHOG_PORT.

Option 3: Manual setup

If you can't use containers, follow the Alaveteli manual installation docs with our Alaveteli fork, using the layout Alaveteli's theme tooling expects:

  1. Check out the fork, and create a sibling directory called alaveteli-themes with this repository inside it (alaveteli/ and alaveteli-themes/righttoknow/ side by side).
  2. Copy this repository's config/general-righttoknow.yml.example to alaveteli/config/general-righttoknow.yml.
  3. From the alaveteli directory run bundle exec script/switch-theme.rb righttoknow.

Seeding test data

script/seed_test_data.rb fills a development or test environment with a realistic subset of production data so authority listings, jurisdiction logic and request states behave like the real site.

It creates:

  • A handful of real authorities per jurisdiction tag, taken from production's public all-authorities.csv export (public information only — name, tags, URL slug — no request PII). Every seeded authority is given a dummy @example.com request email so the environment can never contact a real authority.
  • Dummy requests per authority spread across a range of statuses, with a subset of authorities carrying 3+ requester_only (prominence) requests.
  • A browse-by-category taxonomy synthesised from the jurisdiction tags on the imported authorities. This is not a copy of production's own category structure — production does not publish its category definitions.

The script refuses to run in production.

Run it against the Alaveteli app (not this theme repo) with rails runner. In the development environment above it's one command:

make seed

or manually (from the alaveteli directory):

bundle exec rails runner \
  ../alaveteli-themes/righttoknow/script/seed_test_data.rb

Authorities and the browse-by-category page work immediately. Search and the request listings are Xapian-backed and will not show seeded data until the index is updated — make seed does this for you; otherwise either re-run with SEED_REBUILD_INDEX=1, or run:

bundle exec rake xapian:destroy_and_rebuild_index \
  models="PublicBody User InfoRequestEvent"

Optional environment variables:

Variable Purpose
SEED_CSV_URL Override the production authorities CSV URL.
SEED_CSV_PATH Read authorities from a local CSV instead of fetching (handy offline; expects the all-authorities.csv format).
SEED_BODIES_PER_TAG Number of authorities per jurisdiction tag (default 5).
SEED_REBUILD_INDEX Set to 1 to update the Xapian index at the end so seeded data shows up in search and request listings.

The script is idempotent: re-running it reuses existing authorities and categories and won't stack additional seeded requests onto authorities that already have them.

Pro subscriptions

Restricting a coupon to a billing interval

Stripe coupons can only be limited to a product, not to an individual price. Because the Pro monthly and annual plans are two prices under one product, Stripe's own applies_to restriction cannot stop a coupon from discounting both. Some coupons are meant for a single plan (for example, a monthly-only referral coupon).

The theme enforces a per-interval restriction itself, driven by coupon metadata so the rule lives with the coupon in Stripe (no code change or deploy needed to add or adjust restricted coupons):

  • Add a metadata key interval to the coupon in the Stripe dashboard, set to the billing interval the coupon is allowed on: day, week, month or year. A monthly-only coupon takes interval = month.
  • A coupon without an interval metadata key is unrestricted and applies to any plan, exactly as before.

With interval set, lib/controller_patches.rb blocks the coupon at checkout on a non-matching plan (before any subscription is created) and the live plan-page price preview refuses it too, so a discount is never advertised that checkout would then reject.

For extra defence-in-depth you can also set the coupon's native applies_to products to the Pro product; this stops it discounting any other product, though it still can't separate monthly from annual.

Promotion codes

The "Do you have a coupon code?" field on the plan page accepts a Stripe promotion code as well as a coupon id. A promotion code is a customer-facing string pointing at a coupon, and it carries restrictions a coupon cannot express: a redemption cap for that code alone, first-time subscribers only, and an expiry independent of the coupon's. Many codes can share one coupon, so a campaign can be tracked and retired without touching the discount itself.

To add one, open the coupon in the Stripe dashboard and create a promotion code against it.

Two things differ from coupons:

  • Promotion codes are not namespaced. Coupon ids are prefixed with STRIPE_NAMESPACE (a typed FOO looks up the coupon RTK-FOO), but a promotion code is matched exactly as typed. Create the code as the string you want people to type.
  • Coupons win a collision. A typed code is looked up as a coupon first, then as a promotion code, so a coupon RTK-FOO takes precedence over a promotion code FOO. Avoid using the same string for both.

Only active promotion codes resolve. Stripe deactivates a code when it expires or hits its redemption cap, and a deactivated code reads as invalid rather than expired.

Who enforces what:

Restriction Enforced by
max_redemptions, expires_at, per-customer Stripe, on redemption
first_time_transaction Stripe, plus a check here before any charge
minimum_amount Stripe, plus a check here before any charge
interval metadata (see above) This theme

A minimum_amount is compared against the plan price before tax and before the discount, so a $10 monthly plan meets a minimum of 1000 but not 1050. A minimum set only in another currency is treated as unmeetable.

The interval and humanized_terms metadata keys are read from the underlying coupon. You can override either on the promotion code's own metadata, so one coupon can back several codes that describe themselves differently.

Contributing

If you want to modify the customised look and feel of Right To Know then you should edit this repository however if it's something more general you probably want to edit the upstream core Alaveteli software.

To contribute an enhancement or a fix to this theme:

  • Fork the project on GitHub.
  • Make a topic branch from the staging branch.
  • Make your changes and test.
  • Commit the changes without making changes to any files that aren't related to your enhancement or fix.
  • Send a pull request against the staging branch.

Deployment

The application is deployed using Capistrano 3. Deployment is run from this repository against the alaveteli codebase.

Releases to production happen as a staging→production release pull request. As part of each release PR, add a section to CHANGELOG.md covering everything merged to staging since the previous release.

Prerequisites

Capistrano looks up the EC2 deploy targets dynamically by their Application and Stage tags and tunnels SSH through AWS SSM Session Manager rather than connecting to a public hostname, so you need:

  • The AWS CLI and the Session Manager plugin installed
  • An AWS profile named oaf with permission to describe EC2 instances and start SSM sessions. config/deploy.rb sets AWS_PROFILE to oaf unless you have already set it, so instance lookup and the SSM tunnel always use the same account; set AWS_PROFILE yourself if your profile is named something else
  • An SSH key for the deploy user (SSH still runs as normal inside the SSM tunnel). If your key isn't picked up by default, add a Host i-* entry with IdentityFile to ~/.ssh/config
  • Gems installed: bundle install --with deployment
  • The server must have shared/rbenv-version, shared/general.yml, and all other shared files in place (managed by the infrastructure repo)

To check which instances a stage will deploy to:

bundle exec cap staging aws:ec2:instances

Deploy commands

Deploy to staging:

bundle exec cap staging deploy

Deploy to production:

bundle exec cap production deploy

Run database migrations only (maintenance page shown automatically):

bundle exec cap staging deploy:migrate
bundle exec cap production deploy:migrate

Restart the application without deploying:

bundle exec cap staging deploy:restart
bundle exec cap production deploy:restart

Rebuild the Xapian search index:

bundle exec cap staging xapian:destroy_and_rebuild_index
bundle exec cap production xapian:destroy_and_rebuild_index

Account housekeeping

One-off jobs that act on people's accounts. Each is dry unless you pass DRYRUN=0, and each prints account ids and dates rather than email addresses, so the output is safe to paste into an issue as a record of what ran.

Destroy never-confirmed accounts that have no content and are over two years old (#1096):

# List what would go, and check the count against production before acting
bundle exec cap production accounts:destroy_never_confirmed

# Start small, check Sentry, then run the rest
bundle exec cap production accounts:destroy_never_confirmed DRYRUN=0 LIMIT=10
bundle exec cap production accounts:destroy_never_confirmed DRYRUN=0

These accounts never confirmed their email address, so they cannot be warned first and are handled separately from the dormant-account notice. What "never-confirmed" and "dormant" mean, and why the order matters, is in AGENTS.md under "Key domain knowledge" and in ADR-0003.

Tell the remaining dormant accounts they will be removed unless they sign in (#1095):

# List who would be notified, and the removal date the email would quote
bundle exec cap production accounts:send_dormant_notices

# Send one tranche, then watch the bounce count before sending the next
bundle exec cap production accounts:send_dormant_notices DRYRUN=0 LIMIT=20
bundle exec cap production accounts:send_dormant_notices DRYRUN=0

Do not send these live until bounce recording works (#1094). Without it nothing suppresses repeat sends to addresses that have permanently failed, and there is no way to judge the send. Right to Know's sending reputation is what gets FOI requests delivered to authorities.

Each notice quotes a removal date of the run date plus 60 days, and each account notified is tagged dormant_account_notice:<date> so a later run never mails the same person twice. The host's users:destroy_unused cron must not be enabled before the latest date any tranche was told. The run prints that date so it can be recorded on the issue.

Either job can be run from a shell on the server, with the same environment variables:

cd /srv/www/production/current
lib/themes/righttoknow/script/destroy-never-confirmed-accounts
lib/themes/righttoknow/script/send-dormant-account-notices

First-time server setup

After the infrastructure repo has provisioned the host, the shared path needs the config files in place before the first deploy. The deploy itself auto-creates any missing shared directories (cache, logs, storage, xapian indexes, vendored bundle, etc.) on each run via deploy:check_shared.

One-time bootstrap per server:

  1. Ensure <deploy_to>/shared/ exists (ssh deploy@<host> 'mkdir -p <deploy_to>/shared').
  2. Place the shared config files at the top level of shared/ (basename only, not under shared/config/). The expected files — drawn from SHARED_FILES in alaveteli/config/general-righttoknow.yml — are:
    • general.yml
    • database.yml
    • sidekiq.yml
    • storage.yml
    • user_spam_scorer.yml
    • rails_env.rb
    • newrelic.yml
    • foi-live-creation.png
    • foi-user-use.png
    • rbenv-version
  3. Run the first deploy: bundle exec cap <stage> deploy.

deploy:check_shared runs at the start of every deploy and fails fast if any required file is missing.

whatismyip trust-boundary check

GET /whatismyip reports the IP Rails sees for the request, to aid in checking cloudflare proxying with a one-line curl rather than a real sign-in or a log dig. It returns the IP, or the IP plus FAIL if that IP falls within Cloudflare's own published ranges - a sign the trust boundary isn't rewriting it to the real visitor IP. If Cloudflare's published ranges can't be fetched or look truncated, it returns the IP plus UNABLE TO CHECK rather than guessing.

It should report the same value as https://whatismyip.akamai.com/

Off by default; set PROVIDE_WHATISMYIP: true in general.yml to turn it on.

curl https://www.righttoknow.org.au/whatismyip
curl https://www-staging.righttoknow.org.au/whatismyip

Authorities

Adding new authorities

Should an agency be added?

Not everything that the government considers to be a distinct agency is an entity that people actually want to make requests to. It is very much a subjective call, but always try and make the site most useful to the people who use it. If there is a lot of unnecessary sub-division authorities for example, requests will be more difficult to find in the site. Try to avoid this. For example, the IT division or the head office of an agency are probably just part of the agency itself for the purpose of information requests.

If a sub-department agency has a distinct office, it’s own website and information request email address, and does stuff that people would want to make requests about, it should probably be a distinct authority in Right To Know.

If in doubt, ask the team.

Format of state name in authority names

For state authorities often the name of the state appears in the name of the authority.

On the Art Gallery of NSW website they refer to their own name in three different forms: Art Gallery NSW, Art Gallery of New South Wales, and Art Gallery of NSW. We're currently using Art Gallery of NSW because it is commonly used, succinct, and searchable. Therefore if the name of the state is in the name of the authority, use the form they use or the form in most common use.

When you need to choose, use real acronyms (NSW, ACT, WA, SA, NT) but not contractions (use Victoria, Tasmania, and Queensland, not VIC, TAS, or QLD).

Request email

When collecting the email address that requests to the authority are sent to, we need to find the best address to deal with them directly. A specific address for the authority isn’t always available, especially when they exist within a bigger department. This is the order of preference for the authority’s request email:

  1. Specific FOI address for sub-department agency (e.g. <foi@special_agency.gov.au>)
  2. Specific address for sub-department agency at department (e.g. special_agency@department.gov.au)
  3. Specific FOI address for department (e.g. foi@department.gov.au)
  4. Generic address for department (e.g. info@department.gov.au)
  5. Address for a specific person at the agency (e.g. jane.zhang@dept.gov.au)

A person’s address is the absolute last resort.

Public notes

You can add public notes about the authority. The notes are displayed on the authority’s page, as an excerpt in lists of authorities, and as a highlighted notice when someone makes a request to the authority. They are covered in the site search.

The note could include important information about requesting from this department, key terms people might use when searching for the agency, and a basic description of the authority. For example:

HomeStart was created by the South Australian Government in 1989, as a response to high interest rates and a lack of affordable home loan finance options. HomeStart was established as a statutory corporation under the Housing and Urban Development (Administrative Arrangements) Act 1995 and reports to the Minister for Housing and Urban Development.

Short name

You can add abbreviated version of the authority’s name as it’s short name. These are really useful because the common name for an agency might not be it’s full formal name. For example, people commonly search “ABS” when looking for the “Australian Bureau of Statistics”.

It’s very important to only add acronyms or abbreviations that people really do use. These are displayed on authority pages and lists of authorities, and having lots of irrelevant short names adds unnecessary noise to the page.

Removing an authority

Authorities that are defunct aren’t actually removed from Right To Know. Add the defunct tag to the authority and it will no longer be available for requests. People can still find requests to the authority in search results and see all the requests that were made to them while they were active.

Jurisdictions

A unique aspect of Right To Know compared to other Alaveteli installations is that we're in the process of supporting 9 different jurisdictions - Federal and all the states and territories.

We have customisations in this theme to adjust the length of time authorities have to respond to a request and the law names depending on what jurisdiction an authority is covered by. These customisations rely on the use of Alaveteli's tags to work out what jurisdiction an authority is covered by.

The table below show what tag you need to use for each jurisdiction. Don't forget to also add the appropriate category tag, described in the section below, for the authority you're adding.

Jurisdiction Tag
Federal federal
ACT ACT
NSW NSW
NT NT
QLD QLD
SA SA
TAS TAS
VIC VIC
WA WA

Categories

Public authority categories are configured in the Alaveteli admin interface. This how we want Right To Know's categories organised:

Federal

Title Description Tag
Agriculture part of the Agriculture portfolio agriculture
Attorney-General part of the Attorney-General portfolio attorney_general
Communications part of the Communications portfolio communications
Defence part of the Defence portfolio defence
Education and Training part of the Education and Training portfolio education_and_training
Employment part of the Employment portfolio employment
Environment part of the Environment portfolio environment
Finance part of the Finance portfolio finance
Foreign Affairs and Trade part of the Foreign Affairs and Trade portfolio foreign_affairs_and_trade
Health part of the Health portfolio health
Immigration & Border Protection part of the Immigration & Border Protection portfolio immigration_and_border_protection
Industry and Science part of the Industry and Science portfolio industry_and_science
Infrastructure and Regional Development part of the Infrastructure and Regional Development portfolio infrastructure_and_regional_development
Prime Minister part of the Prime Minister portfolio prime_minister
Social Services part of the Social Services portfolio social_services
Treasury part of the Treasury portfolio treasury
Veterans' Affairs part of the Veterans' Affairs portfolio veterans_affairs
All Federal authorities a Federal authority federal

State and Territory

Title Description Tag
ACT an ACT authority ACT_state
New South Wales a NSW authority NSW_state
Northern Territory a Northern Territory authority NT_state
Queensland a Queensland authority QLD_state
South Australia a South Australian authority SA_state
Tasmania a Tasmanian authority TAS_state
Victoria a Victorian authority VIC_state
Western Australia a Western Australian authority WA_state

Local

Title Description Tag
New South Wales a NSW Council NSW_council
Northern Territory a Northern Territory Council NT_council
Queensland a Queensland Council QLD_council
South Australia a South Australian Council SA_council
Tasmania a Tasmanian Council TAS_council
Victoria a Victorian Council VIC_council
Western Australia a Western Australian Council WA_council

Adding more jurisdictions

When adding authorities for jurisdictions we don't yet cover we need to:

This project is tested with BrowserStack

About

Theme for, and issues specific to, Right To Know.

Topics

Resources

Contributing

Stars

30 stars

Watchers

5 watching

Forks

Sponsor this project

Used by

Contributors

Languages