Skip to content

Commit 2c18804

Browse files
authored
Merge pull request #44 from ONS-Innovation/dataset_refresh
KEH-1020 | Dataset Refresh Button
2 parents 83d1733 + 7327a77 commit 2c18804

5 files changed

Lines changed: 120 additions & 2 deletions

File tree

docs/dashboard/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ Displays metrics on Dependabot alerts.
4242
- **Repository Overview:** Provides a high-level view of the repositories with Dependabot alerts, including the number of alerts and their severity.
4343
- **Repository Severity Proportion:** Displays the proportion of alerts by severity for each repository, allowing users to identify the most critical issues.
4444

45+
### Data Refreshing
46+
47+
- Users can refresh the backend data manually by clicking the "Refresh Data" button in the sidebar. This will trigger the Data Logger to collect the latest data from GitHub and update the S3 bucket. This functionality is considerate of GitHub's API rate limits, ensuring that there is enough rate limit remaining before attempting to refresh the data. If the rate limit is exceeded, the user will be informed and the refresh will not proceed.
48+
4549
## Data Collection Process
4650

4751
The Dashboard relies on the data collected by the Data Logger, which is responsible for gathering information from GitHub repositories and storing it in a database. The Data Logger runs periodically to ensure that the Dashboard has up-to-date information.

src/app.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""The main application entry point for the GitHub Policy Dashboard."""
22

33
import streamlit as st
4+
from src.refresh_data import refresh_data
45

56
st.set_page_config(
67
page_title="GitHub Policy Dashboard",
@@ -12,6 +13,24 @@
1213
st.Page("./repositories/repositories.py", title="Repositories", icon="📦"),
1314
st.Page("./secret_scanning/secret_scanning.py", title="Secret Scanning", icon="🔍"),
1415
st.Page("./dependabot/dependabot.py", title="Dependabot", icon="🤖"),
15-
])
16+
],
17+
expanded=True,
18+
)
19+
20+
if st.sidebar.button(
21+
"Refresh Dataset",
22+
key="refresh_dataset",
23+
help="Click to refresh the dataset from GitHub. This may take a few minutes.",
24+
icon="🔄",
25+
):
26+
with st.spinner("Refreshing dataset... This may take a few minutes."):
27+
status = refresh_data()
28+
29+
if status["status"] == "success":
30+
# Clear cache to ensure fresh data is loaded
31+
st.cache_data.clear()
32+
st.success(status["message"])
33+
else:
34+
st.error(status["message"])
1635

1736
pg.run()

src/refresh_data.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""A Python script to refresh the dataset for the GitHub Policy Dashboard."""
2+
3+
import botocore.config
4+
import boto3
5+
import botocore
6+
from requests import Response
7+
import datetime
8+
9+
import utilities as utils
10+
11+
def refresh_data() -> dict:
12+
"""A function to refresh the dataset for the GitHub Policy Dashboard.
13+
14+
Returns:
15+
dict: A dictionary containing the status of the data refresh operation and a message.
16+
"""
17+
18+
# Check GitHub API rate limit
19+
# If not enough rate limit, show error message and say when to try again
20+
# If enough rate limit, proceed with data refresh
21+
22+
env = utils.get_environment_variables()
23+
24+
session = boto3.Session()
25+
secret_manager = session.client("secretsmanager", region_name=env["secret_region"])
26+
27+
rest = utils.get_rest_interface(
28+
secret_manager,
29+
env["secret_name"],
30+
env["org"],
31+
env["client_id"]
32+
)
33+
34+
response = rest.get("/rate_limit")
35+
36+
if type(response) is not Response:
37+
return {"status": "error", "message": "Error fetching rate limit from GitHub API."}
38+
39+
rate_limit = response.json()
40+
41+
remaining = {
42+
"rest": {
43+
"remaining": rate_limit["rate"]["remaining"],
44+
"reset": rate_limit["rate"]["reset"]
45+
},
46+
"graphql": {
47+
"remaining": rate_limit["resources"]["graphql"]["remaining"],
48+
"reset": rate_limit["resources"]["graphql"]["reset"]
49+
}
50+
}
51+
52+
if remaining["rest"]["remaining"] < 5000:
53+
54+
reset_time = datetime.datetime.fromtimestamp(
55+
remaining["rest"]["reset"]
56+
).strftime("%H:%M")
57+
58+
return {"status": "error", "message": f"GitHub API rate limit exceeded. Please try again after {reset_time}."}
59+
60+
if remaining["graphql"]["remaining"] < 8000:
61+
62+
reset_time = datetime.datetime.fromtimestamp(
63+
remaining["graphql"]["reset"]
64+
).strftime("%H:%M")
65+
66+
return {"status": "error", "message": f"GitHub GraphQL API rate limit exceeded. Please try again after {reset_time}."}
67+
68+
# Proceed with data refresh
69+
70+
lambda_config = botocore.config.Config(
71+
read_timeout=900, # 15 minutes (Maximum timeout for Lambda)
72+
retries={
73+
"total_max_attempts": 1,
74+
}
75+
)
76+
77+
lambda_client = session.client("lambda", region_name=env["secret_region"], config=lambda_config)
78+
79+
response = lambda_client.invoke(
80+
FunctionName="policy-dashboard-lambda",
81+
InvocationType="RequestResponse",
82+
)
83+
84+
if response["StatusCode"] != 200:
85+
return {"status": "error", "message": "Error invoking Lambda function to refresh dataset."}
86+
87+
if "FunctionError" in response:
88+
return {"status": "error", "message": "Error in Lambda function execution."}
89+
90+
return {"status": "success", "message": "Dataset refreshed successfully!"}

src/repositories/repositories.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,5 +347,9 @@
347347

348348
else:
349349
st.write("No point of contact available.")
350+
351+
if env["org"] == "ONS-Innovation":
352+
st.write("Points of contact are not available for ONS Innovation repositories.")
353+
350354
else:
351355
st.caption("Select a repository for more information.")

src/utilities.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
import boto3
66
import github_api_toolkit
7-
from datetime import timedelta
7+
from datetime import timedelta, timezone
88
from requests import Response
99
from typing import Tuple
1010

@@ -49,6 +49,7 @@ def get_last_modified(s3: boto3.client, bucket: str, filename: str) -> str | Non
4949
response = s3.head_object(Bucket=bucket, Key=filename)
5050

5151
last_modified = response['LastModified']
52+
last_modified = last_modified.replace(tzinfo=timezone.utc).astimezone(tz=None)
5253

5354
if not last_modified:
5455
return None

0 commit comments

Comments
 (0)