Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,22 @@ jobs:
# by the test service's command.
- name: pytest
run: docker compose -f docker-compose.test.yml run --rm test

frontend:
name: frontend tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: front-end
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "18"
cache: npm
cache-dependency-path: front-end/package-lock.json
- name: install
run: npm ci
# Jest + React Testing Library component/contract tests (jsdom, no browser).
- name: jest
run: npm test -- --ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""add performance indexes on hot tables

Revision ID: 57780bad18dd
Revises: 7955e39062da
Create Date: 2026-06-05 15:07:02.972682

"""
from alembic import op


# revision identifiers, used by Alembic.
revision = '57780bad18dd'
down_revision = '7955e39062da'
branch_labels = None
depends_on = None


def upgrade() -> None:
# Hot-path indexes (see database.models __table_args__ — kept in sync).
# Coverage reads/writes filter kml_data & fabric_data by file_id/location_id;
# tile serving looks up vector_tiles by (mbtiles_id, zoom, column, row); the
# task list filters celerytaskinfo by organization_id.
op.create_index("ix_kml_data_file_id", "kml_data", ["file_id"])
op.create_index("ix_kml_data_location_id", "kml_data", ["location_id"])
op.create_index("ix_fabric_data_file_id", "fabric_data", ["file_id"])
op.create_index("ix_fabric_data_location_id", "fabric_data", ["location_id"])
op.create_index(
"ix_celerytaskinfo_organization_id", "celerytaskinfo", ["organization_id"]
)
op.create_index(
"ix_vector_tiles_lookup",
"vector_tiles",
["mbtiles_id", "zoom_level", "tile_column", "tile_row"],
)


def downgrade() -> None:
op.drop_index("ix_vector_tiles_lookup", table_name="vector_tiles")
op.drop_index("ix_celerytaskinfo_organization_id", table_name="celerytaskinfo")
op.drop_index("ix_fabric_data_location_id", table_name="fabric_data")
op.drop_index("ix_fabric_data_file_id", table_name="fabric_data")
op.drop_index("ix_kml_data_location_id", table_name="kml_data")
op.drop_index("ix_kml_data_file_id", table_name="kml_data")
9 changes: 7 additions & 2 deletions back-end/controllers/database_controller/fabric_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from sqlalchemy import and_, create_engine

from database.models import fabric_data, file
from database.sessions import ScopedSession, Session
from database.sessions import Session
from utils.facts import states
from utils.settings import DATABASE_URL

Expand Down Expand Up @@ -36,7 +36,12 @@ def check_num_records_greater_zero(folderid):


def write_to_db(fileid):
session = ScopedSession()
# Own a plain Session here (not the request-scoped ScopedSession): this runs
# in a Celery task, and under eager execution it shares the web handler's
# thread — grabbing+closing the scoped session there would detach the
# handler's ORM objects mid-request. A fresh Session is also consistent with
# the sibling ops in this module.
session = Session()
with db_lock:
file_record = session.query(file).filter(file.id == fileid).first()
session.close()
Expand Down
19 changes: 19 additions & 0 deletions back-end/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
DateTime,
Float,
ForeignKey,
Index,
Integer,
LargeBinary,
String,
Expand Down Expand Up @@ -46,6 +47,7 @@ class user(Base):

class celerytaskinfo(Base):
__tablename__ = "celerytaskinfo"
__table_args__ = (Index("ix_celerytaskinfo_organization_id", "organization_id"),)

id = Column(Integer, primary_key=True)
task_id = Column(String(36), nullable=False)
Expand Down Expand Up @@ -291,6 +293,10 @@ def copy(self, session, new_folder_id=None):

class fabric_data(Base):
__tablename__ = "fabric_data"
__table_args__ = (
Index("ix_fabric_data_file_id", "file_id"),
Index("ix_fabric_data_location_id", "location_id"),
)
location_id = Column(Integer)
address_primary = Column(String)
city = Column(String)
Expand Down Expand Up @@ -336,6 +342,10 @@ class fabric_data_temp(Base):

class kml_data(Base):
__tablename__ = "kml_data"
__table_args__ = (
Index("ix_kml_data_file_id", "file_id"),
Index("ix_kml_data_location_id", "location_id"),
)

id = Column(Integer, primary_key=True, autoincrement=True)
location_id = Column(Integer)
Expand Down Expand Up @@ -391,6 +401,15 @@ def copy(self, session, new_folder_id):

class vector_tiles(Base):
__tablename__ = "vector_tiles"
__table_args__ = (
Index(
"ix_vector_tiles_lookup",
"mbtiles_id",
"zoom_level",
"tile_column",
"tile_row",
),
)
id = Column(Integer, primary_key=True, autoincrement=True)
zoom_level = Column(Integer)
tile_column = Column(Integer)
Expand Down
14 changes: 14 additions & 0 deletions back-end/database/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)


def get_session():
"""Request-scoped session for web handlers.

Returns the same session throughout a single request; its lifecycle
(rollback of anything uncommitted + close) is handled automatically by the
``teardown_appcontext`` registered in ``utils.flask_app``. Handlers must NOT
call ``.close()`` on it — just ``.commit()`` when they mean to persist.

NOT for Celery tasks: those run without a Flask app context and manage their
own ``Session()`` instances (see ``celery_tasks``).
"""
return ScopedSession()


def init_db():
"""Create tables if the schema isn't present yet.

Expand Down
Loading
Loading