Skip to content

Commit

Permalink
Add video widget admin side component and resource (#1828)
Browse files Browse the repository at this point in the history
* Add video widget model and option card

* add video widget service and resource

* Add video widget crud and UI component

* Fix linting issue
  • Loading branch information
jadmsaadaot authored Jul 13, 2023
1 parent b34ac34 commit bd80e5f
Show file tree
Hide file tree
Showing 22 changed files with 750 additions and 2 deletions.
52 changes: 52 additions & 0 deletions met-api/migrations/versions/47fc88fe0477_video_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Add video widget
Revision ID: 47fc88fe0477
Revises: b3b5c66cea4b
Create Date: 2023-07-11 10:44:35.980432
"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = '47fc88fe0477'
down_revision = 'b3b5c66cea4b'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('widget_video',
sa.Column('created_date', sa.DateTime(), nullable=False),
sa.Column('updated_date', sa.DateTime(), nullable=True),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('widget_id', sa.Integer(), nullable=True),
sa.Column('engagement_id', sa.Integer(), nullable=True),
sa.Column('video_url', sa.String(length=255), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('created_by', sa.String(length=50), nullable=True),
sa.Column('updated_by', sa.String(length=50), nullable=True),
sa.ForeignKeyConstraint(['engagement_id'], ['engagement.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['widget_id'], ['widget.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
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': 7, 'name': 'Video', 'description': 'Add a link to a hosted video and link preview'}
])
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('widget_video')

conn = op.get_bind()

conn.execute('DELETE FROM widget_type WHERE id=7')
# ### end Alembic commands ###
1 change: 1 addition & 0 deletions met-api/src/met_api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@
from .email_queue import EmailQueue
from .engagement_slug import EngagementSlug
from .report_setting import ReportSetting
from .widget_video import WidgetVideo
39 changes: 39 additions & 0 deletions met-api/src/met_api/models/widget_video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""WidgetVideo model class.
Manages the video widget
"""
from __future__ import annotations

from sqlalchemy.sql.schema import ForeignKey

from .base_model import BaseModel
from .db import db


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

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

@classmethod
def get_video(cls, widget_id) -> list[WidgetVideo]:
"""Get video."""
widget_video = db.session.query(WidgetVideo) \
.filter(WidgetVideo.widget_id == widget_id) \
.all()
return widget_video

@classmethod
def update_video(cls, video_widget_id, video_data: dict) -> WidgetVideo:
"""Update video."""
widget_video: WidgetVideo = WidgetVideo.query.get(video_widget_id)
if widget_video:
for key, value in video_data.items():
setattr(widget_video, key, value)
widget_video.save()
return widget_video
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 @@ -45,6 +45,7 @@
from .tenant import API as TENANT_API
from .engagement_slug import API as ENGAGEMENT_SLUG_API
from .report_setting import API as REPORT_SETTING_API
from .widget_video import API as WIDGET_VIDEO_API

__all__ = ('API_BLUEPRINT',)

Expand Down Expand Up @@ -81,3 +82,4 @@
API.add_namespace(WIDGET_MAPS_API, path='/widgets/<int:widget_id>/maps')
API.add_namespace(ENGAGEMENT_SLUG_API, path='/slugs')
API.add_namespace(REPORT_SETTING_API, path='/surveys/<int:survey_id>/reportsettings')
API.add_namespace(WIDGET_VIDEO_API, path='/widgets/<int:widget_id>/videos')
2 changes: 1 addition & 1 deletion met-api/src/met_api/resources/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""API endpoints for managing a FOI Requests resource."""
"""API endpoints for managing documents resource."""

from http import HTTPStatus

Expand Down
2 changes: 1 addition & 1 deletion met-api/src/met_api/resources/widget_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""API endpoints for managing a FOI Requests resource."""
"""API endpoints for managing map resource."""
import json
from http import HTTPStatus

Expand Down
80 changes: 80 additions & 0 deletions met-api/src/met_api/resources/widget_video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Copyright © 2021 Province of British Columbia
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""API endpoints for managing a video widget resource."""
from http import HTTPStatus

from flask import request
from flask_cors import cross_origin
from flask_restx import Namespace, Resource

from met_api.auth import jwt as _jwt
from met_api.exceptions.business_exception import BusinessException
from met_api.schemas import utils as schema_utils
from met_api.schemas.widget_video import WidgetVideoSchema
from met_api.services.widget_video_service import WidgetVideoService
from met_api.utils.roles import Role
from met_api.utils.util import allowedorigins, cors_preflight


API = Namespace('widget_videos', description='Endpoints for Video Widget Management')
"""Widget Videos"""


@cors_preflight('GET, POST, PATCH, OPTIONS')
@API.route('')
class Videos(Resource):
"""Resource for managing video widgets."""

@staticmethod
@cross_origin(origins=allowedorigins())
def get(widget_id):
"""Get video widget."""
try:
widget_video = WidgetVideoService().get_video(widget_id)
return WidgetVideoSchema().dump(widget_video, many=True), HTTPStatus.OK
except BusinessException as err:
return str(err), err.status_code

@staticmethod
@cross_origin(origins=allowedorigins())
@_jwt.has_one_of_roles([Role.EDIT_ENGAGEMENT.value])
def post(widget_id):
"""Create video widget."""
try:
request_json = request.get_json()
widget_video = WidgetVideoService().create_video(widget_id, request_json)
return WidgetVideoSchema().dump(widget_video), HTTPStatus.OK
except BusinessException as err:
return str(err), err.status_code


@cors_preflight('PATCH')
@API.route('/<int:video_widget_id>')
class Video(Resource):
"""Resource for managing video widgets."""

@staticmethod
@cross_origin(origins=allowedorigins())
@_jwt.has_one_of_roles([Role.EDIT_ENGAGEMENT.value])
def patch(widget_id, video_widget_id):
"""Update video widget."""
request_json = request.get_json()
valid_format, errors = schema_utils.validate(request_json, 'video_widget_update')
if not valid_format:
return {'message': schema_utils.serialize(errors)}, HTTPStatus.BAD_REQUEST
try:
widget_video = WidgetVideoService().update_video(widget_id, video_widget_id, request_json)
return WidgetVideoSchema().dump(widget_video), HTTPStatus.OK
except BusinessException as err:
return str(err), err.status_code
31 changes: 31 additions & 0 deletions met-api/src/met_api/schemas/schemas/video_widget_update.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://met.gov.bc.ca/.well_known/schemas/video_widget_update",
"type": "object",
"title": "The root schema",
"description": "The root schema comprises the entire JSON document.",
"default": {},
"examples": [
{
"description": "A video widget description",
"video_url": "https://www.youtube.com"
}
],
"required": [],
"properties": {
"description": {
"$id": "#/properties/description",
"type": "string",
"title": "Video description",
"description": "The description of this video.",
"examples": ["A video widget description"]
},
"video_url": {
"$id": "#/properties/video_url",
"type": "string",
"title": "Video url",
"description": "The url link to this video.",
"examples": ["https://www.youtube.com"]
}
}
}
28 changes: 28 additions & 0 deletions met-api/src/met_api/schemas/widget_video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright © 2019 Province of British Columbia
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Manager for widget video schema."""

from met_api.models.widget_video import WidgetVideo as WidgetVideoModel

from marshmallow import Schema


class WidgetVideoSchema(Schema): # pylint: disable=too-many-ancestors, too-few-public-methods
"""This is the schema for the video model."""

class Meta: # pylint: disable=too-few-public-methods
"""Videos all of the Widget Video fields to a default schema."""

model = WidgetVideoModel
fields = ('id', 'widget_id', 'engagement_id', 'video_url', 'description')
42 changes: 42 additions & 0 deletions met-api/src/met_api/services/widget_video_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Service for Widget Video management."""
from met_api.models.widget_video import WidgetVideo as WidgetVideoModel


class WidgetVideoService:
"""Widget Video management service."""

@staticmethod
def get_video(widget_id):
"""Get video by widget id."""
widget_video = WidgetVideoModel.get_video(widget_id)
return widget_video

@staticmethod
def create_video(widget_id, video_details: dict):
"""Create video for the widget."""
video_data = dict(video_details)
widget_video = WidgetVideoService._create_video_model(widget_id, video_data)
widget_video.commit()
return widget_video

@staticmethod
def update_video(widget_id, video_widget_id, video_data):
"""Update video widget."""
widget_video: WidgetVideoModel = WidgetVideoModel.find_by_id(video_widget_id)
if not widget_video:
raise KeyError('Video widget not found')

if widget_video.widget_id != widget_id:
raise ValueError('Invalid widgets and video')

return WidgetVideoModel.update_video(widget_video.id, video_data)

@staticmethod
def _create_video_model(widget_id, video_data: dict):
video_model: WidgetVideoModel = WidgetVideoModel()
video_model.widget_id = widget_id
video_model.engagement_id = video_data.get('engagement_id')
video_model.video_url = video_data.get('video_url')
video_model.description = video_data.get('description')
video_model.flush()
return video_model
5 changes: 5 additions & 0 deletions met-web/src/apiManager/endpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ const Endpoints = {
CREATE: `${AppConfig.apiUrl}/widgets/widget_id/maps`,
SHAPEFILE_PREVIEW: `${AppConfig.apiUrl}/shapefile`,
},
VideoWidgets: {
GET: `${AppConfig.apiUrl}/widgets/widget_id/videos`,
CREATE: `${AppConfig.apiUrl}/widgets/widget_id/videos`,
UPDATE: `${AppConfig.apiUrl}/widgets/widget_id/videos/video_widget_id`,
},
Tenants: {
GET: `${AppConfig.apiUrl}/tenants/tenant_id`,
},
Expand Down
Loading

0 comments on commit bd80e5f

Please sign in to comment.