Skip to content

Commit

Permalink
Feature/deseng439: Created Timline Widget and merged with Content fro…
Browse files Browse the repository at this point in the history
…m Main (#2347)

* Add initial version of change log (#2318)

Add a change log to allow developers to log any changes they make to the project.

* Feature/update sample env files (#2320)

* Feature: update sample .env files

* Remove old production .env file

* Update DEVELOPMENT.md to reflect project state

* Update CHANGELOG.md before PR

* Link JIRA ticket # on relevant changes

* Bring bugfixes from main into gdx-dev (#2328)

* Made slug url case insensitive , Fixed bug with wrong query join for submission (#2321)

* Made slug url case insensitive ,
Fixed bug with wrong query join for submission

* removed a couple of if statements

* CSV export made working for multipage wizard surveys (#2322)

---------

Co-authored-by: saravanpa-aot <saravankumar.pa@aot-technologies.com>

* bugfix/deseng421: Changed engagement links so that they open in the same window/tab as opposed to a new one. (#2329)

* Bugfix/deseng413 (#2330)

* bugfix/deseng413: Upgraded BC-Sans font to newest version.

* bugfix/deseng413: Small update to changelog for clarification.

* Feature/deseng415 (#2334)

* feature/deseng415: Added recording of date with feedback submission and displaying the data on admin side.

* feature/deseng415: Fixed feedback schema, removed yup import, fixed change log date.

* bugfix/deseng429: Removed outdated service class. (#2337)

* bugfix/deseng429: Removed outdated service class.

* bugfix/deseng429: Changed version and changelog to match deployments to gdx-main.

* feature/deseng439: Finished the Timeline Widget and linted.

* feature/deseng439: Updated changelog before merge.

* feature/deseng439: Made revisions as per Alex and added sorting to React components.

---------

Co-authored-by: Nat² <nat.weiland@gov.bc.ca>
Co-authored-by: saravanpa-aot <saravankumar.pa@aot-technologies.com>
  • Loading branch information
3 people committed Jan 19, 2024
1 parent c61a148 commit 9ec490d
Show file tree
Hide file tree
Showing 32 changed files with 1,420 additions and 4 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Change Log

All notable changes to this project will be documented in this file.
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/).

## December 28, 2023

> **Feature**: Added the timeline widget. [🎟️DESENG-439](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-439)
## December 11, 2023

Expand Down
2 changes: 1 addition & 1 deletion analytics-api/src/analytics_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
load_dotenv(find_dotenv())


def get_named_config(environment: str | None) -> '_Config':
def get_named_config(environment: 'str | None') -> '_Config':
"""
Retrieve a configuration object by name. Used by the Flask app factory.
Expand Down
27 changes: 27 additions & 0 deletions docs/MET_database_ERD.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,33 @@ erDiagram
string updated_by
}
widget only one to zero or more widget_video : has
widget_timeline {
integer id PK
integer widget_id FK "The id from widget"
integer engagement_id FK "The id from engagement"
string title
string description
timestamp created_date
timestamp updated_date
string created_by
string updated_by
}
widget only one to zero or more widget_timeline : has
timeline_event {
integer id PK
integer widget_id FK "The id from widget"
integer engagement_id FK "The id from engagement"
integer timeline_id FK "The id from timeline"
string description
string time
enum status
integer position
timestamp created_date
timestamp updated_date
string created_by
string updated_by
}
widget only one to zero or more timeline_event : has
widget_documents {
integer id PK
string title
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Added the Timeline Widget.
Revision ID: 3e4dc76a96ab
Revises: 02ff8ecc6b91
Create Date: 2023-12-05 17:04:46.304368
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from met_api.constants.timeline_event_status import TimelineEventStatus

# revision identifiers, used by Alembic.
revision = '3e4dc76a96ab'
down_revision = '02ff8ecc6b91'
branch_labels = None
depends_on = None


def upgrade():
op.create_table('widget_timeline',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('engagement_id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=255), nullable=True),
sa.Column('description', sa.Text(), nullable=True),
sa.ForeignKeyConstraint(['engagement_id'], ['engagement.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
)
op.create_table('timeline_event',
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
sa.Column('engagement_id', sa.Integer(), nullable=False),
sa.Column('timeline_id', sa.Integer(), nullable=False),
sa.Column('status', sa.Enum(TimelineEventStatus), nullable=True),
sa.Column('position', sa.Integer(), nullable=True),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('time', sa.String(length=255), nullable=True),
sa.ForeignKeyConstraint(['engagement_id'], ['engagement.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['timeline_id'], ['widget_timeline.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
)

def downgrade():
op.drop_table('widget_timeline')
op.drop_table('timeline_event')
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Add widget_id to widget_timeline and timeline_event tables
Revision ID: 4114001e1a4c
Revises: c09e77fde608
Create Date: 2023-12-11 15:46:30.773046
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = '4114001e1a4c'
down_revision = 'c09e77fde608'
branch_labels = None
depends_on = None


def upgrade():
op.add_column('widget_timeline', sa.Column('widget_id', sa.Integer()))
op.create_foreign_key('timeline_widget_fk', 'widget_timeline', 'widget', ['widget_id'], ['id'], ondelete='CASCADE')
op.add_column('timeline_event', sa.Column('widget_id', sa.Integer()))
op.create_foreign_key('event_widget_fk', 'timeline_event', 'widget', ['widget_id'], ['id'], ondelete='CASCADE')

def downgrade():
op.drop_column('widget_timeline', 'widget_id')
op.drop_column('timeline_event', 'widget_id')
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Added enum value for Timeline Widget.
Revision ID: c09e77fde608
Revises: 3e4dc76a96ab
Create Date: 2023-12-06 11:46:20.934373
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = 'c09e77fde608'
down_revision = '3e4dc76a96ab'
branch_labels = None
depends_on = None


def upgrade():
op.add_column('timeline_event', sa.Column('created_date', sa.DateTime(), nullable=False))
op.add_column('timeline_event', sa.Column('updated_date', sa.DateTime(), nullable=True))
op.add_column('timeline_event', sa.Column('created_by', sa.String(length=50), nullable=True))
op.add_column('timeline_event', sa.Column('updated_by', sa.String(length=50), nullable=True))

op.add_column('widget_timeline', sa.Column('created_date', sa.DateTime(), nullable=False))
op.add_column('widget_timeline', sa.Column('updated_date', sa.DateTime(), nullable=True))
op.add_column('widget_timeline', sa.Column('created_by', sa.String(length=50), nullable=True))
op.add_column('widget_timeline', sa.Column('updated_by', sa.String(length=50), nullable=True))

widget_type_table = sa.table('widget_type',
sa.Column('id', sa.Integer),
sa.Column('name', sa.String),
sa.Column('description', sa.String))

op.bulk_insert(
widget_type_table,
[
{
'id': 8,
'name': 'CAC Form',
'description': 'Add a CAC Form to your project',
},
{
'id': 9,
'name': 'Timeline',
'description': 'Create a timeline for a series of events',
},
]
)

def downgrade():
op.drop_column('widget_timeline', 'updated_by')
op.drop_column('widget_timeline', 'created_by')
op.drop_column('widget_timeline', 'updated_date')
op.drop_column('widget_timeline', 'created_date')
op.drop_column('timeline_event', 'updated_by')
op.drop_column('timeline_event', 'created_by')
op.drop_column('timeline_event', 'updated_date')
op.drop_column('timeline_event', 'created_date')
op.delete(widget_type_table).filter_by(id=8)
op.delete(widget_type_table).filter_by(id=9)
2 changes: 1 addition & 1 deletion met-api/src/met_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
os.environ = {k: v for k, v in os.environ.items() if v}


def get_named_config(environment: str | None) -> 'Config':
def get_named_config(environment: 'str | None') -> 'Config':
"""
Retrieve a configuration object by name. Used by the Flask app factory.
Expand Down
10 changes: 10 additions & 0 deletions met-api/src/met_api/constants/timeline_event_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Constants of timeline events."""
from enum import IntEnum


class TimelineEventStatus(IntEnum):
"""Enum of timeline event status status."""

Pending = 1
InProgress = 2
Completed = 3
2 changes: 2 additions & 0 deletions met-api/src/met_api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@
from .report_setting import ReportSetting
from .widget_video import WidgetVideo
from .cac_form import CACForm
from .widget_timeline import WidgetTimeline
from .timeline_event import TimelineEvent
51 changes: 51 additions & 0 deletions met-api/src/met_api/models/timeline_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Timeline Event model class.
Manages the timeline events
"""
from __future__ import annotations

from sqlalchemy.sql.schema import ForeignKey
from met_api.constants.timeline_event_status import TimelineEventStatus

from .base_model import BaseModel
from .db import db


class TimelineEvent(BaseModel):
"""Definition of the TimelineEvent entity."""

__tablename__ = 'timeline_event'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
engagement_id = db.Column(db.Integer, ForeignKey('engagement.id', ondelete='CASCADE'), nullable=True)
widget_id = db.Column(db.Integer, ForeignKey('widget.id', ondelete='CASCADE'), nullable=True)
timeline_id = db.Column(db.Integer, ForeignKey('widget_timeline.id', ondelete='CASCADE'), nullable=True)
status = db.Column(db.Enum(TimelineEventStatus), nullable=False)
position = db.Column(db.Integer, nullable=False)
description = db.Column(db.Text(), nullable=True)
time = db.Column(db.String(255), nullable=True)

@classmethod
def delete_event(cls, timeline_id):
"""Delete timeline."""
timeline_event = db.session.query(TimelineEvent) \
.filter(TimelineEvent.timeline_id == timeline_id)
timeline_event.delete()
db.session.commit()

@classmethod
def get_timeline_events(cls, timeline_id) -> list[TimelineEvent]:
"""Get timeline event."""
timeline_event = db.session.query(TimelineEvent) \
.filter(TimelineEvent.timeline_id == timeline_id) \
.all()
return timeline_event

@classmethod
def update_timeline_event(cls, timeline_id, event_data: dict) -> TimelineEvent:
"""Update timeline event."""
timeline_event: TimelineEvent = TimelineEvent.query.get(timeline_id)
if timeline_event:
for key, value in event_data.items():
setattr(timeline_event, key, value)
timeline_event.save()
return timeline_event
45 changes: 45 additions & 0 deletions met-api/src/met_api/models/widget_timeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""WidgetTimeline model class.
Manages the timeline widget
"""
from __future__ import annotations
from typing import Optional
from sqlalchemy.sql.schema import ForeignKey
from met_api.models.timeline_event import TimelineEvent
from met_api.services.timeline_event_service import TimelineEventService
from .base_model import BaseModel
from .db import db

class WidgetTimeline(BaseModel): # pylint: disable=too-few-public-methods, too-many-instance-attributes
"""Definition of the Timeline entity."""

__tablename__ = 'widget_timeline'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
engagement_id = db.Column(db.Integer, ForeignKey('engagement.id', ondelete='CASCADE'), nullable=True)
widget_id = db.Column(db.Integer, ForeignKey('widget.id', ondelete='CASCADE'), nullable=True)
title = db.Column(db.String(255), nullable=True)
description = db.Column(db.Text(), nullable=True)

# Relationship to timeline_event
events = db.relationship(TimelineEvent, backref='widget_timeline', lazy=True)

@classmethod
def get_timeline(cls, timeline_id) -> list[WidgetTimeline]:
"""Get timeline."""
widget_timeline = db.session.query(WidgetTimeline) \
.filter(WidgetTimeline.widget_id == timeline_id) \
.all()
return widget_timeline

@classmethod
def update_timeline(cls, timeline_id, timeline_data: dict) -> Optional[WidgetTimeline or None]:
"""Update timeline."""
TimelineEvent.delete_event(timeline_id)
widget_timeline: WidgetTimeline = WidgetTimeline.query.get(timeline_id)
if widget_timeline:
widget_timeline.title = timeline_data.get('title')
widget_timeline.description = timeline_data.get('description')
for event in timeline_data.get('events', []):
TimelineEventService.create_timeline_event(timeline_id, event)
widget_timeline.save()
return widget_timeline
2 changes: 2 additions & 0 deletions met-api/src/met_api/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from .widget_video import API as WIDGET_VIDEO_API
from .engagement_settings import API as ENGAGEMENT_SETTINGS_API
from .cac_form import API as CAC_FORM_API
from .widget_timeline import API as WIDGET_TIMELINE_API

__all__ = ('API_BLUEPRINT',)

Expand Down Expand Up @@ -89,3 +90,4 @@
API.add_namespace(WIDGET_VIDEO_API, path='/widgets/<int:widget_id>/videos')
API.add_namespace(ENGAGEMENT_SETTINGS_API)
API.add_namespace(CAC_FORM_API, path='/engagements/<int:engagement_id>/cacform')
API.add_namespace(WIDGET_TIMELINE_API, path='/widgets/<int:widget_id>/timelines')
Loading

0 comments on commit 9ec490d

Please sign in to comment.