Skip to content

Commit afa5fe0

Browse files
Feature/dep94: Added authoring feedback functionality, fixed survey section (#2745)
* feature/dep94: Added feedback functionality, fixed survey section. * feature/dep94: Refactored code as per Sonarqube Cloud. * feature/dep94: Removed refs to react-router-dom, fixed report settings component and unit test, fixed Python linting. * feature/dep94: Additional Python linting. * feature/dep94: Python version typing resolution, light front-end unit test string conversion change. * Hotfix: engagement tile display * Remove vault secret placeholder as it was causing CD failure * Hotfix: remove duplicate env-reader package from API dev dependencies * feature/dep94: Made engagement_id nullable in Survey table, changed no content response for survey get, optimized survey loader, removed selected_survey_id from factory scenarios in API. --------- Co-authored-by: NatSquared <nat.k.weiland@gmail.com>
1 parent 2877224 commit afa5fe0

48 files changed

Lines changed: 1291 additions & 1040 deletions

Some content is hidden

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

CHANGELOG.MD

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
## February 18, 2026
2+
3+
- Admin authoring - provide feedback section [🎟️ DEP-94](https://citz-gdx.atlassian.net/browse/DEP-94)
4+
- Modified existing "Provide Feedback" section to reflect needed controls (Autocomplete survey selector), as per Steve
5+
- Hooked up all form controls and created necessary columns in Engagement models/schemas to accomodate data
6+
- Got validation working and added navigate away message when form is dirty
7+
- Added a confirm modal if engagement is published and user is changing the survey
8+
- Fixed the survey section
9+
- Got the survey creation wizard working
10+
- Added mandatory field to select connected engagement when creating a new survey
11+
- Fixed invalid calls for survey setting data before it exists
12+
- Modified the survey loader to deliver data instead of promises due to complexity of survey data needs
13+
- Fixed form validation that was misfiring
14+
- Replaced many deprecated components (not exhaustive)
15+
116
## February 10, 2026
217

318
- **Chore** Move new look route from `/new-look` to base path `/` [🎟️ DEP-217](https://citz-gdx.atlassian.net/browse/DEP-217)

docs/MET_database_ERD.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ erDiagram
2020
timestamp scheduled_date
2121
integer tenant_id FK "The id from tenant"
2222
boolean is_internal
23+
string feedback_heading
24+
json feedback_body
25+
string selected_survey_id
2326
}
2427
survey {
2528
integer id PK
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Added feedback heading and feedback body columns to engagement table.
2+
3+
Revision ID: 6261697b7a20
4+
Revises: 539c56c61127
5+
Create Date: 2026-02-06 14:12:26.770307
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
from sqlalchemy.dialects import postgresql
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '6261697b7a20'
14+
down_revision = '539c56c61127'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
op.add_column('engagement', sa.Column('feedback_heading', sa.String(length=60), nullable=True))
21+
op.add_column('engagement', sa.Column('feedback_body', postgresql.JSON(astext_type=sa.Text()), nullable=True))
22+
23+
24+
def downgrade():
25+
op.drop_column('engagement', 'feedback_body')
26+
op.drop_column('engagement', 'feedback_heading')
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Added selected survey column to engagement, added relationship in survey model, modified engagement surveys.
2+
3+
Revision ID: 6b80c03ea472
4+
Revises: 6261697b7a20
5+
Create Date: 2026-02-11 18:05:03.488297
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '6b80c03ea472'
14+
down_revision = '6261697b7a20'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
op.add_column('engagement', sa.Column('selected_survey_id', sa.Integer(), nullable=True))
21+
op.create_foreign_key('engagement_selected_survey_fk', 'engagement', 'survey', ['selected_survey_id'], ['id'], ondelete='SET NULL')
22+
op.alter_column('survey', 'engagement_id',
23+
existing_type=sa.INTEGER(),
24+
nullable=False)
25+
26+
def downgrade():
27+
op.alter_column('survey', 'engagement_id',
28+
existing_type=sa.INTEGER(),
29+
nullable=True)
30+
op.drop_constraint('engagement_selected_survey_fk', 'engagement', type_='foreignkey')
31+
op.drop_column('engagement', 'selected_survey_id')
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Making engagement_id column in Survey table nullable again.
2+
3+
Revision ID: e65303810f54
4+
Revises: 6b80c03ea472
5+
Create Date: 2026-02-19 17:55:07.103147
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = 'e65303810f54'
14+
down_revision = '6b80c03ea472'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
op.alter_column('survey', 'engagement_id',
21+
existing_type=sa.INTEGER(),
22+
nullable=True)
23+
24+
25+
def downgrade():
26+
op.alter_column('survey', 'engagement_id',
27+
existing_type=sa.INTEGER(),
28+
nullable=False)

met-api/sample.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ JWT_OIDC_TEST_ALGORITHMS=RS256
114114

115115
# Test database settings
116116
# If unset, uses the same settings as the main database
117+
# Warning: Tests currently drop the public schema in the DB so do not use your primary db here.
117118
DATABASE_TEST_USERNAME=
118119
DATABASE_TEST_PASSWORD=
119120
DATABASE_TEST_NAME=

met-api/src/met_api/models/engagement.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,20 @@ class Engagement(BaseModel):
4848
published_date = db.Column(db.DateTime, nullable=True)
4949
scheduled_date = db.Column(db.DateTime, nullable=True)
5050
banner_filename = db.Column(db.String(), unique=False, nullable=True)
51+
feedback_heading = db.Column(db.String(60), unique=False, nullable=True)
52+
feedback_body = db.Column(JSON, unique=False, nullable=True)
53+
selected_survey_id = db.Column(db.Integer, ForeignKey(
54+
'survey.id', ondelete='SET NULL'
55+
), nullable=True)
5156
surveys = db.relationship(
52-
'Survey', backref='engagement', cascade='all, delete')
57+
'Survey',
58+
back_populates='engagement',
59+
primaryjoin='Survey.engagement_id == Engagement.id',
60+
foreign_keys='Survey.engagement_id',
61+
cascade='all, delete-orphan',
62+
passive_deletes=True,
63+
lazy='selectin',
64+
)
5365
status_block = db.relationship(
5466
'EngagementStatusBlock', backref='engagement')
5567
tenant_id = db.Column(
@@ -135,6 +147,9 @@ def update_engagement(cls, engagement: EngagementSchema) -> Engagement:
135147
'start_date': engagement.get('start_date', None),
136148
'end_date': engagement.get('end_date', None),
137149
'status_id': engagement.get('status_id', None),
150+
'feedback_heading': engagement.get('feedback_heading', None),
151+
'feedback_body': engagement.get('feedback_body', None),
152+
'selected_survey_id': engagement.get('selected_survey_id', None),
138153
# to fix the bug with UI not passing published date always.
139154
# Defaulting to existing
140155
'published_date': engagement.get(

met-api/src/met_api/models/survey.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,35 @@ class Survey(BaseModel): # pylint: disable=too-few-public-methods
3131
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
3232
name = db.Column(db.String(50), index=True)
3333
form_json = db.Column(postgresql.JSONB(astext_type=db.Text()), nullable=False, server_default='{}')
34-
engagement_id = db.Column(db.Integer, ForeignKey('engagement.id', ondelete='CASCADE'))
34+
engagement_id = db.Column(db.Integer, ForeignKey('engagement.id', ondelete='CASCADE'), nullable=True)
3535
comments = db.relationship('Comment', backref='survey', cascade='all, delete')
3636
submissions = db.relationship('Submission', backref='survey', cascade='all, delete')
3737
# Survey templates might not need tenant id
3838
tenant_id = db.Column(db.Integer, db.ForeignKey('tenant.id'), nullable=True)
3939
is_hidden = db.Column(db.Boolean, nullable=False)
4040
is_template = db.Column(db.Boolean, nullable=False)
4141
generate_dashboard = db.Column(db.Boolean, default=True)
42+
engagement = db.relationship(
43+
'Engagement',
44+
back_populates='surveys',
45+
foreign_keys=[engagement_id]
46+
)
47+
48+
@classmethod
49+
def get_survey(cls, survey_id) -> Survey:
50+
"""Get a single survey by ID, status agnostic."""
51+
survey: Survey = db.session.query(Survey).filter_by(id=survey_id) \
52+
.join(Survey.engagement) \
53+
.join(EngagementStatus) \
54+
.first()
55+
return survey
4256

4357
@classmethod
4458
def get_open(cls, survey_id) -> Survey:
4559
"""Get an open survey."""
4660
now = datetime.now().date() # Get the current date without the timestamp
4761
survey: Survey = db.session.query(Survey).filter_by(id=survey_id) \
48-
.join(Engagement) \
62+
.join(Survey.engagement) \
4963
.filter_by(status_id=Status.Published.value) \
5064
.filter(and_(func.date(Engagement.start_date) <= now, func.date(Engagement.end_date) >= now)) \
5165
.join(EngagementStatus) \
@@ -56,7 +70,18 @@ def get_open(cls, survey_id) -> Survey:
5670
def get_surveys_paginated(cls, pagination_options: PaginationOptions,
5771
survey_search_options: SurveySearchOptions):
5872
"""Get surveys paginated."""
59-
query = db.session.query(Survey).join(Engagement, isouter=True).join(EngagementStatus, isouter=True)
73+
query = (
74+
db.session.query(Survey)
75+
.outerjoin(
76+
Engagement,
77+
Survey.engagement_id == Engagement.id,
78+
)
79+
.outerjoin(
80+
EngagementStatus,
81+
Engagement.status_id == EngagementStatus.id,
82+
)
83+
)
84+
6085
query = cls._add_tenant_filter(query)
6186

6287
query = cls.filter_by_search_options(survey_search_options, query)

met-api/src/met_api/resources/engagement.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,13 @@ def patch():
157157
requestjson['updated_by'] = user_id
158158

159159
engagement_schema = EngagementSchema()
160-
engagement_schema.load(requestjson, partial=True)
161-
engagement = EngagementService().edit_engagement(requestjson)
160+
payload = engagement_schema.load(requestjson, partial=True)
161+
engagement = EngagementService().edit_engagement(payload)
162162

163163
return engagement_schema.dump(engagement), HTTPStatus.OK
164164
except KeyError as err:
165165
return str(err), HTTPStatus.INTERNAL_SERVER_ERROR
166166
except ValueError as err:
167167
return str(err), HTTPStatus.INTERNAL_SERVER_ERROR
168168
except ValidationError as err:
169-
return str(err.messages), HTTPStatus.INTERNAL_SERVER_ERROR
169+
return str(err.messages), HTTPStatus.BAD_REQUEST

met-api/src/met_api/resources/report_setting.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def get(survey_id):
4747
if report_setting:
4848
return jsonify(report_setting), HTTPStatus.OK
4949

50-
return 'Report setting was not found', HTTPStatus.NOT_FOUND
50+
return 'Report setting was not found', HTTPStatus.NO_CONTENT
5151
except KeyError:
5252
return 'Report setting was not found', HTTPStatus.INTERNAL_SERVER_ERROR
5353
except ValueError as err:

0 commit comments

Comments
 (0)