From 154cd87055a96a0025785a708c5ac7f95dc500d7 Mon Sep 17 00:00:00 2001 From: Devin Matte Date: Tue, 19 Mar 2024 18:00:04 -0400 Subject: [PATCH] Adding detailed README and cleanup (#12) --- .flake8 | 5 +++ .github/workflows/deploy.yml | 6 ++-- .github/workflows/lint.yml | 60 +++++++++++++++++++++++++++++++++ .gitignore | 2 -- .nvmrc | 1 + .vscode/extensions.json | 7 ++++ .vscode/settings.json | 16 +++++++++ README.md | 65 ++++++++++++++++++++++++------------ package-lock.json | 19 ++++++----- package.json | 11 +++--- poetry.lock | 32 +++++++++--------- server/app.py | 24 +++++++------ 12 files changed, 182 insertions(+), 66 deletions(-) create mode 100644 .flake8 create mode 100644 .github/workflows/lint.yml create mode 100644 .nvmrc create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..a43020e --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = E501,E731,W503 +exclude = + .chalice + .venv \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b3d8700..da1008c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -22,13 +22,13 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Node ${{ matrix.node-version }}.x - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Set up CI Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.npm diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..6dd5ea5 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,60 @@ +name: lint + +on: + pull_request: + push: + branches: + - main + +jobs: + frontend: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: ['20'] + python-version: ['3.11'] + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Set up Node ${{ matrix.node-version }}.x + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - name: Check if package-lock.json is up to date + run: | + npx --yes package-lock-utd@1.1.0 + - name: Lint frontend code with ESLint + run: | + curl -sSL https://install.python-poetry.org | python3 - + npm ci + npm run lint-frontend + + backend: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: ['20'] + python-version: ['3.11'] + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Set up Node ${{ matrix.node-version }}.x + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - name: Check if package-lock.json is up to date + run: | + npx --yes package-lock-utd@1.1.0 + - name: Lint backend code with Flake8 + run: | + curl -sSL https://install.python-poetry.org | python3 - + npm ci + npm run lint-backend diff --git a/.gitignore b/.gitignore index 0c09d3b..0f316c5 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,6 @@ server/cfn/* server/.chalice/deployments/* # Editor directories and files -.vscode/* -!.vscode/extensions.json .idea .DS_Store *.suo diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2edeafb --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..ad84f89 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "bradlc.vscode-tailwindcss" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..33f77f6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "eslint.enable": true, + "eslint.format.enable": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], + "typescript.enablePromptUseWorkspaceTsdk": true, + "editor.formatOnSave": true, + "python.analysis.typeCheckingMode": "off", + "python.formatting.provider": "black", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } +} diff --git a/README.md b/README.md index 0d6babe..e7a3aa2 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,51 @@ -# React + TypeScript + Vite +# TransitMatters Shutdown Tracker -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +This is the repository for the TransitMatters Shutdown Tracker. Client code is written in Typescript with React and vite, and the minimal backend is written in Python with Chalice. -Currently, two official plugins are available: +## Requirements to develop locally -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +- node 20.x and npm 10+ required + - With `nvm` installed, use `nvm install && nvm use` + - verify with `node -v` +- Python 3.11 with recent poetry (1.6.0 or later) + - Verify with `python --version && poetry --version` + - `poetry self update` to update poetry + - If using `pyenv`, `pyenv install 3.11.7` -## Expanding the ESLint configuration +## Development Instructions -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: +1. In the root directory, run `npm install` to install all frontend and backend dependencies +2. Run `npm start` to start both the Vite development server and the Python backend at the same time. + 1. `npm run dev` to just run the Vite development server + 2. `npm run start-python` to just run the Chalice backend server +3. Navigate to [http://localhost:3000](http://localhost:3000) (or the url provided after running `npm start`) -- Configure the top-level `parserOptions` property like this: +## Deployment Instructions -```js -export default { - // other rules... - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: ['./tsconfig.json', './tsconfig.node.json'], - tsconfigRootDir: __dirname, - }, -} -``` +1. Configure AWS CLI 1.x or 2.x with your AWS access key ID and secret under the profile name `transitmatters`. +2. Configure shell environment variables for AWS ACM domain certificates. + - `TM_LABS_WILDCARD_CERT_ARN` + - (You may also need to set `AWS_DEFAULT_REGION` in your shell to `us-east-1`. Maybe not! We're not sure.) + - `DD_API_KEY` (Datadog API key, needed to deploy to TransitMatters stack in prod) +3. Execute `./deploy.sh`. -- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` -- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list +#### Additional notes: + +- If you're on a platform with a non-GNU `sed`, deploy.sh might fail. On macOS, this is fixed by `brew install gnu-sed` and adding it to your PATH. +- If you get an unexplained error, check the CloudFormation stack status in AWS Console. Good luck! + +### Linting + +To lint frontend and backend code, run `npm run lint` in the root directory + +To lint just frontend code, run `npm run lint-frontend` + +To lint just backend code, run `npm run lint-backend` + +#### VSCode + +If you're using VSCode, `.vscode` contains a based default workspace setup. It also includes recommended extentions that will improve the dev experience. This config is meant to be as small as possible to enable an "out of the box" easy experience for handling eslint. + +## 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. diff --git a/package-lock.json b/package-lock.json index 42aaf09..28edd5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "tm-shutdowns", "version": "0.0.1", + "hasInstallScript": true, "dependencies": { "@headlessui/react": "^1.7.18", "@heroicons/react": "^2.1.1", @@ -17,7 +18,7 @@ "chartjs-plugin-annotation": "^3.0.1", "chartjs-plugin-watermark": "^2.0.2", "classnames": "^2.5.1", - "concurrently": "^8.2.0", + "concurrently": "^8.2.2", "date-fns": "^3.3.1", "dayjs": "^1.11.10", "react": "^18.2.0", @@ -27,7 +28,7 @@ "react-router-dom": "^6.22.0", "react-scroll": "^1.9.0", "react-toggle-dark-mode": "^1.1.1", - "zustand": "^4.5.0" + "zustand": "^4.5.1" }, "devDependencies": { "@types/bezier-js": "^4.1.3", @@ -53,7 +54,7 @@ }, "engines": { "node": ">=20.10.0", - "npm": ">=9.0.0" + "npm": ">=10.0.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -8187,9 +8188,9 @@ } }, "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", "peer": true }, "node_modules/is-array-buffer": { @@ -13897,9 +13898,9 @@ "peer": true }, "node_modules/zustand": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.0.tgz", - "integrity": "sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.1.tgz", + "integrity": "sha512-XlauQmH64xXSC1qGYNv00ODaQ3B+tNPoy22jv2diYiP4eoDKr9LA+Bh5Bc3gplTrFdb6JVI+N4kc1DZ/tbtfPg==", "dependencies": { "use-sync-external-store": "1.2.0" }, diff --git a/package.json b/package.json index 9fb20cb..6d2b9dc 100644 --- a/package.json +++ b/package.json @@ -8,16 +8,19 @@ "start-python": "cd server && poetry run chalice local --port=5000", "dev": "vite", "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint-frontend": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 10", + "lint-backend": "cd server && poetry run flake8 && poetry run black . --check", + "lint": "concurrently npm:lint-frontend npm:lint-backend", + "postinstall": "cd server && poetry install", "preview": "vite preview" }, "engines": { "node": ">=20.10.0", - "npm": ">=9.0.0" + "npm": ">=10.0.0" }, "proxy": "http://localhost:5000", "dependencies": { - "concurrently": "^8.2.0", + "concurrently": "^8.2.2", "@headlessui/react": "^1.7.18", "@heroicons/react": "^2.1.1", "@tanstack/react-query": "^5.18.1", @@ -36,7 +39,7 @@ "react-router-dom": "^6.22.0", "react-scroll": "^1.9.0", "react-toggle-dark-mode": "^1.1.1", - "zustand": "^4.5.0" + "zustand": "^4.5.1" }, "devDependencies": { "@types/bezier-js": "^4.1.3", diff --git a/poetry.lock b/poetry.lock index 5f9f0da..5e84924 100644 --- a/poetry.lock +++ b/poetry.lock @@ -92,17 +92,17 @@ wcwidth = ">=0.1.4" [[package]] name = "boto3" -version = "1.34.44" +version = "1.34.49" description = "The AWS SDK for Python" optional = false python-versions = ">= 3.8" files = [ - {file = "boto3-1.34.44-py3-none-any.whl", hash = "sha256:40f89fb2acee0a0879effe81badffcd801a348e715483227223241ae311c48fc"}, - {file = "boto3-1.34.44.tar.gz", hash = "sha256:86bcf79a56631609a9f8023fe8f53e2869702bdd4c9047c6d9f091eb39c9b0fa"}, + {file = "boto3-1.34.49-py3-none-any.whl", hash = "sha256:ce8d1de03024f52a1810e8d71ad4dba3a5b9bb48b35567191500e3432a9130b4"}, + {file = "boto3-1.34.49.tar.gz", hash = "sha256:96b9dc85ce8d52619b56ca7b1ac1423eaf0af5ce132904bcc8aa81396eec2abf"}, ] [package.dependencies] -botocore = ">=1.34.44,<1.35.0" +botocore = ">=1.34.49,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -111,13 +111,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.44" +version = "1.34.49" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">= 3.8" files = [ - {file = "botocore-1.34.44-py3-none-any.whl", hash = "sha256:8d9837fb33256e70b9c8955a32d3e60fa70a0b72849a909737cf105fcc3b5deb"}, - {file = "botocore-1.34.44.tar.gz", hash = "sha256:b0f40c54477e8e0a5c43377a927b8959a86bb8824aaef2d28db7c9c367cdefaa"}, + {file = "botocore-1.34.49-py3-none-any.whl", hash = "sha256:4ed9d7603a04b5bb5bd5de63b513bc2c8a7e8b1cd0088229c5ceb461161f43b6"}, + {file = "botocore-1.34.49.tar.gz", hash = "sha256:d89410bc60673eaff1699f3f1fdcb0e3a5e1f7a6a048c0d88c3ce5c3549433ec"}, ] [package.dependencies] @@ -615,13 +615,13 @@ files = [ [[package]] name = "opentelemetry-api" -version = "1.22.0" +version = "1.23.0" description = "OpenTelemetry Python API" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "opentelemetry_api-1.22.0-py3-none-any.whl", hash = "sha256:43621514301a7e9f5d06dd8013a1b450f30c2e9372b8e30aaeb4562abf2ce034"}, - {file = "opentelemetry_api-1.22.0.tar.gz", hash = "sha256:15ae4ca925ecf9cfdfb7a709250846fbb08072260fca08ade78056c502b86bed"}, + {file = "opentelemetry_api-1.23.0-py3-none-any.whl", hash = "sha256:cc03ea4025353048aadb9c64919099663664672ea1c6be6ddd8fee8e4cd5e774"}, + {file = "opentelemetry_api-1.23.0.tar.gz", hash = "sha256:14a766548c8dd2eb4dfc349739eb4c3893712a0daa996e5dbf945f9da665da9d"}, ] [package.dependencies] @@ -858,19 +858,19 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] [[package]] name = "setuptools" -version = "69.1.0" +version = "69.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.1.0-py3-none-any.whl", hash = "sha256:c054629b81b946d63a9c6e732bc8b2513a7c3ea645f11d0139a2191d735c60c6"}, - {file = "setuptools-69.1.0.tar.gz", hash = "sha256:850894c4195f09c4ed30dba56213bf7c3f21d86ed6bdaafb5df5972593bfc401"}, + {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, + {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" diff --git a/server/app.py b/server/app.py index c8dffac..3dfeb63 100644 --- a/server/app.py +++ b/server/app.py @@ -1,5 +1,5 @@ from datetime import datetime -from icalendar import Calendar, Event, vCalAddress +from icalendar import Calendar, Event import json import os from chalice import Chalice, CORSConfig, ConvertToMiddleware, Response @@ -21,19 +21,20 @@ @app.route("/api/aggregate/{method}", cors=cors_config) def proxy(method): - if (method is not None): + if method is not None: data_dashboard_response = requests.get( f"https://dashboard-api.labs.transitmatters.org/api/aggregate/{method}?{urlencode(app.current_request.query_params)}" ) return json.dumps(data_dashboard_response.json(), indent=4, sort_keys=True, default=str) return None + @app.route("/calendar", cors=cors_config) def calendar(): # init the calendar cal = Calendar() - cal.add('prodid', '-//MBTA Shutdown Calendar//transitmatters.org//') - cal.add('version', '2.0') + cal.add("prodid", "-//MBTA Shutdown Calendar//transitmatters.org//") + cal.add("version", "2.0") # get the shutdowns with open("../src/constants/shutdowns.json", "r") as f: @@ -43,12 +44,15 @@ def calendar(): for shutdown in shutdowns[line]: event = Event() event_name = f"MBTA {line} Line Shutdown {shutdown['start_station']} - {shutdown['end_station']}" - event.add('name', event_name) - event.add('summary', event_name) - event.add('description', f"The MBTA {line} Line will be shut down between {shutdown['start_station']} and {shutdown['end_station']}. During this time the MBTA plans to make repairs and improvements to the line.") - event.add('dtstart', datetime.strptime(shutdown['start_date'], "%Y-%m-%d").date()) - event.add('dtend', datetime.strptime(shutdown['stop_date'], "%Y-%m-%d").date()) - event.add('color', line) + event.add("name", event_name) + event.add("summary", event_name) + event.add( + "description", + f"The MBTA {line} Line will be shut down between {shutdown['start_station']} and {shutdown['end_station']}. During this time the MBTA plans to make repairs and improvements to the line.", + ) + event.add("dtstart", datetime.strptime(shutdown["start_date"], "%Y-%m-%d").date()) + event.add("dtend", datetime.strptime(shutdown["stop_date"], "%Y-%m-%d").date()) + event.add("color", line) cal.add_component(event)