diff --git a/.eslintrc.js b/.eslintrc.js index 0c8fb30d..d55a120a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,64 +1,112 @@ module.exports = { - 'env': { - 'browser': true, - 'node': true, - 'es2021': true, + env: { + browser: true, + node: true, + es2021: true, }, - 'extends': [ + extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended', 'plugin:vue/recommended', ], - 'parserOptions': { - "parser": "@typescript-eslint/parser", - "project": "./tsconfig.json", - 'extraFileExtensions': [ '.vue' ] + parserOptions: { + parser: '@typescript-eslint/parser', + project: './tsconfig.json', + extraFileExtensions: ['.vue'], }, - 'plugins': [ - 'vue', - ], - 'rules': { - 'max-len': ['error', { - 'code': 100, - // Fixes errors in HTML files with long links - 'ignoreUrls': true, - }], + plugins: ['vue'], + rules: { + 'max-len': [ + 'error', + { + code: 100, + // Fixes errors in HTML files with long links + ignoreUrls: true, + }, + ], + + // Disable stylistic rules that interfere with Prettier + 'vue/html-closing-bracket-newline': 'off', + 'vue/html-self-closing': 'off', + 'vue/max-attributes-per-line': 'off', + 'vue/singleline-html-element-content-newline': 'off', + 'vue/html-indent': 'off', 'vue/multi-word-component-names': ['off'], // This rule is for Vue3, and Gridsome uses Vue2 'vue/no-deprecated-filter': ['off'], // Don't make multiline, it adds spaces - "vue/multiline-html-element-content-newline": ["error", { - "ignores": [ - "g-link", - // Original inline elements from: https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/utils/inline-non-void-elements.json - "a", "abbr", "audio", "b", "bdi", "bdo", "canvas", "cite", "code", "data", "del", "dfn", "em", "i", "iframe", "ins", "kbd", "label", "map", "mark", "noscript", "object", "output", "picture", "q", "ruby", "s", "samp", "small", "span", "strong", "sub", "sup", "svg", "time", "u", "var", "video" - ], - }], - "vue/singleline-html-element-content-newline": ["error", { - "externalIgnores": ["g-link"] - }], + 'vue/multiline-html-element-content-newline': [ + 'error', + { + ignores: [ + 'g-link', + // Original inline elements from: https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/utils/inline-non-void-elements.json + 'a', + 'abbr', + 'audio', + 'b', + 'bdi', + 'bdo', + 'canvas', + 'cite', + 'code', + 'data', + 'del', + 'dfn', + 'em', + 'i', + 'iframe', + 'ins', + 'kbd', + 'label', + 'map', + 'mark', + 'noscript', + 'object', + 'output', + 'picture', + 'q', + 'ruby', + 's', + 'samp', + 'small', + 'span', + 'strong', + 'sub', + 'sup', + 'svg', + 'time', + 'u', + 'var', + 'video', + ], + }, + ], '@typescript-eslint/indent': ['error', 2], - "semi": ["error", "always"], - "arrow-parens": ["error", "always"], - "comma-dangle": ["error", "always-multiline"], + semi: ['error', 'always'], + 'arrow-parens': ['error', 'always'], + 'comma-dangle': ['error', 'always-multiline'], - "@typescript-eslint/explicit-function-return-type": ["error", { - "allowExpressions": true, - "allowHigherOrderFunctions": true, - "allowTypedFunctionExpressions": true - }] + '@typescript-eslint/explicit-function-return-type': [ + 'error', + { + allowExpressions: true, + allowHigherOrderFunctions: true, + allowTypedFunctionExpressions: true, + }, + ], }, - 'overrides': [ + overrides: [ { - 'files': ['*.vue', 'declarations/*.ts'], - 'rules': { - 'indent': 'off', + files: ['*.vue', 'declarations/*.ts'], + rules: { + indent: 'off', '@typescript-eslint/indent': 'off', '@typescript-eslint/no-var-requires': 0, - } - } - ] + }, + }, + ], }; diff --git a/.github/workflows/eslint.yml b/.github/workflows/linting.yml similarity index 78% rename from .github/workflows/eslint.yml rename to .github/workflows/linting.yml index eca608b7..d30c53a1 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/linting.yml @@ -1,20 +1,24 @@ +### +### GitHub action to run linting (ESLint & Prettier) +### + # ESLint is a tool for identifying and reporting on patterns # found in ECMAScript/JavaScript code. # More details at https://github.com/eslint/eslint # and https://eslint.org -name: ESLint +name: ESLint & Prettier on: push: - branches: [ "main" ] + branches: ['main'] pull_request: # The branches below must be a subset of the branches above - branches: [ "main" ] + branches: ['main'] jobs: - eslint: - name: Run ESLint scanning + linting: + name: Run ESLint & Prettier runs-on: ubuntu-latest permissions: contents: read @@ -33,9 +37,12 @@ jobs: run: yarn install - name: Run ESLint - run: yarn lint-ci + run: yarn eslint-ci continue-on-error: true + - name: Run Prettier + run: yarn prettier . --check + - name: Upload analysis results to GitHub uses: github/codeql-action/upload-sarif@v2 with: diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index ebea6994..bb09991b 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -4,10 +4,10 @@ name: Pytest Data Tests on: push: - branches: [ "main" ] + branches: ['main'] pull_request: # The branches below must be a subset of the branches above - branches: [ "main" ] + branches: ['main'] jobs: pytest: diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..c6d72715 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +# Ignore built files +dist diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..544138be --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/README.md b/README.md index 4692f18d..e1ef228e 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ When you see the above output, it means the site is now running and now you can ### Run Front-End Linting -To run linting with auto-fix, run the following command: +To run linting with auto-fixing (ESLint + Prettier), run the following command: ```bash docker-compose run --rm electrify-chicago yarn lint-fix @@ -72,7 +72,7 @@ docker-compose run --rm electrify-chicago yarn lint-fix ### Run Data Processing 1. If you update the raw data CSVs or the data scripts that post-process them (like if you are adding -a new statistical analysis), you need to re-run the data processing. + a new statistical analysis), you need to re-run the data processing. 2. To then process a new CSV file (at `src/data/source/ChicagoEnergyBenchmarking.csv`), you need to run the following command: @@ -93,8 +93,9 @@ docker-compose run --rm electrify-chicago bash create_test_data.sh ```bash docker-compose run --rm electrify-chicago python -m pytest ``` + 3. Run the following command for individual unit test suite (where YOUR_FILE_NAME is something like -`test_clean_all_years`): + `test_clean_all_years`): ```bash docker-compose run --rm electrify-chicago python -m pytest tests/data/scripts/unit/YOUR_FILE_NAME.py @@ -107,7 +108,7 @@ docker-compose run --rm electrify-chicago python -m pytest tests/data/scripts/un If there's a new large building owner to add, simply: 1. **Add the building owner in the `BuildingOwners` constant** in `buildings-custom-info.constant.vue` - -this defines metadata about the owner like their name and logo URLs + this defines metadata about the owner like their name and logo URLs Example: @@ -122,8 +123,8 @@ iit: { ``` 2. **Tag buildings they own in the `BuildingsCustomInfo` constant** (in the same -`buildings-custom-info.constant.vue` file) - this associates a given building (by its numeric unique -ID, found under its address on its details page), with a given owner. + `buildings-custom-info.constant.vue` file) - this associates a given building (by its numeric unique + ID, found under its address on its details page), with a given owner. Example: @@ -133,7 +134,7 @@ Example: ``` 3. **Setup their route by adding the new owner's ID (key) to `BuildingOwnerIds`** (in -`gridsome.server.js`) - this tells Gridsome to create a route for this given slug + `gridsome.server.js`) - this tells Gridsome to create a route for this given slug Example: @@ -141,7 +142,7 @@ Example: const BuildingOwnerIds = [ 'iit', // ... -] +]; ``` **Note:** You'll have to restart your `yarn develop` after step 3 to see changes, since @@ -149,8 +150,8 @@ const BuildingOwnerIds = [ ### Adding Building Images -1. **Find A Suitable Image* -- Building images can be sourced from Google Maps or a source that allows redistribution, like -Wikimedia. +1. \*_Find A Suitable Image_ -- Building images can be sourced from Google Maps or a source that allows redistribution, like + Wikimedia. 2 **Process the Image** @@ -162,12 +163,11 @@ We should reasonably crop images if needed and then scale them to be EITHER: Make sure to export it as a `.jpg` image at a quality level of 70, which should ensure a reasonable file size under 200 kB. -**Store the image in `/static/building-imgs/`. +\*\*Store the image in `/static/building-imgs/`. 3. **Tell The Site There's a Building Image** - Follow the pattern of other buildings in the -`building-images.constant.vue`, providing an attribution URL, the image file name, and specify -whether it's a tall (portrait) image and whether it's from Google Maps. - + `building-images.constant.vue`, providing an attribution URL, the image file name, and specify + whether it's a tall (portrait) image and whether it's from Google Maps. 4. **Confirm the image is visible and looks good** - and that's all there is to it! diff --git a/docker-compose.yml b/docker-compose.yml index fae95a02..02fc07d6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: context: . dockerfile: Dockerfile ports: - - "8080:8080" + - '8080:8080' entrypoint: /app/docker-entrypoint.sh volumes: - .:/app diff --git a/gridsome.config.js b/gridsome.config.js index fb7ce1f5..e10b5524 100644 --- a/gridsome.config.js +++ b/gridsome.config.js @@ -12,16 +12,14 @@ const path = require('path'); * @param {string} rule A rule? */ function addStyleResource(rule) { - rule.use('style-resource') - .loader('style-resources-loader') - .options({ - patterns: [ - path.resolve(__dirname, './src/scss/*.scss'), - ], - }); + rule + .use('style-resource') + .loader('style-resources-loader') + .options({ + patterns: [path.resolve(__dirname, './src/scss/*.scss')], + }); } - module.exports = { siteName: 'Electrify Chicago', plugins: [ diff --git a/gridsome.server.js b/gridsome.server.js index c41c74c8..cb50e812 100644 --- a/gridsome.server.js +++ b/gridsome.server.js @@ -9,7 +9,7 @@ * From fetching CSV data: * https://gridsome.org/docs/fetching-data/#csv */ -const {readFileSync} = require('fs'); +const { readFileSync } = require('fs'); const parse = require('csv-parse/sync').parse; const DataDirectory = './src/data/dist/'; @@ -36,7 +36,7 @@ const BuildingOwnerIds = [ 'npu', ]; -module.exports = function(api) { +module.exports = function (api) { // Use the Data Store API here: https://gridsome.org/docs/data-store-api/ api.loadSource(async (actions) => { loadBuildingBenchmarkData(actions); @@ -47,13 +47,13 @@ module.exports = function(api) { api.createPages(({ createPage }) => { // Create pages for building owners. This could be a dynamic route, but making it this way // should let them get statically built as expected - BuildingOwnerIds.forEach(ownerId => { + BuildingOwnerIds.forEach((ownerId) => { createPage({ path: `/owner/${ownerId}`, component: './src/templates/BuildingOwner.vue', context: { ownerId }, - }) - }) + }); + }); }); }; @@ -63,7 +63,10 @@ module.exports = function(api) { * @param {unknown} actions The actions class? */ function loadBuildingBenchmarkData(actions) { - const latestBenchmarksRaw = readFileSync(`${DataDirectory}${BuildingEmissionsDataFile}`, 'utf8'); + const latestBenchmarksRaw = readFileSync( + `${DataDirectory}${BuildingEmissionsDataFile}`, + 'utf8', + ); /** * Load in building benchmarks and expose as Buildings collection @@ -73,12 +76,15 @@ function loadBuildingBenchmarkData(actions) { skip_empty_lines: true, }); - const collection = actions.addCollection({typeName: 'Building'}); + const collection = actions.addCollection({ typeName: 'Building' }); for (const building of LatestBenchmarksData) { // Make a slugSource that is the property name or the address as a fallback (skip one letter // names, e.g. '-) - building.slugSource = building.PropertyName.length > 1 ? building.PropertyName : building.Address; + building.slugSource = + building.PropertyName.length > 1 + ? building.PropertyName + : building.Address; if (!building.slugSource || typeof building.slugSource !== 'string') { throw new Error('No building slug source (name or address)!', building); @@ -88,14 +94,16 @@ function loadBuildingBenchmarkData(actions) { } } - /** * Load in the historic benchmark data * * @param {unknown} actions The actions class? */ function loadHistoricBenchmarkDat(actions) { - const historicBenchmarksRaw = readFileSync(`${DataDirectory}${HistoricBenchmarkingDataFile}`, 'utf8'); + const historicBenchmarksRaw = readFileSync( + `${DataDirectory}${HistoricBenchmarkingDataFile}`, + 'utf8', + ); /** * Load in building benchmarks and expose as Buildings collection diff --git a/package.json b/package.json index 78fa9f69..ff77eda3 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,10 @@ "build": "gridsome build", "develop": "gridsome develop", "explore": "gridsome explore", - "lint": "yarn eslint --ext .ts,.vue ./src", - "lint-ci": "yarn lint --format @microsoft/eslint-formatter-sarif --output-file eslint-results.sarif", - "lint-fix": "yarn lint --fix" + "eslint-ci": "yarn eslint-base --format @microsoft/eslint-formatter-sarif --output-file eslint-results.sarif", + "eslint-base": "yarn eslint --ext .ts,.vue ./src", + "lint": "yarn eslint-base && npx prettier . --check", + "lint-fix": "yarn eslint-base --fix && npx prettier . --write" }, "dependencies": { "csv-parse": "^5.3.6", @@ -39,6 +40,7 @@ "eslint-plugin-promise": "^6.1.1", "eslint-plugin-vue": "^9.9.0", "gridsome-plugin-typescript": "^0.4.0", + "prettier": "3.4.2", "sass": "^1.59.3", "sass-loader": "^10.1.1", "style-resources-loader": "^1.5.0", diff --git a/src/common-functions.vue b/src/common-functions.vue index 051b09f7..204aa28f 100644 --- a/src/common-functions.vue +++ b/src/common-functions.vue @@ -1,5 +1,5 @@ diff --git a/src/components/BuildingsTable.vue b/src/components/BuildingsTable.vue index 3194008d..5e733d26 100644 --- a/src/components/BuildingsTable.vue +++ b/src/components/BuildingsTable.vue @@ -6,7 +6,6 @@ import OverallRankEmoji from './OverallRankEmoji.vue'; import OwnerLogo from './OwnerLogo.vue'; import { IBuilding, IBuildingBenchmarkStats } from '../common-functions.vue'; - // This simple JSON is a lot easier to just use directly than going through GraphQL and it's // tiny import BuildingBenchmarkStats from '../data/dist/building-benchmark-stats.json'; @@ -20,72 +19,51 @@ import BuildingBenchmarkStats from '../data/dist/building-benchmark-stats.json'; }) export default class BuildingsTable extends Vue { /** Expose stats to template */ - readonly BuildingBenchmarkStats: IBuildingBenchmarkStats = BuildingBenchmarkStats; + readonly BuildingBenchmarkStats: IBuildingBenchmarkStats = + BuildingBenchmarkStats; - @Prop({required:true}) buildings!: Array<{ node: IBuilding }>; + @Prop({ required: true }) buildings!: Array<{ node: IBuilding }>; - @Prop({default: false}) showSquareFootage!: boolean; + @Prop({ default: false }) showSquareFootage!: boolean; - @Prop({default: false}) showGasUse!: boolean; + @Prop({ default: false }) showGasUse!: boolean; - @Prop({default: false}) showElectricityUse!: boolean; + @Prop({ default: false }) showElectricityUse!: boolean; } - + - + @@ -214,7 +170,9 @@ export default class BuildingsTable extends Vue { min-width: 88rem; // Wide columns shouldn't be as wide if we have more of them - .wide-col { width: 17%; } + .wide-col { + width: 17%; + } } a { @@ -228,7 +186,9 @@ export default class BuildingsTable extends Vue { position: sticky; top: 0; - tr { background-color: $grey-dark; } + tr { + background-color: $grey-dark; + } th { text-align: left; @@ -244,18 +204,31 @@ export default class BuildingsTable extends Vue { } } - th, td { + th, + td { padding: 0.75rem; line-height: 1.25; - &:first-of-type { padding-left: 1rem; } - &:last-of-type { padding-right: 1rem; } - &.numeric { text-align: right; } - &.wide-col { width: 20%; } - &.prop-type { width: 12rem; } + &:first-of-type { + padding-left: 1rem; + } + &:last-of-type { + padding-right: 1rem; + } + &.numeric { + text-align: right; + } + &.wide-col { + width: 20%; + } + &.prop-type { + width: 12rem; + } } - tr:nth-of-type(2n + 2) { background-color: $grey; } + tr:nth-of-type(2n + 2) { + background-color: $grey; + } .prop-address { font-size: 0.75em; @@ -277,7 +250,10 @@ export default class BuildingsTable extends Vue { font-size: 0.825rem; padding: 0.5rem 0.25rem; } - td.property-name, td.property-address { width: 10rem; } + td.property-name, + td.property-address { + width: 10rem; + } } } } diff --git a/src/components/DataDisclaimer.vue b/src/components/DataDisclaimer.vue index c2a981eb..447dbde6 100644 --- a/src/components/DataDisclaimer.vue +++ b/src/components/DataDisclaimer.vue @@ -1,29 +1,27 @@