diff --git a/.github/workflows/frontend-json.yml b/.github/workflows/frontend-json.yml new file mode 100644 index 00000000..4046a817 --- /dev/null +++ b/.github/workflows/frontend-json.yml @@ -0,0 +1,42 @@ +name: JSON validate Frontend service + +on: + push: + branches: + - main + paths: + - 'frontend/pbtar_schema.json' + - 'frontend/src/data/scenarios_metadata.json' + - '.github/workflows/frontend-json.yml' + pull_request: + branches: + - main + paths: + - 'frontend/pbtar_schema.json' + - 'frontend/src/data/scenarios_metadata.json' + - '.github/workflows/frontend-json.yml' + workflow_dispatch: + +jobs: + lint: + name: "Run ajv JSON validator" + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + working-directory: ./frontend + run: npm ci + + - name: Run ajv JSON validator + working-directory: ./frontend + run: npm run json:check diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c4021b1f..e64d47c2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,6 +16,7 @@ }, "devDependencies": { "@eslint/js": "^9.27.0", + "@jirutka/ajv-cli": "^6.0.0", "@tailwindcss/postcss": "^4.1.7", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", @@ -1063,6 +1064,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@humanwhocodes/momoa": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-3.3.8.tgz", + "integrity": "sha512-/3PZzor2imi/RLLcnHztkwA79txiVvW145Ve2cp5dxRcH5qOUNJPToasqLFHniTfw4B4lT7jGDdBOPXbXYlIMQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", @@ -1089,6 +1100,84 @@ "node": ">=18.0.0" } }, + "node_modules/@jirutka/ajv-cli": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@jirutka/ajv-cli/-/ajv-cli-6.0.0.tgz", + "integrity": "sha512-qQbBwIUltRLJow/E+H/ipga0pc4qcY31PDNM1RhkS+dn/hFDS+HuuEKEBBmACRj+uV8N07+ybskSgKBDujkmeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@humanwhocodes/momoa": "^3.0.2", + "@json-schema-tools/traverse": "^1.10.4", + "ajv": "^8.13.0", + "chalk": "^5.3.0", + "damerau-levenshtein": "^1.0.8", + "fast-json-patch": "^3.1.0", + "picomatch": "^4.0.2", + "type-flag": "^3.0.0", + "yaml": "^2.4.5" + }, + "bin": { + "ajv": "lib/main.js" + } + }, + "node_modules/@jirutka/ajv-cli/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@jirutka/ajv-cli/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jirutka/ajv-cli/node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jirutka/ajv-cli/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jirutka/ajv-cli/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", @@ -1137,6 +1226,13 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@json-schema-tools/traverse": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@json-schema-tools/traverse/-/traverse-1.10.4.tgz", + "integrity": "sha512-9e42zjhLIxzBONroNC4SGsTqdB877tzwH2S6lqgTav9K24kWJR9vNieeMVSuyqnY8FlclH21D8wsm/tuD9WA9Q==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2643,6 +2739,13 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/data-urls": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", @@ -3030,6 +3133,23 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -4212,6 +4332,16 @@ "node": ">=8" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4631,6 +4761,16 @@ "node": ">= 0.8.0" } }, + "node_modules/type-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/type-flag/-/type-flag-3.0.0.tgz", + "integrity": "sha512-3YaYwMseXCAhBB14RXW5cRQfJQlEknS6i4C8fCfeUdS3ihG9EdccdR9kt3vP73ZdeTGmPb4bZtkDn5XMIn1DLA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/type-flag?sponsor=1" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -5034,6 +5174,19 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 5089b9c0..d96246d5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,7 +13,8 @@ "lint": "eslint .", "lint:fix": "eslint . --fix", "format": "prettier --write .", - "format:check": "prettier --check ." + "format:check": "prettier --check .", + "json:check": "ajv validate -s pbtar_schema.json src/data/scenarios_metadata.json" }, "author": "RMI", "license": "MIT", @@ -25,6 +26,7 @@ }, "devDependencies": { "@eslint/js": "^9.27.0", + "@jirutka/ajv-cli": "^6.0.0", "@tailwindcss/postcss": "^4.1.7", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", diff --git a/frontend/pbtar_schema.json b/frontend/pbtar_schema.json new file mode 100644 index 00000000..b2666c08 --- /dev/null +++ b/frontend/pbtar_schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "pbtar_schema.json", + "title": "PBTAR Scenarios metadata Schema", + "description": "A schema for the scenarios metadata dataset in PBTAR", + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a scenario", + "type": "string" + }, + "name": { + "description": "Name of the scenario", + "type": "string" + }, + "description": { + "description": "Description of the scenario", + "type": "string" + }, + "category": { + "description": "Category of the scenario", + "type": "string" + }, + "target_year": { + "description": "Target year of the scenario", + "type": "string" + }, + "target_temperature": { + "description": "Target temperature of the scenario", + "type": "string" + }, + "regions": { + "description": "Regions that the scenario covers", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "sectors": { + "description": "Sectors that the scenario covers", + "type": "array", + "items": { + "enum": [ + "Agriculture", + "Buildings", + "Coal", + "Industrial", + "Oil & Gas", + "Power", + "Renewables", + "Transport" + ] + } + }, + "publisher": { + "description": "Publisher of the scenario", + "type": "string" + }, + "published_date": { + "description": "Date that the scenario was published", + "type": "string" + }, + "overview": { + "description": "Overview of the scenario", + "type": "string" + }, + "expertRecommendation": { + "description": "Expert recommendation for the scenario", + "type": "string" + }, + "dataSource": { + "description": "Data source for the scenario", + "type": "object", + "properties": { + "description": { "type": "string" }, + "url": { "type": "string" }, + "downloadAvailable": { "type": "boolean" } + }, + "additionalProperties": false, + "required": ["description", "url", "downloadAvailable"] + } + }, + "additionalProperties": false, + "required": [ + "id", + "name", + "description", + "category", + "target_year", + "target_temperature", + "regions", + "sectors", + "publisher", + "published_date", + "overview", + "expertRecommendation", + "dataSource" + ] + } +}