Skip to content

Commit

Permalink
Fixing deployment and last seen
Browse files Browse the repository at this point in the history
  • Loading branch information
devinmatte committed Jun 6, 2024
1 parent 48362a7 commit 1c3a90c
Show file tree
Hide file tree
Showing 13 changed files with 52 additions and 62 deletions.
4 changes: 0 additions & 4 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ jobs:
aws_access_key_id = ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_access_key = ${{ secrets.AWS_SECRET_ACCESS_KEY }}
EOF
cat >> ./devops/ec2-secrets.py << EOF
MBTA_V3_API_KEY = '${{ secrets.MBTA_V3_API_KEY }}'
LAST_SEEN_UPDATE = True
EOF
- name: Write SSH key
run: |
mkdir ~/.ssh
Expand Down
20 changes: 6 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ Dependencies:
Run:
- `$ npm install`
- `$ npm start`
- visit [http://localhost:5000/](http://localhost:5000/)
- visit [http://localhost:5173/](http://localhost:5173/)

To use an API key, put it in `server/secrets.py` or as an environment variable `MBTA_V3_API_KEY`
To use an API key, put it as an environment variable `MBTA_V3_API_KEY`

### Linting
To lint frontend and backend code, run `$ npm run lint` in the root directory
Expand All @@ -27,27 +27,19 @@ To lint just frontend code, run `$ npm run lint-frontend`

To lint just backend code, run `$ npm run lint-backend`

## Server Deployment
Additional requirements:
- nginx

Nginx serves as a reverse-proxy for the app running on localhost:5001.
The Flask app is run under gunicorn and controlled by systemd, which will restart after failure or reboot automatically.

## AWS Deployment
1. Make sure AWS CLI is set up and working — i.e. `aws cloudformation describe-stacks | wc -l` should work
2. Make sure these environment variables are set up in your shell (ask a Labs member for values if needed):
- `TM_NTT_CERT_ARN` (for production)
- `TM_LABS_WILDCARD_CERT_ARN` (for beta)
- `TM_LABS_WILDCARD_CERT_ARN` (for beta & production)
3. A key named `transitmatters-ntt` needs to be available in your AWS account and copied to `~/.ssh/transitmatters-ntt.pem`.
4. You will also need to create an `ec2-secrets.py` file in the `devops/` directory.
5. Run `cd devops && ./deploy.sh` (add `-p` for production) to deploy.
6. You're all set! Visit:
4. Run `./deploy.sh` (add `-p` for production) to deploy.
5. You're all set! Visit:
- https://ntt-beta.labs.transitmatters.org for beta
- https://traintracker.transitmatters.org for production

## Other Deployments
This project generally fits the "Flask app" mold. Contact us if you need help: labs@transitmatters.org
This project generally fits the "Chalice app" mold. Contact us if you need help: labs@transitmatters.org

## Support TransitMatters
If you've found this app helpful or interesting, please consider [donating](https://transitmatters.org/donate) to TransitMatters to help support our mission to provide data-driven advocacy for a more reliable, sustainable, and equitable transit system in Metropolitan Boston.
2 changes: 1 addition & 1 deletion deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ GIT_ABR_VERSION=`git describe --tags --abbrev=0`
echo "Deploying version $GIT_VERSION | $GIT_SHA"

# Adding some datadog tags to get better data
DD_TAGS="git.commit.sha:$GIT_SHA,git.repository_url:github.com/transitmatters/parking-explorer"
DD_TAGS="git.commit.sha:$GIT_SHA,git.repository_url:github.com/transitmatters/new-train-tracker"

npm run build

Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@
"@tanstack/react-query": "^5.40.1",
"bezier-js": "^2.5.1",
"classnames": "^2.5.1",
"dayjs": "^1.11.11",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-favicon": "2.0.5",
"react-loading-indicators": "^0.2.3",
"react-router-dom": "^6.23.1",
"react-spring": "^8.0.27",
"seamless-scroll-polyfill": "^2.3.4",
"timeago.js": "^4.0.2"
"seamless-scroll-polyfill": "^2.3.4"
},
"devDependencies": {
"@types/node": "^20.14.2",
Expand Down
10 changes: 10 additions & 0 deletions server/.chalice/policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
],
"Effect": "Allow",
"Resource": "arn:*:logs:*:*:*"
},
{
"Action": [
"s3:*"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::traintracker.transitmatters.org",
"arn:aws:s3:::traintracker.transitmatters.org/*"
]
}
]
}
2 changes: 1 addition & 1 deletion server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def vehicles(trip_id, stop_id):

@app.schedule(Cron("0/10", "0-6,9-23", "*", "*", "?", "*"))
def update_last_seen(event):
asyncio.run(last_seen.update_recent_sightings())
last_seen.update_recent_sightings()


@app.route("/healthcheck", cors=cors_config)
Expand Down
9 changes: 0 additions & 9 deletions server/chalicelib/healthcheck.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import json
import os
import stat
import time
from chalice import Response
import chalicelib.secrets as secrets
from chalicelib.last_seen import JSON_PATH as LAST_SEEN_JSON_PATH


def file_age_s(pathname):
return time.time() - os.stat(pathname)[stat.ST_MTIME]


def run():
checks = [
lambda: len(secrets.MBTA_V3_API_KEY) > 0,
lambda: file_age_s(LAST_SEEN_JSON_PATH) < 1800, # allow up to 30 minutes of outdated last_seen.json
]

for i in range(0, len(checks)):
Expand Down
9 changes: 5 additions & 4 deletions server/chalicelib/last_seen.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import datetime
import json
from zoneinfo import ZoneInfo

import chalicelib.s3 as s3
import chalicelib.mbta_api as mbta_api
Expand All @@ -10,17 +11,18 @@

JSON_PATH = "last_seen.json"
ROUTES = DEFAULT_ROUTE_IDS
EASTERN_TIME = ZoneInfo("US/Eastern")


async def update_recent_sightings():
def update_recent_sightings():
try:
last_seen_times = json.loads(s3.download(JSON_PATH, "utf8", compressed=False))
except Exception as e:
print("Couldn't read last seen times from s3: ", e)
last_seen_times = {}
try:
print("Updating recent sightings...")
now = datetime.datetime.utcnow()
now = datetime.datetime.now(EASTERN_TIME)

all_vehicles = asyncio.run(mbta_api.vehicle_data_for_routes(ROUTES))
new_vehicles = filter_new(all_vehicles)
Expand All @@ -29,8 +31,7 @@ async def update_recent_sightings():
line = get_line_for_route(vehicle["route"])
last_seen_times[line] = {
"car": vehicle["label"],
# Python isoformat() doesn't include TZ, but we know this is UTC because we used utcnow() above
"time": now.isoformat()[:-3] + "Z",
"time": now.isoformat(),
}
s3.upload(JSON_PATH, json.dumps(last_seen_times), compress=False)
except Exception as e:
Expand Down
1 change: 0 additions & 1 deletion server/chalicelib/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@


def download(key, encoding="utf8", compressed=True):
print(BUCKET)
obj = s3.get_object(Bucket=BUCKET, Key=key)
s3_data = obj["Body"].read()
if not compressed:
Expand Down
17 changes: 0 additions & 17 deletions server/chalicelib/secrets.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,3 @@
import os


MBTA_V3_API_KEY = os.environ.get("MBTA_V3_API_KEY", "")
# False by default, because debug Flask spawns two processes, and two last seen updaters will trample each other!
LAST_SEEN_UPDATE = False
"""
If you put your api key here, you may want to run
`git update-index --assume-unchanged server/secrets.py`
so that it doesn't pollute your `git status`
To make that more convenient, you may want to add these aliases to ~/.gitconfig
[alias]
hide = update-index --assume-unchanged
unhide = update-index --no-assume-unchanged
hidden = !git ls-files -v | grep "^[[:lower:]]"
Then run `git hide server/secrets.py`
"""
15 changes: 15 additions & 0 deletions server/cloudformation.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,21 @@
}
}
},
"UpdateLastSeen": {
"Type": "AWS::Serverless::Function",
"Properties": {
"Description": "Update the last seen time for new trains on a schedule",
"Environment": {
"Variables": {
"MBTA_V3_API_KEY": { "Ref": "MbtaV3ApiKey" },
"DD_API_KEY": { "Ref": "DDApiKey" },
"DD_VERSION": { "Ref": "GitVersion" },
"DD_TAGS": { "Ref": "DDTags" }
}
}
}

},
"FrontendBucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
Expand Down
7 changes: 5 additions & 2 deletions src/components/Line.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useMemo, useState, useLayoutEffect, useEffect } from 'react';
import classNames from 'classnames';
import * as timeago from 'timeago.js';

import { prerenderLine } from '../prerender';
import { renderTextTrainlabel } from '../labels';
Expand All @@ -10,6 +9,8 @@ import { PopoverContainerContext, getTrainRoutePairsForLine, setCssVariable } fr
import { Line as TLine, Pair, StationPositions, VehiclesAge } from '../types';
import { MBTAApi } from '../hooks/useMbtaApi';
import { useLastSightingByLine } from '../hooks/useLastSighting';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';

const AGE_WORD_MAP = new Map<VehiclesAge, string>([
['new_vehicles', ' new '],
Expand All @@ -23,6 +24,8 @@ interface LineProps {
age: VehiclesAge;
}

dayjs.extend(relativeTime);

const abbreviateStationName = (station: string) =>
station
.replace('Boston College', 'B.C.')
Expand Down Expand Up @@ -58,7 +61,7 @@ const EmptyNoticeForLine: React.FC<{ line: string; age: VehiclesAge }> = ({ line
// What to show when new is selected
if (sightingForLine) {
const { car, time } = sightingForLine;
const ago = timeago.format(time);
const ago = dayjs(time).fromNow();
return <>{`A new ${line} Line train (#${car}) was last seen ${ago}.`}</>;
}
return <>{`No new trains on the ${line} Line right now.`}</>;
Expand Down

0 comments on commit 1c3a90c

Please sign in to comment.