Skip to content

Commit 4e91358

Browse files
committed
2 parents 607f791 + 329a5ba commit 4e91358

File tree

80 files changed

+5597
-5675
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+5597
-5675
lines changed

.github/actions/convertIssue/issue-to-json.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,13 @@ export async function issueToJson() {
4242

4343
let abbrevKey = getInput("hash-property-name");
4444
abbrevKey = abbrevKey.toLowerCase();
45-
let fileName = getFileName(configData[ abbrevKey ]);
46-
await writeFile(path.join(outputDir, fileName), JSON.stringify(configData, null, 2));
45+
let abbrev = configData[abbrevKey];
46+
exportVariable("UrlAbbreviation", abbrev);
47+
let fileName = getFileName(abbrev);
48+
await writeFile(
49+
path.join(outputDir, fileName),
50+
JSON.stringify(configData, null, 4) + "\n",
51+
);
4752
} catch (error) {
4853
setFailed(error.message);
4954
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Update Deployment Config
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
deployment:
7+
required: true
8+
type: string
9+
description: Name of the deployment to update config for (e.g. nrel-commute)
10+
user_email:
11+
required: true
12+
type: string
13+
description: Email of the user triggering the workflow
14+
script_name:
15+
required: true
16+
type: choice
17+
options:
18+
- update_admin_access
19+
script_args:
20+
required: false
21+
type: string
22+
description: Additional arguments for the script (space-separated)
23+
token:
24+
required: false
25+
type: string
26+
description: GitHub token for authentication. If not provided, the workflow will use the default token.
27+
28+
env:
29+
GITHUB_TOKEN: ${{ inputs.token || secrets.GITHUB_TOKEN }}
30+
31+
jobs:
32+
update_config:
33+
runs-on: ubuntu-latest
34+
35+
outputs:
36+
PR_URL: ${{ steps.commit_and_pr.outputs.PR_URL }}
37+
38+
steps:
39+
- name: Checkout code
40+
uses: actions/checkout@v3
41+
42+
- name: Set up Python
43+
uses: actions/setup-python@v4
44+
with:
45+
python-version: '3.9'
46+
47+
- name: Create update branch
48+
id: create_branch
49+
run: |
50+
BRANCH_NAME="config-update-${{ github.event.inputs.script_name }}-${{ github.run_id }}"
51+
git checkout -b $BRANCH_NAME
52+
echo "BRANCH_NAME=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
53+
54+
- name: Run config update script
55+
run: |
56+
python bin/config_update/${{ inputs.script_name }}.py ${{ inputs.deployment }} ${{ inputs.script_args }}
57+
58+
- name: Commit and PR changes if needed
59+
id: commit_and_pr
60+
run: |
61+
git config user.name "GitHub Actions"
62+
git config user.email "actions@github.com"
63+
git add .
64+
git commit \
65+
-m "[${{ inputs.deployment }}] ${{ inputs.script_name }}" \
66+
-m "Updated [${{ inputs.deployment }}] configuration using \`${{ inputs.script_name }}\` with args \`${{ inputs.script_args }}\`" \
67+
-m "This commit was generated by the \`config-update\` workflow: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
68+
-m "Triggered by admin user: ${{ inputs.user_email }}"
69+
git push --set-upstream origin ${{ steps.create_branch.outputs.BRANCH_NAME }}
70+
echo "Creating PR..."
71+
PR_URL=$(gh pr create --base main --head ${{ steps.create_branch.outputs.BRANCH_NAME }} --fill)
72+
GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} gh pr merge "$PR_URL" --auto --squash
73+
echo "PR created and will merge if checks pass: $PR_URL"

.github/workflows/issue-to-json.yml

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,15 @@ jobs:
1414
if: contains( github.event.issue.title, 'New Project Configuration' )
1515
runs-on: ubuntu-latest
1616
steps:
17-
# This step checks out a copy of your repository.
1817
- name: Check out repository
1918
uses: actions/checkout@v4
20-
- name: install dependencies
21-
shell: bash -l {0}
22-
run: |
23-
npm install js-yaml
24-
npm i @actions/core
25-
npm i @actions/github
26-
# This step references the directory that contains the action.
19+
20+
- name: Install dependencies
21+
run: npm i js-yaml @actions/core @actions/github
22+
23+
- name: Create new branch
24+
run: git checkout -b new-config-${{ github.event.issue.number }}
25+
2726
- name: Use local convertIssue action
2827
uses: ./.github/actions/convertIssue
2928
with:
@@ -33,27 +32,25 @@ jobs:
3332
issue-template: "add-new-config.yml"
3433
# This controls which property we use to key the file name
3534
hash-property-name: "url_abbreviation"
36-
- name: Commit files
35+
36+
- name: Generate token to authenticate as op-config-updates GitHub App
37+
id: generate_token
38+
uses: tibdex/github-app-token@v2
39+
with:
40+
app_id: ${{ secrets.CONFIG_UPDATES_GH_APP_ID }}
41+
private_key: ${{ secrets.CONFIG_UPDATES_GH_APP_PRIVATE_KEY }}
42+
43+
- name: Commit and PR changes
44+
env:
45+
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
3746
run: |
3847
git config --local user.email "action@github.com"
3948
git config --local user.name "GitHub Action"
4049
git add configs/
41-
git commit -m "Creating or updating file from form in issue #${{ env.IssueNumber }}"
42-
- name: Create Pull Request
43-
id: cpr
44-
uses: peter-evans/create-pull-request@v5
45-
with:
46-
add-paths: |
47-
configs/
48-
commit-message: Create or update a new config file
49-
signoff: false
50-
branch: new-config-#${{ env.IssueNumber }}
51-
delete-branch: false
52-
title: '[Config #${{ env.IssueNumber }}] create new file'
53-
body: |
54-
Adding a new config file
55-
- Initialized by creating or updating the coressponding issue
56-
- Auto-generated by [create-pull-request][1]
57-
58-
[1]: https://github.com/peter-evans/create-pull-request
59-
draft: false
50+
git commit \
51+
-m "[${{ env.UrlAbbreviation }}] generate config from #${{ github.event.issue.number }}" \
52+
-m "- Triggered by creating or updating #${{ github.event.issue.number }}" \
53+
-m "- Auto-generated by the \`convert-issue-to-json\` workflow" \
54+
-m "When merged, this will close #${{ github.event.issue.number }}"
55+
git push --set-upstream origin new-config-${{ github.event.issue.number }}
56+
gh pr create --base main --head new-config-${{ github.event.issue.number }} --fill
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Validate Configs
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
validate_configs:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v3
16+
17+
- name: Set up Node.js
18+
uses: actions/setup-node@v3
19+
with:
20+
node-version: '18'
21+
22+
- name: Generate JSON schema with typescript-json-schema
23+
run: |
24+
npm install -g typescript-json-schema
25+
npx typescript-json-schema index.d.ts DeploymentConfig --noExtraProps --required --out config.schema.json
26+
27+
- name: Validate all configs against config.schema.json
28+
uses: cardinalby/schema-validator-action@v3
29+
with:
30+
file: 'configs/*.json'
31+
schema: 'config.schema.json'

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
.DS_Store
22
.vscode/settings.json
3+
.env
4+
__pycache__

README.md

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,20 @@ If you are here to test out the app on your personal phone, please use the open-
4444

4545
In general, if you are planning to keep the app installed for less than a day, please use stage so you don't pollute the real dataset.
4646

47-
#### Testing configs
47+
#### Testing local configs on e-mission-phone
4848

49-
As we test more config options, we sometimes need to be able to edit and load configs locally without pushing to github
50-
and waiting for a PR to be approved.
49+
As we test more config options, we sometimes need to be able to test local config changes without pushing to GitHub and waiting for a PR to be approved.
5150

5251
To accomplish this:
53-
- Change the download URL in `www/js/config/dynamic_config.js` to `"http://localhost:9090/configs/"+label+".nrel-op.json"`
54-
- Modify one of the existing configs **OR** create a new config and add it to `docs/index.html`
55-
- `docker-compose -f docker-compose.dev.yml up -d`
56-
- In the emulator, go to http://localhost:9090 and click on the appropriate link
52+
- Modify one of the existing configs in your local repo or create a new one (e.g. my-deployment.nrel-op.json)
53+
- `docker-compose -f docker-compose.dev.yml up -d` to serve your local configs on localhost:9090
54+
- With an emulator running e-mission-phone, log in with an opcode for that deployment (e.g. nrelop_my-deployment_1234)
55+
- As long as you are not using a 'production' build of e-mission-phone, the app will check localhost:9090 before it checks GitHub
56+
- You can use "Profile" > "Refresh App Configuration" to quickly test additional changes as long as "version" is incremented in your new version of the config
5757

58-
### File format
58+
### Config options
5959

60-
Config format (with default values) is:
60+
Example config format (with default values) is:
6161

6262
```
6363
{
@@ -95,13 +95,4 @@ Config format (with default values) is:
9595
}
9696
```
9797

98-
### Development
99-
100-
This repo has some simple GitHub pages in the `docs` repo
101-
102-
If you want to experiment with them (e.g. by changing the format or the URL
103-
prefix), you can use the attached `docker-compose.yml` to serve the pages
104-
locally at http://localhost:9090
105-
106-
I found this useful while testing the QR code functionality on the devapp,
107-
which responds to the `emission` URL prefix, not `nrelopenpath`
98+
A TypeScript specification of all the available config options is available in `index.d.ts`.

bin/config_update/_util.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import os
2+
import sys
3+
import json
4+
5+
6+
def read_config(deployment):
7+
config_file_path = f'configs/{deployment}.nrel-op.json'
8+
if not os.path.exists(config_file_path):
9+
print(f"There is no deployment called {deployment} - no changes made.")
10+
sys.exit(1)
11+
with open(config_file_path, 'r', encoding='utf-8') as f:
12+
return json.load(f)
13+
14+
15+
def update_config(deployment, new_config, msg):
16+
config_file_path = f'configs/{deployment}.nrel-op.json'
17+
if new_config and msg:
18+
with open(config_file_path, 'w', encoding='utf-8') as f:
19+
json.dump(new_config, f, indent=4, ensure_ascii=False)
20+
f.write("\n")
21+
print(f"Updated {config_file_path} with new config.")
22+
else:
23+
print("No changes made.")
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""
2+
Add or remove admin email addresses from admin_dashboard.admin_access
3+
"""
4+
5+
import sys
6+
import argparse
7+
import re
8+
9+
from _util import read_config, update_config
10+
11+
12+
def add_admin_email(config, email):
13+
if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w{2,}$', email):
14+
print(f"{email} is an invalid email format - no changes made.")
15+
sys.exit(1)
16+
17+
if email not in config.get('admin_dashboard', {}).get('admin_access', []):
18+
if 'admin_dashboard' not in config:
19+
config['admin_dashboard'] = {}
20+
if 'admin_access' not in config['admin_dashboard']:
21+
config['admin_dashboard']['admin_access'] = []
22+
config['admin_dashboard']['admin_access'].append(email)
23+
print(f"Added {email} to the admin_access list.")
24+
else:
25+
print(f"{email} is already an admin - no changes made.")
26+
return
27+
28+
return config
29+
30+
31+
def remove_admin_email(config, email):
32+
if email in config.get('admin_dashboard', {}).get('admin_access', []):
33+
config['admin_dashboard']['admin_access'].remove(email)
34+
else:
35+
print(f"{email} is not an admin - no changes made.")
36+
return
37+
38+
return config
39+
40+
41+
def update_admin_access(config, action, email):
42+
new_config = None
43+
if action == 'add':
44+
new_config = add_admin_email(config, email)
45+
elif action == 'remove':
46+
new_config = remove_admin_email(config, email)
47+
48+
return new_config
49+
50+
51+
if __name__ == '__main__':
52+
parser = argparse.ArgumentParser()
53+
parser.add_argument('deployment', type=str)
54+
parser.add_argument('action', type=str, help='Action to perform (add/remove)')
55+
parser.add_argument('email', type=str, help='Email address to add/remove')
56+
args = parser.parse_args()
57+
58+
config = read_config(args.deployment)
59+
new_config = update_admin_access(config, args.action, args.email)
60+
if new_config:
61+
msg = f"update admin_access\n\n - {args.action} {args.email}"
62+
update_config(args.deployment, new_config, msg)
63+
else:
64+
print("No changes made.")

bin/formatAllConfigs.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
const configsDir = path.join(process.cwd(), 'configs');
5+
6+
fs.readdir(configsDir, (err, files) => {
7+
files.forEach(file => {
8+
const filePath = path.join(configsDir, file);
9+
fs.readFile(filePath, 'utf-8', (err, data) => {
10+
const jsonData = JSON.parse(data);
11+
const formattedData = JSON.stringify(jsonData, null, 4) + '\n';
12+
fs.writeFile(filePath, formattedData, 'utf-8', (err) => {});
13+
});
14+
});
15+
});

0 commit comments

Comments
 (0)