Skip to content

Commit fccef95

Browse files
Merge latest upstream changes for RabbitMQ credential fix
2 parents 307364b + d289de1 commit fccef95

11 files changed

Lines changed: 989 additions & 183 deletions

File tree

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
## Join the Community
44
We have a public Slack channel in the CHAOSS workspace, as well as public meetings.
55

6-
We encourage all contributors to join the [CHAOSS Slack workspace](https://chaoss.community/kb-getting-started/) and participate in the `#wg-collectoss-8knot` channel. Our meeting tumes are kept up to date in the Software section of the [CHAOSS Calendar](https://chaoss.community/chaoss-calendar/). We recommend subscribing to the CHAOSS Software calendar so you can automatically stay up to date with any schedule or timezone changes. If you can't attend these meetings, they are also recorded and made available on the [CHAOSS YouTube](https://www.youtube.com/@CHAOSStube).
6+
We encourage all contributors to join the [CHAOSS Slack workspace](https://chaoss.community/kb-getting-started/) and participate in the `#wg-collectoss-8knot` channel. Our meeting times are kept up to date in the Software section of the [CHAOSS Calendar](https://chaoss.community/chaoss-calendar/). We recommend subscribing to the CHAOSS Software calendar so you can automatically stay up to date with any schedule or timezone changes. If you can't attend these meetings, they are also recorded and made available on the [CHAOSS YouTube](https://www.youtube.com/@CHAOSStube).
77

88
These resources are a great way to meet the people behind the project, ask questions, get help, participate in discussions, and stay updated on community meetings and planning. Everyone is welcome, so feel free to introduce yourself and ask for help if you get stuck or feel frustrated with any part of this setup process!
99

collectoss/application/db/materialized_views.py

Lines changed: 824 additions & 0 deletions
Large diffs are not rendered by default.

collectoss/application/schema/alembic/env.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,27 @@
2626
# target_metadata = mymodel.Base.metadata
2727
target_metadata = Base.metadata
2828

29+
# NOTE FOR DEVELOPERS: alembic_utils manages materialized view definitions via
30+
# DROP + CREATE (replace). When a view is replaced:
31+
#
32+
# 1. ALL indexes are destroyed. Manually add CREATE UNIQUE INDEX statements
33+
# after the replace op, using MaterializedView.unique_index_columns from
34+
# the registry (collectoss/application/db/materialized_views.py).
35+
#
36+
# 2. The view is recreated WITH NO DATA. You CANNOT run REFRESH CONCURRENTLY
37+
# immediately — it requires both a unique index and pre-existing data.
38+
# After recreating the index, run a non-concurrent refresh first:
39+
# REFRESH MATERIALIZED VIEW augur_data.<view_name> WITH DATA;
40+
# Only after that will the Celery refresh task's CONCURRENTLY succeed.
41+
#
42+
# WARNING: If MATERIALIZED_VIEWS is ever emptied, autogenerate will propose
43+
# dropping all registered views. Keep the list complete.
44+
from alembic_utils.pg_materialized_view import PGMaterializedView
45+
from alembic_utils.replaceable_entity import register_entities
46+
from collectoss.application.db.materialized_views import MATERIALIZED_VIEWS
47+
_materialized_view_entities = [view.to_pg_view() for view in MATERIALIZED_VIEWS]
48+
register_entities(_materialized_view_entities, entity_types=[PGMaterializedView])
49+
2950
# other values from the config, defined by the needs of env.py,
3051
# can be acquired:
3152
# my_important_option = config.get_main_option("my_important_option")

collectoss/tasks/db/refresh_materialized_views.py

Lines changed: 32 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -3,197 +3,57 @@
33
import sqlalchemy as s
44

55
from collectoss.tasks.init.celery_app import celery_app as celery
6-
from collectoss.application.db.lib import execute_sql
6+
from collectoss.application.db.materialized_views import MATERIALIZED_VIEWS
77
from collectoss.tasks.git.util.facade_worker.facade_worker.config import FacadeHelper
88
from collectoss.tasks.git.util.facade_worker.facade_worker.rebuildcache import invalidate_caches, rebuild_unknown_affiliation_and_web_caches
99

1010

1111
@celery.task(bind=True)
1212
def refresh_materialized_views(self):
1313

14-
#self.logger = SystemLogger("data_collection_jobs").get_logger()
15-
16-
engine = self.app.engine
17-
1814
logger = logging.getLogger(refresh_materialized_views.__name__)
19-
#self.logger = logging.getLogger(refresh_materialized_views.__name__)
20-
21-
mv1_refresh = s.sql.text("""
22-
REFRESH MATERIALIZED VIEW concurrently data.api_get_all_repo_prs with data;
23-
COMMIT;
24-
""")
25-
26-
mv2_refresh = s.sql.text("""
27-
REFRESH MATERIALIZED VIEW concurrently data.api_get_all_repos_commits with data;
28-
COMMIT;
29-
""")
30-
31-
mv3_refresh = s.sql.text("""
32-
REFRESH MATERIALIZED VIEW concurrently data.api_get_all_repos_issues with data;
33-
COMMIT;
34-
""")
35-
36-
mv4_refresh = s.sql.text("""
37-
REFRESH MATERIALIZED VIEW concurrently data.augur_new_contributors with data;
38-
COMMIT;
39-
""")
40-
mv5_refresh = s.sql.text("""
41-
REFRESH MATERIALIZED VIEW concurrently data.explorer_commits_and_committers_daily_count with data;
42-
COMMIT;
43-
""")
44-
45-
mv6_refresh = s.sql.text("""
46-
REFRESH MATERIALIZED VIEW concurrently data.explorer_new_contributors with data;
47-
COMMIT;
48-
""")
49-
50-
mv7_refresh = s.sql.text("""
51-
REFRESH MATERIALIZED VIEW concurrently data.explorer_entry_list with data;
52-
COMMIT;
53-
""")
54-
55-
mv8_refresh = s.sql.text("""
56-
57-
REFRESH MATERIALIZED VIEW concurrently data.explorer_contributor_actions with data;
58-
COMMIT;
59-
""")
60-
61-
mv9_refresh = s.sql.text("""
62-
63-
REFRESH MATERIALIZED VIEW concurrently data.explorer_user_repos with data;
64-
COMMIT;
65-
""")
66-
67-
mv10_refresh = s.sql.text("""
68-
69-
REFRESH MATERIALIZED VIEW concurrently data.explorer_pr_response_times with data;
70-
COMMIT;
71-
""")
72-
73-
mv11_refresh = s.sql.text("""
74-
75-
REFRESH MATERIALIZED VIEW concurrently data.explorer_pr_assignments with data;
76-
COMMIT;
77-
""")
78-
79-
mv12_refresh = s.sql.text("""
80-
81-
REFRESH MATERIALIZED VIEW concurrently data.explorer_issue_assignments with data;
82-
COMMIT;
83-
""")
84-
85-
mv13_refresh = s.sql.text("""
86-
87-
REFRESH MATERIALIZED VIEW concurrently data.explorer_pr_response with data;
88-
COMMIT;
89-
""")
90-
91-
mv14_refresh = s.sql.text("""
9215

93-
REFRESH MATERIALIZED VIEW concurrently data.explorer_repo_languages with data;
94-
COMMIT;
95-
""")
96-
97-
try:
98-
execute_sql(mv1_refresh)
99-
except Exception as e:
100-
logger.info(f"error is {e}")
101-
pass
102-
103-
try:
104-
execute_sql(mv2_refresh)
105-
except Exception as e:
106-
logger.info(f"error is {e}")
107-
pass
108-
109-
try:
110-
execute_sql(mv3_refresh)
111-
except Exception as e:
112-
logger.info(f"error is {e}")
113-
pass
114-
115-
try:
116-
execute_sql(mv4_refresh)
117-
except Exception as e:
118-
logger.info(f"error is {e}")
119-
pass
120-
121-
try:
122-
execute_sql(mv5_refresh)
123-
except Exception as e:
124-
logger.info(f"error is {e}")
125-
pass
126-
127-
try:
128-
execute_sql(mv6_refresh)
129-
except Exception as e:
130-
logger.info(f"error is {e}")
131-
pass
132-
133-
try:
134-
execute_sql(mv7_refresh)
135-
except Exception as e:
136-
logger.info(f"error is {e}")
137-
pass
138-
139-
try:
140-
execute_sql(mv8_refresh)
141-
except Exception as e:
142-
logger.info(f"error is {e}")
143-
pass
144-
145-
try:
146-
execute_sql(mv9_refresh)
147-
except Exception as e:
148-
logger.info(f"error is {e}")
149-
pass
150-
151-
try:
152-
execute_sql(mv10_refresh)
153-
except Exception as e:
154-
logger.info(f"error is {e}")
155-
pass
156-
157-
try:
158-
execute_sql(mv11_refresh)
159-
except Exception as e:
160-
logger.info(f"error is {e}")
161-
pass
162-
163-
try:
164-
execute_sql(mv12_refresh)
165-
except Exception as e:
166-
logger.info(f"error is {e}")
167-
pass
168-
169-
try:
170-
execute_sql(mv13_refresh)
171-
except Exception as e:
172-
logger.info(f"error is {e}")
173-
pass
174-
175-
try:
176-
execute_sql(mv14_refresh)
177-
except Exception as e:
178-
logger.info(f"error is {e}")
179-
pass
16+
# REFRESH MATERIALIZED VIEW CONCURRENTLY cannot run inside a transaction
17+
# block, so we use an autocommit connection rather than execute_sql().
18+
failed_views = []
19+
with self.app.engine.connect().execution_options(isolation_level="AUTOCOMMIT") as conn:
20+
for view in MATERIALIZED_VIEWS:
21+
logger.info(f"Refreshing materialized view: {view.fqn}")
22+
23+
if len(view.unique_index_columns) > 0:
24+
try:
25+
conn.execute(s.sql.text(view.refresh_sql(concurrently=True)))
26+
continue
27+
except Exception as e:
28+
logger.warning(f"Concurrent refresh failed for {view.fqn}, trying non-concurrent: {e}")
29+
30+
try:
31+
conn.execute(s.sql.text(view.refresh_sql(concurrently=False)))
32+
except Exception as e2:
33+
logger.error(f"Non-concurrent refresh failed for {view.fqn}: {e2}")
34+
failed_views.append(view.fqn)
35+
36+
if failed_views:
37+
raise RuntimeError(
38+
f"{len(failed_views)} materialized view(s) failed to refresh: {failed_views}"
39+
)
18040

18141
#Now refresh facade tables
182-
#Use this class to get all the settings and
42+
#Use this class to get all the settings and
18343
#utility functions for facade
18444
facade_helper = FacadeHelper(logger)
18545

18646
if facade_helper.nuke_stored_affiliations:
18747
logger.error("Nuke stored affiliations is deprecated!")
188-
# deprecated because the UI component of facade where affiliations would be
189-
# nuked upon change no longer exists, and this information can easily be derived
48+
# deprecated because the UI component of facade where affiliations would be
49+
# nuked upon change no longer exists, and this information can easily be derived
19050
# from queries and materialized views in the current version of CollectOSS.
19151
# This method is also a major performance bottleneck with little value.
192-
52+
19353
if not facade_helper.limited_run or (facade_helper.limited_run and facade_helper.fix_affiliations):
19454
logger.error("Fill empty affiliations is deprecated!")
195-
# deprecated because the UI component of facade where affiliations would need
196-
# to be fixed upon change no longer exists, and this information can easily be derived
55+
# deprecated because the UI component of facade where affiliations would need
56+
# to be fixed upon change no longer exists, and this information can easily be derived
19757
# from queries and materialized views in the current version of CollectOSS.
19858
# This method is also a major performance bottleneck with little value.
19959

@@ -202,13 +62,9 @@ def refresh_materialized_views(self):
20262
invalidate_caches(facade_helper)
20363
except Exception as e:
20464
logger.info(f"error is {e}")
205-
65+
20666
if not facade_helper.limited_run or (facade_helper.limited_run and facade_helper.rebuild_caches):
20767
try:
20868
rebuild_unknown_affiliation_and_web_caches(facade_helper)
20969
except Exception as e:
21070
logger.info(f"error is {e}")
211-
212-
213-
214-

collectoss/tasks/github/util/github_data_access.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def make_request(self, url, method="GET", timeout=100):
169169
raise UrlNotFoundException(f"Could not find {url}")
170170

171171
if response.status_code == 401:
172-
raise NotAuthorizedException(f"Could not authorize with the github api")
172+
raise NotAuthorizedException(f"Could not authorize with the github api using key: {mask_key(self.key)}")
173173

174174
if response.status_code == 410:
175175
response_msg = response.json().get("message")

collectoss/templates/admin-dashboard.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
<hr>
4646
<div class="dropdown">
4747
<a href="{{ url_for('root') }}" class="d-flex align-items-center text-white text-decoration-none">
48-
<img src="{{ url_for('logo') }}" alt="CollectOSS logo" height="32" class="rounded-circle me-2">
48+
<img src="{{ url_for('logo') }}" alt="CollectOSS logo" height="32" class="rounded me-2">
4949
</a>
5050
{# Reserved for future use
5151
<ul class="dropdown-menu dropdown-menu-dark text-small shadow" aria-labelledby="dropdownUser1">

collectoss/templates/settings.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
class="d-flex flex-md-column text-truncate flex-row flex-grow-1 align-items-center align-items-md-start px-3 py-2 text-white">
4141
<a href="{{ url_for('root') }}"
4242
class="d-flex align-items-around mw-100 text-white text-decoration-none">
43-
<img src="{{ url_for('logo') }}" alt="CollectOSS logo" height="40" class="rounded-circle me-2">
43+
<img src="{{ url_for('logo') }}" alt="CollectOSS logo" height="40" class="rounded me-2">
4444
</a>
4545
<ul class="nav nav-pills flex-md-column flex-row flex-nowrap flex-shrink-1 flex-md-grow-0 flex-grow-1 mb-md-auto mb-0 justify-content-center align-items-center align-items-md-start"
4646
id="menu">

collectoss/util/keys.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
def mask_key(key: str, first: int = 6, last: int = 3, stars: int = 6) -> str:
22
"""Mask key except for the first and last few characters."""
3-
if not isinstance(key, str) or len(key) <= (first + last):
4-
return "*" * stars
5-
return f"{key[:first]}{'*' * stars}{key[-last:]}"
3+
if key is None:
4+
return None
5+
6+
if isinstance(key, str):
7+
if key == "":
8+
return "*" * stars + f" Type: empty string"
9+
if len(key) <= (first + last):
10+
return "*" * stars
11+
return f"{key[:first]}{'*' * stars}{key[-last:]}"
12+
else:
13+
return "*" * stars + f" Type: {str(type(key))}"

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ classifiers = [
1919
]
2020
dependencies = [
2121
"alembic>=1.17.1",
22+
"alembic-utils==0.8.8",
2223
"Beaker==1.11.0",
2324
"boto3==1.17.57",
2425
"bs4==0.0.1",
@@ -148,6 +149,7 @@ testpaths = [
148149
"tests/test_application/test_cli/test_csv_utils.py",
149150
"tests/test_tasks/test_task_utilities/test_util/",
150151
"tests/test_application/test_db/test_timestamp_utils.py",
152+
"tests/test_util/test_keys.py",
151153
# "tests/test_routes", # runs, but needs a fixture for connecting to the web interface of Augur
152154
# "tests/test_metrics",
153155
# "tests/test_tasks",

tests/test_util/test_keys.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import pytest
2+
3+
from collectoss.util.keys import mask_key
4+
5+
6+
def test_none():
7+
assert mask_key(None) is None
8+
9+
def test_empty_string():
10+
assert mask_key("") == "*" * 6 + " Type: empty string"
11+
12+
def test_non_string():
13+
result = mask_key(12345)
14+
assert "*" in result
15+
assert str(type(12345)) in result
16+
17+
18+
def test_non_string_list():
19+
result = mask_key([1, 2, 3])
20+
assert "*" in result
21+
assert str(type([])) in result
22+
23+
24+
def test_short_string():
25+
assert mask_key("short") == f"******"
26+
27+
28+
def test_long_string_masked_correctly():
29+
key = "ghp_abcdefghij" # 14 chars → first 6 + 6 stars + last 3
30+
assert mask_key(key) == "ghp_ab******hij"
31+
32+
33+
def test_exactly_one_over_boundary():
34+
# 10 chars: first 6 + 6 stars + last 3
35+
key = "1234567890"
36+
assert mask_key(key) == "123456******890"
37+
38+
39+
def test_default_star_count_is_six():
40+
key = "abcdefghijk" # 11 chars
41+
result = mask_key(key)
42+
middle = result[6:-3]
43+
assert middle == "******"
44+
45+
46+
def test_custom_first_last_stars():
47+
# first=2, last=2, stars=3 → boundary=4; "hello" is 5 chars → masked
48+
assert mask_key("hello", first=2, last=2, stars=3) == "he***lo"
49+
50+
51+
def test_custom_stars_count_in_output():
52+
key = "abcdefghijk"
53+
result = mask_key(key, stars=10)
54+
assert result == "abcdef**********ijk"
55+
56+

0 commit comments

Comments
 (0)