Skip to content

Commit dbb2bfe

Browse files
authored
Merge pull request #66 from filips123/sentry-and-performance-fixes
2 parents 37a1e13 + a4c2e17 commit dbb2bfe

File tree

9 files changed

+146
-43
lines changed

9 files changed

+146
-43
lines changed

.github/workflows/deploy.yaml

+11-10
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,6 @@ jobs:
5858
with:
5959
node-version: "18"
6060

61-
- name: Install Sentry CLI
62-
run: npm install -g @sentry/cli
63-
6461
- name: Prepare the SSH configuration
6562
run: |
6663
mkdir ~/.ssh
@@ -98,6 +95,11 @@ jobs:
9895
yarn version --no-git-tag-version --new-version "$VERSION"
9996
yarn install --frozen-lockfile
10097
yarn build
98+
env:
99+
SENTRY_UPLOAD_SOURCEMAPS: true
100+
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
101+
SENTRY_PROJECT: ${{ secrets.SENTRY_FRONTEND_PROJECT }}
102+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
101103

102104
- name: Deploy the backend
103105
run: |
@@ -117,13 +119,12 @@ jobs:
117119
- name: Reload the server
118120
run: ssh urnik "sudo systemctl reload apache2.service"
119121

120-
- name: Create the Sentry release
121-
run: |
122-
sentry-cli releases new "$VERSION" -p "${{ secrets.SENTRY_BACKEND_PROJECT }}" -p "${{ secrets.SENTRY_FRONTEND_PROJECT }}"
123-
sentry-cli releases -p "${{ secrets.SENTRY_FRONTEND_PROJECT }}" files "$VERSION" upload-sourcemaps website/dist/
124-
sentry-cli releases set-commits "$VERSION" --auto
125-
sentry-cli releases finalize "$VERSION"
126-
sentry-cli releases deploys "$VERSION" new -e "${{ secrets.SENTRY_ENVIRONMENT }}"
122+
- name: Create a Sentry release
123+
uses: getsentry/action-release@v1
127124
env:
128125
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
129126
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
127+
with:
128+
projects: ${{ secrets.SENTRY_BACKEND_PROJECT }} ${{ secrets.SENTRY_FRONTEND_PROJECT }}
129+
version: ${{ env.VERSION }}
130+
environment: production

API/gimvicurnik/__init__.py

+9
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def configure_sentry(self) -> None:
101101
from sentry_sdk.integrations.flask import FlaskIntegration
102102
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
103103
from sentry_sdk.integrations.pure_eval import PureEvalIntegration
104+
from sentry_sdk.scrubber import EventScrubber, DEFAULT_DENYLIST
104105

105106
sentry_config = self.config.sentry
106107

@@ -112,6 +113,13 @@ def configure_sentry(self) -> None:
112113
environment = os.environ.get("GIMVICURNIK_ENV", "production")
113114
release = sentry_config.releasePrefix + version + sentry_config.releaseSuffix
114115

116+
# Remove IP-related elements from the default denylist
117+
if sentry_config.collectIPs:
118+
allowlist = {"ip_address", "remote_addr", "x_forwarded_for", "x_real_ip"}
119+
denylist = list(set(DEFAULT_DENYLIST) - allowlist)
120+
else:
121+
denylist = DEFAULT_DENYLIST
122+
115123
# Create custom traces sampler so different traces can be configured separately
116124
def _sentry_traces_sampler(context: dict[str, Any]) -> float | int | bool:
117125
if context["transaction_context"]["op"] == "command":
@@ -136,6 +144,7 @@ def _sentry_profiler_sampler(context: dict[str, Any]) -> float | int | bool:
136144
max_breadcrumbs=sentry_config.maxBreadcrumbs,
137145
traces_sampler=_sentry_traces_sampler,
138146
profiles_sampler=_sentry_profiler_sampler,
147+
event_scrubber=EventScrubber(denylist=denylist),
139148
integrations=[
140149
FlaskIntegration(transaction_style="url"),
141150
SqlalchemyIntegration(),

API/gimvicurnik/blueprints/calendar.py

+12-8
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
if typing.TYPE_CHECKING:
1616
from typing import Any, Iterator
1717
from flask import Blueprint, Response
18-
from sqlalchemy.orm.query import Query
18+
from sqlalchemy.orm.query import RowReturningQuery
1919
from ..config import Config, ConfigLessonTime
2020

2121

@@ -66,7 +66,7 @@ def create_school_calendar(
6666
span.set_tag("event.time", subject["time"])
6767
span.set_data("event.source", subject)
6868

69-
logger.info(
69+
logger.debug(
7070
"Preparing iCalendar event",
7171
extra={"type": "timetable", "source": subject},
7272
)
@@ -123,7 +123,7 @@ def create_school_calendar(
123123
span.set_tag("event.time", subject["time"])
124124
span.set_data("event.source", subject)
125125

126-
logger.info(
126+
logger.debug(
127127
"Preparing iCalendar event",
128128
extra={"type": "substitution", "source": subject},
129129
)
@@ -183,18 +183,22 @@ def create_school_calendar(
183183

184184

185185
@with_span(op="generate")
186-
def create_schedule_calendar(query: Query[LunchSchedule], name: str, url: str) -> Response:
186+
def create_schedule_calendar(
187+
query: RowReturningQuery[tuple[LunchSchedule, str]],
188+
name: str,
189+
url: str,
190+
) -> Response:
187191
logger = logging.getLogger(__name__)
188192
calendar = create_calendar(name, url)
189193

190-
for model in query:
194+
for model, classname in query:
191195
with start_span(op="event") as span:
192196
span.set_tag("event.type", "lunch-schedule")
193197
span.set_tag("event.date", model.date)
194198
span.set_tag("event.time", model.time)
195199
span.set_data("event.source", model)
196200

197-
logger.info(
201+
logger.debug(
198202
"Preparing iCalendar event",
199203
extra={"type": "lunch-schedule", "source": model},
200204
)
@@ -215,7 +219,7 @@ def create_schedule_calendar(query: Query[LunchSchedule], name: str, url: str) -
215219
(
216220
str(model.date)
217221
+ str(model.time)
218-
+ str(model.class_.name)
222+
+ str(classname)
219223
+ str(model.location)
220224
+ str(model.notes)
221225
).encode("utf-8")
@@ -281,7 +285,7 @@ def get_substitutions_calendar_for_classes(classes: list[str]) -> Response:
281285
@bp.route("/calendar/schedules/<list:classes>")
282286
def get_schedules_calendar_for_classes(classes: list[str]) -> Response:
283287
return create_schedule_calendar(
284-
Session.query(LunchSchedule)
288+
Session.query(LunchSchedule, Class.name)
285289
.join(Class)
286290
.filter(Class.name.in_(classes))
287291
.order_by(LunchSchedule.time, LunchSchedule.class_),

API/gimvicurnik/blueprints/schedule.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ def routes(cls, bp: Blueprint, config: Config) -> None:
2121
def get_lunch_schedule(date: datetime.date) -> list[dict[str, Any]]:
2222
return [
2323
{
24-
"class": model.class_.name,
25-
"date": model.date.strftime("%Y-%m-%d"),
26-
"time": model.time.strftime("%H:%M"),
27-
"location": model.location,
28-
"notes": model.notes,
24+
"class": model[1],
25+
"date": model[0].date.strftime("%Y-%m-%d"),
26+
"time": model[0].time.strftime("%H:%M"),
27+
"location": model[0].location,
28+
"notes": model[0].notes,
2929
}
3030
for model in (
31-
Session.query(LunchSchedule)
31+
Session.query(LunchSchedule, Class.name)
3232
.join(Class)
3333
.filter(LunchSchedule.date == date)
3434
.order_by(LunchSchedule.time, LunchSchedule.class_)
@@ -39,14 +39,14 @@ def get_lunch_schedule(date: datetime.date) -> list[dict[str, Any]]:
3939
def get_lunch_schedule_for_classes(date: datetime.date, classes: list[str]) -> list[dict[str, Any]]:
4040
return [
4141
{
42-
"class": model.class_.name,
43-
"date": model.date.strftime("%Y-%m-%d"),
44-
"time": model.time.strftime("%H:%M"),
45-
"location": model.location,
46-
"notes": model.notes,
42+
"class": model[1],
43+
"date": model[0].date.strftime("%Y-%m-%d"),
44+
"time": model[0].time.strftime("%H:%M"),
45+
"location": model[0].location,
46+
"notes": model[0].notes,
4747
}
4848
for model in (
49-
Session.query(LunchSchedule)
49+
Session.query(LunchSchedule, Class.name)
5050
.join(Class)
5151
.filter(LunchSchedule.date == date, Class.name.in_(classes))
5252
.order_by(LunchSchedule.time, LunchSchedule.class_)

API/gimvicurnik/database/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def get_lessons(
100100
query = (
101101
Session.query(Lesson, Class.name, Teacher.name, Classroom.name)
102102
.join(Class)
103-
.join(Teacher)
103+
.join(Teacher, isouter=True)
104104
.join(Classroom, isouter=True)
105105
.order_by(Lesson.day, Lesson.time)
106106
)

API/gimvicurnik/updaters/eclassroom.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ def _parse_substitutions(self, tables: Tables, effective: date) -> None:
399399
# Parse tables into substitutions
400400
for table in tables:
401401
for row0 in table:
402+
# We use different variable name here, otherwise mypy complains
402403
row = [column.replace("\n", " ").strip() if column else "" for column in row0]
403404

404405
# Get parser type
@@ -569,9 +570,12 @@ def _parse_substitutions(self, tables: Tables, effective: date) -> None:
569570
# Deduplicate substitutions
570571
substitutions = [dict(subs2) for subs2 in {tuple(subs1.items()) for subs1 in substitutions}]
571572

572-
# Store substitutions to a database
573+
# Remove old substitutions from a database
573574
self.session.query(Substitution).filter(Substitution.date == effective).delete()
574-
self.session.execute(insert(Substitution), substitutions)
575+
576+
# Store new substitutions to a database
577+
if substitutions:
578+
self.session.execute(insert(Substitution), substitutions)
575579

576580
def _parse_lunch_schedule(self, tables: Tables, effective: date) -> None:
577581
"""Parse the lunch schedule document."""

website/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
},
3838
"devDependencies": {
3939
"@rushstack/eslint-patch": "^1.2.0",
40+
"@sentry/webpack-plugin": "^1.20.1",
4041
"@types/pulltorefreshjs": "^0.1.5",
4142
"@typescript-eslint/eslint-plugin": "^5.49.1",
4243
"@typescript-eslint/parser": "^5.49.1",

website/vue.config.js

+39
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
process.env.VUE_APP_VERSION = require('./package.json').version
22

3+
const path = require('path')
4+
5+
const SentryWebpackPlugin = require('@sentry/webpack-plugin')
36
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin')
47
const { defineConfig } = require('@vue/cli-service')
58

@@ -77,5 +80,41 @@ module.exports = defineConfig({
7780
return 'script'
7881
}
7982
}])
83+
},
84+
85+
configureWebpack: (config) => {
86+
config.output.devtoolFallbackModuleFilenameTemplate = 'webpack:///[resource-path]?[hash]'
87+
88+
config.output.devtoolModuleFilenameTemplate = info => {
89+
const isVue = info.resourcePath.match(/\.vue$/)
90+
const isScript = info.query.match(/type=script/)
91+
const hasModuleId = info.moduleId !== ''
92+
93+
const resourcePath = path.normalize(info.resourcePath).replaceAll('\\', '/')
94+
95+
if (isVue && (!isScript || hasModuleId)) {
96+
// Detect generated files, filter as webpack-generated
97+
return `webpack-generated:///${resourcePath}?${info.hash}`
98+
} else {
99+
// If not generated, filter as webpack
100+
return `webpack:///${resourcePath}`
101+
}
102+
}
103+
104+
// Upload sourcemaps to Sentry if enabled
105+
if (process.env.SENTRY_UPLOAD_SOURCEMAPS === 'true') {
106+
const releasePrefix = process.env.VUE_APP_SENTRY_RELEASE_PREFIX || ''
107+
const releaseSuffix = process.env.VUE_APP_SENTRY_RELEASE_SUFFIX || ''
108+
const releaseVersion = releasePrefix + process.env.VUE_APP_VERSION + releaseSuffix
109+
110+
config.plugins.push(new SentryWebpackPlugin({
111+
org: process.env.SENTRY_ORG,
112+
project: process.env.SENTRY_PROJECT,
113+
authToken: process.env.SENTRY_AUTH_TOKEN,
114+
release: releaseVersion,
115+
include: './dist',
116+
finalize: false
117+
}))
118+
}
80119
}
81120
})

0 commit comments

Comments
 (0)