From 90149b2763739a3e69aac1a78987fc624b935f0f Mon Sep 17 00:00:00 2001 From: Jareth Whitney Date: Wed, 18 Sep 2024 16:49:50 -0700 Subject: [PATCH 1/6] feature/deseng692 and feature/deseng671: Updated video widget, made vital fixes to action and loading logic in authoring section, added columns to engagement and widget_video tables. --- .../admin/config/EngagementCreateAction.tsx | 1 + .../create/authoring/AuthoringContext.tsx | 7 +- .../create/authoring/AuthoringSummary.tsx | 136 +++++----- .../create/authoring/AuthoringTemplate.tsx | 5 +- .../engagementAuthoringUpdateAction.tsx | 1 + .../admin/create/authoring/types.ts | 1 + .../EngagementTabsContext.tsx | 4 + .../form/EngagementWidgets/Video/Form.tsx | 28 +- .../src/components/engagement/form/types.ts | 2 + .../widgets/Video/VideoWidgetView.tsx | 241 +++++++++++++++--- .../public/view/EngagementDescription.tsx | 22 +- met-web/src/models/engagement.ts | 2 + met-web/src/models/videoWidget.ts | 1 + .../src/services/engagementService/types.ts | 3 + .../widgetService/VideoService/index.tsx | 2 + .../components/comment/CommentReview.test.tsx | 1 + .../engagement/EngagementListing.test.tsx | 1 + met-web/tests/unit/components/factory.ts | 2 + .../components/survey/surveyListing.test.tsx | 1 + .../widgets/DocumentWidget.test.tsx | 1 + .../components/widgets/VideoWidget.test.tsx | 6 +- 21 files changed, 348 insertions(+), 120 deletions(-) diff --git a/met-web/src/components/engagement/admin/config/EngagementCreateAction.tsx b/met-web/src/components/engagement/admin/config/EngagementCreateAction.tsx index bfc8a3f05..942cb17cf 100644 --- a/met-web/src/components/engagement/admin/config/EngagementCreateAction.tsx +++ b/met-web/src/components/engagement/admin/config/EngagementCreateAction.tsx @@ -12,6 +12,7 @@ export const engagementCreateAction: ActionFunction = async ({ request }) => { is_internal: formData.get('is_internal') === 'true', description: '', rich_description: '', + description_title: '', content: '', rich_content: '', }); diff --git a/met-web/src/components/engagement/admin/create/authoring/AuthoringContext.tsx b/met-web/src/components/engagement/admin/create/authoring/AuthoringContext.tsx index d5a804061..35af10549 100644 --- a/met-web/src/components/engagement/admin/create/authoring/AuthoringContext.tsx +++ b/met-web/src/components/engagement/admin/create/authoring/AuthoringContext.tsx @@ -14,12 +14,13 @@ export interface EngagementUpdateData { end_date: Dayjs; description: string; rich_description: string; + description_title: string; banner_filename: string; status_block: string[]; title: string; icon_name: string; metadata_value: string; - send_report: boolean; + send_report: boolean | undefined; slug: string; request_type: string; text_content: string; @@ -37,6 +38,7 @@ export const defaultValuesObject = { end_date: dayjs(new Date(1970, 0, 1)), description: '', rich_description: '', + description_title: '', banner_filename: '', status_block: [], title: '', @@ -74,12 +76,13 @@ export const AuthoringContext = () => { '1970-01-01' === data.start_date.format('YYYY-MM-DD') ? '' : data.end_date.format('YYYY-MM-DD'), description: data.description, rich_description: data.rich_description, + description_title: data.description_title, banner_filename: data.banner_filename, status_block: data.status_block, title: data.title, icon_name: data.icon_name, metadata_value: data.metadata_value, - send_report: getSendReportValue(data.send_report), + send_report: data.send_report ? getSendReportValue(data.send_report) : '', slug: data.slug, request_type: data.request_type, text_content: data.text_content, diff --git a/met-web/src/components/engagement/admin/create/authoring/AuthoringSummary.tsx b/met-web/src/components/engagement/admin/create/authoring/AuthoringSummary.tsx index 9d1f588da..03f4cc729 100644 --- a/met-web/src/components/engagement/admin/create/authoring/AuthoringSummary.tsx +++ b/met-web/src/components/engagement/admin/create/authoring/AuthoringSummary.tsx @@ -1,29 +1,34 @@ import { Grid } from '@mui/material'; -import React, { useEffect, Suspense } from 'react'; -import { useOutletContext, useRouteLoaderData, Await } from 'react-router-dom'; +import React, { useEffect } from 'react'; +import { useOutletContext, useRouteLoaderData, useNavigate } from 'react-router-dom'; import { TextField } from 'components/common/Input'; import { AuthoringTemplateOutletContext } from './types'; import { colors } from 'styles/Theme'; import { EyebrowText as FormDescriptionText } from 'components/common/Typography'; import { MetHeader3, MetLabel as MetBigLabel } from 'components/common'; import { RichTextArea } from 'components/common/Input/RichTextArea'; -import { convertToRaw, EditorState } from 'draft-js'; +import { convertFromRaw, convertToRaw, EditorState } from 'draft-js'; import { Controller } from 'react-hook-form'; -import { EngagementContent } from 'models/engagementContent'; import { useAppDispatch } from 'hooks'; import { openNotification } from 'services/notificationService/notificationSlice'; +import dayjs from 'dayjs'; +import { EngagementUpdateData } from './AuthoringContext'; +import { Engagement } from 'models/engagement'; const AuthoringSummary = () => { - const { - setValue, - control, - reset, - getValues, - engagement, - defaultValues, - setDefaultValues, - fetcher, - }: AuthoringTemplateOutletContext = useOutletContext(); // Access the form functions and values from the authoring template. + const { setValue, control, reset, getValues, setDefaultValues, fetcher, slug }: AuthoringTemplateOutletContext = + useOutletContext(); // Access the form functions and values from the authoring template. + + // Update the loader data when the authoring section is changed, by triggering navigation(). + const navigate = useNavigate(); + useEffect(() => { + engagementData.then(() => navigate('.', { replace: true })); + }, [slug]); + + // Get the engagement data + const { engagement: engagementData } = useRouteLoaderData('single-engagement') as { + engagement: Promise; + }; // Check if the form has succeeded or failed after a submit, and issue a message to the user. const dispatch = useAppDispatch(); @@ -42,25 +47,48 @@ const AuthoringSummary = () => { } }, [fetcher.data]); - const { content } = useRouteLoaderData('authoring-loader') as { - content: Promise; + const untouchedDefaultValues: EngagementUpdateData = { + id: 0, + status_id: 0, + taxon_id: 0, + content_id: 0, + name: '', + start_date: dayjs(new Date(1970, 0, 1)), + end_date: dayjs(new Date(1970, 0, 1)), + description: '', + rich_description: '', + description_title: '', + banner_filename: '', + status_block: [], + title: '', + icon_name: '', + metadata_value: '', + send_report: undefined, + slug: '', + request_type: '', + text_content: '', + json_content: '{ blocks: [], entityMap: {} }', + summary_editor_state: EditorState.createEmpty(), }; + // Reset values to default and retrieve relevant content from loader. useEffect(() => { - // Reset values to default and retrieve relevant content from loader. - reset(defaultValues); - setValue('id', Number(engagement.id)); - content.then((content) => { - setValue('content_id', Number(content[0].id)); - setValue('title', content[0].title); - setValue('text_content', content[0].text_content); - // Make sure it's valid JSON. - if (tryParse(content[0].json_content)) { - setValue('json_content', content[0].json_content); + reset(untouchedDefaultValues); + engagementData.then((engagement) => { + setValue('id', Number(engagement.id)); + // Make sure it is valid JSON. + if (tryParse(engagement.rich_description)) { + setValue('rich_description', engagement.rich_description); } + setValue('description_title', engagement.description_title || 'Hello world'); + setValue('description', engagement.description); + setValue( + 'summary_editor_state', + EditorState.createWithContent(convertFromRaw(JSON.parse(engagement.rich_description))), + ); setDefaultValues(getValues()); // Update default values so that our loaded values are default. }); - }, [content]); + }, [engagementData]); // Define the styles const metBigLabelStyles = { @@ -110,15 +138,15 @@ const AuthoringSummary = () => { }; const handleTitleChange = (value: string) => { - setValue('title', value); + setValue('description_title', value); return value; }; const handleEditorChange = (newEditorState: EditorState) => { const plainText = newEditorState.getCurrentContent().getPlainText(); const stringifiedEditorState = JSON.stringify(convertToRaw(newEditorState.getCurrentContent())); - setValue('text_content', plainText); - setValue('json_content', stringifiedEditorState); + setValue('description', plainText); + setValue('rich_description', stringifiedEditorState); return newEditorState; }; @@ -136,7 +164,7 @@ const AuthoringSummary = () => { return ( - diff --git a/met-web/src/components/engagement/admin/create/authoring/AuthoringTemplate.tsx b/met-web/src/components/engagement/admin/create/authoring/AuthoringTemplate.tsx index 195b3b6f3..95ab178d2 100644 --- a/met-web/src/components/engagement/admin/create/authoring/AuthoringTemplate.tsx +++ b/met-web/src/components/engagement/admin/create/authoring/AuthoringTemplate.tsx @@ -158,7 +158,6 @@ const AuthoringTemplate = () => { -
@@ -173,8 +172,8 @@ const AuthoringTemplate = () => { getValues, setDefaultValues, reset, - control, engagement, + control, contentTabsEnabled, tabs, singleContentValues, @@ -182,12 +181,12 @@ const AuthoringTemplate = () => { isDirty, defaultValues, fetcher, + slug, }} /> )} - diff --git a/met-web/src/components/engagement/admin/create/authoring/engagementAuthoringUpdateAction.tsx b/met-web/src/components/engagement/admin/create/authoring/engagementAuthoringUpdateAction.tsx index 0f30ee9f0..94fdb720a 100644 --- a/met-web/src/components/engagement/admin/create/authoring/engagementAuthoringUpdateAction.tsx +++ b/met-web/src/components/engagement/admin/create/authoring/engagementAuthoringUpdateAction.tsx @@ -17,6 +17,7 @@ export const engagementAuthoringUpdateAction: ActionFunction = async ({ request end_date: (formData.get('end_date') as string) || undefined, description: (formData.get('description') as string) || undefined, rich_description: (formData.get('rich_description') as string) || undefined, + description_title: (formData.get('description_title') as string) || undefined, banner_filename: (formData.get('banner_filename') as string) || undefined, status_block: (formData.get('status_block') as unknown as unknown[]) || undefined, }); diff --git a/met-web/src/components/engagement/admin/create/authoring/types.ts b/met-web/src/components/engagement/admin/create/authoring/types.ts index 8f3ff82fa..d797c8c7e 100644 --- a/met-web/src/components/engagement/admin/create/authoring/types.ts +++ b/met-web/src/components/engagement/admin/create/authoring/types.ts @@ -68,6 +68,7 @@ export interface AuthoringTemplateOutletContext { defaultValues: EngagementUpdateData; setDefaultValues: Dispatch>; fetcher: FetcherWithComponents; + slug: string; } export interface DetailsTabProps { diff --git a/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx b/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx index d3dd5e320..b11d7c5e4 100644 --- a/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx +++ b/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx @@ -24,6 +24,7 @@ interface EngagementFormData { start_date: string; end_date: string; description: string; + description_title: string; content: string; is_internal: boolean; consent_message: string; @@ -45,6 +46,7 @@ const initialEngagementFormData = { start_date: '', end_date: '', description: '', + description_title: '', content: '', is_internal: false, consent_message: '', @@ -205,6 +207,7 @@ export const EngagementTabsContextProvider = ({ children }: { children: React.Re start_date: savedEngagement.start_date, end_date: savedEngagement.end_date, description: savedEngagement.description || '', + description_title: savedEngagement.description_title || '', content: savedEngagement.content || '', is_internal: savedEngagement.is_internal || false, consent_message: savedEngagement.consent_message || '', @@ -246,6 +249,7 @@ export const EngagementTabsContextProvider = ({ children }: { children: React.Re start_date: savedEngagement.start_date, end_date: savedEngagement.end_date, description: savedEngagement.description || '', + description_title: savedEngagement.description_title || '', content: savedEngagement.content || '', is_internal: savedEngagement.is_internal || false, consent_message: savedEngagement.consent_message || '', diff --git a/met-web/src/components/engagement/form/EngagementWidgets/Video/Form.tsx b/met-web/src/components/engagement/form/EngagementWidgets/Video/Form.tsx index 20721b408..70af54106 100644 --- a/met-web/src/components/engagement/form/EngagementWidgets/Video/Form.tsx +++ b/met-web/src/components/engagement/form/EngagementWidgets/Video/Form.tsx @@ -22,10 +22,8 @@ const schema = yup .url('Please enter a valid Link') .required('Please enter a valid Link') .max(255, 'Video link cannot exceed 255 characters'), - description: yup - .string() - .required('Please enter a description') - .max(500, 'Description cannot exceed 500 characters'), + title: yup.string().max(255, 'Video title cannot exceed 255 characters'), + description: yup.string().max(500, 'Description cannot exceed 500 characters'), }) .required(); @@ -45,6 +43,7 @@ const Form = () => { useEffect(() => { if (videoWidget) { + methods.setValue('title', videoWidget.title); methods.setValue('description', videoWidget.description); methods.setValue('videoUrl', videoWidget.video_url); } @@ -56,12 +55,13 @@ const Form = () => { } const validatedData = await schema.validate(data); - const { videoUrl, description } = validatedData; + const { videoUrl, title, description } = validatedData; await postVideo(widget.id, { widget_id: widget.id, engagement_id: widget.engagement_id, video_url: videoUrl, - description: description, + title: title || '', + description: description || '', location: widget.location in WidgetLocation ? widget.location : null, }); dispatch(openNotification({ severity: 'success', text: 'A new video was successfully added' })); @@ -76,10 +76,12 @@ const Form = () => { const updatedDate = updatedDiff( { description: videoWidget.description, + title: videoWidget.title, video_url: videoWidget.video_url, }, { description: validatedData.description, + title: validatedData.title, video_url: validatedData.videoUrl, }, ); @@ -143,7 +145,19 @@ const Form = () => { spacing={2} > - Description + Title (Optional) + + + + Description (Optional) { + const theme = useTheme(); const dispatch = useAppDispatch(); + const isDarkMode = 'dark' === theme.palette.mode; + const [videoWidget, setVideoWidget] = useState(null); const [isLoading, setIsLoading] = useState(true); + const [videoDuration, setVideoDuration] = useState(''); + const [showOverlay, setShowOverlay] = useState(true); + const [videoOverlayTitle, setVideoOverlayTitle] = useState(''); const playerConfig = { youtube: { @@ -43,24 +63,76 @@ const VideoWidgetView = ({ widget }: VideoWidgetProps) => { } }; + const formatVideoDuration = (duration: number) => { + let minutes = 0; + let hours = 0; + let seconds = 0; + if (3600 < duration) { + hours = Math.floor(duration / 3600); + minutes = Math.floor((duration - hours * 3600) / 60); + seconds = Math.floor(duration - hours * 3600 - minutes * 60); + return `${hours} hr ${minutes} min ${seconds} sec`; + } else if (3600 > duration && 60 < duration) { + minutes = Math.floor(duration / 60); + seconds = Math.floor(duration - minutes * 60); + return `${minutes} min ${seconds} sec`; + } else if (60 > duration && 0 < duration) { + return `${seconds} seconds`; + } else { + return ''; + } + }; + + const getVideoSource = (url: string) => { + if (url.includes('youtube.com') || url.includes('youtu.be')) { + return 'YouTube'; + } else if (url.includes('vimeo.com')) { + return 'Vimeo'; + } else if (url.includes('facebook.com')) { + return 'Facebook'; + } else if (url.includes('twitch.tv')) { + return 'Twitch'; + } else if (url.includes('soundcloud.com')) { + return 'SoundCloud'; + } else if (url.includes('mixcloud.com')) { + return 'Mixcloud'; + } else if (url.includes('dailymotion.com')) { + return 'DailyMotion'; + } else { + return 'Unknown'; + } + }; + + const getVideoTitle = (player: ReactPlayer, source: string, widgetTitle: string) => { + if ('YouTube' === source) { + return `${player.getInternalPlayer().videoTitle} (${source} video)`; + } else if ('Vimeo' === source) { + return `${player.getInternalPlayer().element.title} (${source} video)`; + } else if ('Facebook' === source || 'Twitch' === source || 'DailyMotion' === source) { + return `${widgetTitle} (${source} video)`; // API doesn't return video title, use widget title. + } else if ('SoundCloud' === source || 'Mixcloud' === source) { + return `${widgetTitle} (${source} audio)`; // API doesn't return audio title, use widget title. + } else { + return `${widgetTitle} (video)`; + } + }; + useEffect(() => { fetchVideo(); }, [widget]); if (isLoading) { return ( - - - - - - - - - - + + + + + + + + - + ); } @@ -69,35 +141,126 @@ const VideoWidgetView = ({ widget }: VideoWidgetProps) => { } return ( - - - + + - {widget.title} - - - - {videoWidget.description} - - - - - + {videoWidget.title} + + + + + {videoWidget.description} + + + {/* Overlay covers play button for Mixcloud so it shouldn't be displayed */} + + + + + + + { + setVideoDuration(formatVideoDuration(duration)); + }} + url={videoWidget.video_url} + controls + onReady={(player) => { + const videoSource = getVideoSource(videoWidget.video_url); + setVideoOverlayTitle(getVideoTitle(player, videoSource, videoWidget.title)); + }} + onPlay={() => setShowOverlay(false)} + width="100%" + height={'100%'} + config={playerConfig} + style={{ + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + }} + /> + + + + + {videoDuration} + - + + ); +}; + +interface VideoOverlayProps { + videoOverlayTitle: string; + source: string; +} + +const VideoOverlay = ({ videoOverlayTitle, source }: VideoOverlayProps) => { + const getIcon = (source: string) => { + if ('YouTube' === source) { + return faYoutube; + } else if ('Vimeo' === source) { + return faVimeo; + } else if ('Facebook' === source) { + return faFacebookF; + } else if ('Twitch' === source) { + return faTwitch; + } else if ('SoundCloud' === source) { + return faSoundcloud; + } else if ('Mixcloud' === source) { + return faMixcloud; + } else if ('DailyMotion' === source) { + return faDailymotion; + } else { + return faQuestionCircle; + } + }; + return ( + +
+ {' '} + {videoOverlayTitle} +
+
); }; diff --git a/met-web/src/components/engagement/public/view/EngagementDescription.tsx b/met-web/src/components/engagement/public/view/EngagementDescription.tsx index bc7469afb..1ada2f8be 100644 --- a/met-web/src/components/engagement/public/view/EngagementDescription.tsx +++ b/met-web/src/components/engagement/public/view/EngagementDescription.tsx @@ -65,19 +65,21 @@ export const EngagementDescription = () => { marginBottom: '48px', }} > - - Engagement Description - }> {(engagement: Engagement) => ( - - - + <> + + {engagement.description_title} + + + + + )} diff --git a/met-web/src/models/engagement.ts b/met-web/src/models/engagement.ts index 09bcefd65..e7ab0d9ec 100644 --- a/met-web/src/models/engagement.ts +++ b/met-web/src/models/engagement.ts @@ -7,6 +7,7 @@ export interface Engagement { name: string; description: string; rich_description: string; + description_title: string; status_id: number; start_date: string; end_date: string; @@ -71,6 +72,7 @@ export const createDefaultEngagement = (sponsorName?: string): Engagement => { name: '', description: '', rich_description: '', + description_title: '', status_id: 0, start_date: '', end_date: '', diff --git a/met-web/src/models/videoWidget.ts b/met-web/src/models/videoWidget.ts index b5d681e6b..88f403bac 100644 --- a/met-web/src/models/videoWidget.ts +++ b/met-web/src/models/videoWidget.ts @@ -3,5 +3,6 @@ export interface VideoWidget { widget_id: number; engagement_id: number; video_url: string; + title: string; description: string; } diff --git a/met-web/src/services/engagementService/types.ts b/met-web/src/services/engagementService/types.ts index cc7b596de..1c961eceb 100644 --- a/met-web/src/services/engagementService/types.ts +++ b/met-web/src/services/engagementService/types.ts @@ -10,6 +10,7 @@ export interface PostEngagementRequest { end_date: string; description: string; rich_description: string; + description_title: string; content: string; rich_content: string; banner_filename?: string; @@ -24,6 +25,7 @@ export interface PutEngagementRequest { end_date: string; description: string; rich_description: string; + description_title: string; banner_filename?: string; status_block?: unknown[]; } @@ -36,6 +38,7 @@ export interface PatchEngagementRequest { end_date?: string; description?: string; rich_description?: string; + description_title?: string; banner_filename?: string; status_block?: unknown[]; is_internal?: boolean; diff --git a/met-web/src/services/widgetService/VideoService/index.tsx b/met-web/src/services/widgetService/VideoService/index.tsx index 07bfea460..0400bb2fe 100644 --- a/met-web/src/services/widgetService/VideoService/index.tsx +++ b/met-web/src/services/widgetService/VideoService/index.tsx @@ -18,6 +18,7 @@ interface PostVideoRequest { widget_id: number; engagement_id: number; video_url: string; + title: string; description: string; location: WidgetLocation | null; } @@ -34,6 +35,7 @@ export const postVideo = async (widget_id: number, data: PostVideoRequest): Prom interface PatchVideoRequest { video_url?: string; + title?: string; description?: string; } diff --git a/met-web/tests/unit/components/comment/CommentReview.test.tsx b/met-web/tests/unit/components/comment/CommentReview.test.tsx index 2b6004166..9f5d97102 100644 --- a/met-web/tests/unit/components/comment/CommentReview.test.tsx +++ b/met-web/tests/unit/components/comment/CommentReview.test.tsx @@ -81,6 +81,7 @@ describe('CommentReview Component', () => { name: '', description: '', rich_description: '', + description_title: '', status_id: 0, start_date: '', end_date: '2024-04-10', // Apr 10, 2024 diff --git a/met-web/tests/unit/components/engagement/EngagementListing.test.tsx b/met-web/tests/unit/components/engagement/EngagementListing.test.tsx index b9841f2c1..b2e52da9b 100644 --- a/met-web/tests/unit/components/engagement/EngagementListing.test.tsx +++ b/met-web/tests/unit/components/engagement/EngagementListing.test.tsx @@ -43,6 +43,7 @@ const mockEngagementOne = { rich_description: '{"blocks":[{"key":"bqupg","text":"Test description","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}}],"entityMap":{}}', description: 'Test description', + description_title: 'Test Description Title', start_date: '2022-09-01', end_date: '2022-09-30', engagement_status: { diff --git a/met-web/tests/unit/components/factory.ts b/met-web/tests/unit/components/factory.ts index 31d02d593..5575f10ad 100644 --- a/met-web/tests/unit/components/factory.ts +++ b/met-web/tests/unit/components/factory.ts @@ -48,6 +48,7 @@ const draftEngagement: Engagement = { rich_description: '{"blocks":[{"key":"bqupg","text":"Test description","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}}],"entityMap":{}}', description: 'Test description', + description_title: 'Test description title', start_date: '2022-09-01', end_date: '2022-09-30', surveys: surveys, @@ -248,6 +249,7 @@ const mockVideo: VideoWidget = { id: 1, widget_id: 1, engagement_id: 1, + title: 'Video Title', video_url: 'https://youtube.url', description: 'Video description', }; diff --git a/met-web/tests/unit/components/survey/surveyListing.test.tsx b/met-web/tests/unit/components/survey/surveyListing.test.tsx index 9b974430b..42fb2cc1b 100644 --- a/met-web/tests/unit/components/survey/surveyListing.test.tsx +++ b/met-web/tests/unit/components/survey/surveyListing.test.tsx @@ -23,6 +23,7 @@ const mockEngagementOne = { rich_description: '{"blocks":[{"key":"bqupg","text":"Test description","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}}],"entityMap":{}}', description: 'Test description', + description_title: 'Test Description Title', start_date: '2022-09-01', end_date: '2022-09-30', engagement_status: { diff --git a/met-web/tests/unit/components/widgets/DocumentWidget.test.tsx b/met-web/tests/unit/components/widgets/DocumentWidget.test.tsx index ec85e4e34..271c2be50 100644 --- a/met-web/tests/unit/components/widgets/DocumentWidget.test.tsx +++ b/met-web/tests/unit/components/widgets/DocumentWidget.test.tsx @@ -36,6 +36,7 @@ const engagement: Engagement = { rich_description: '{"blocks":[{"key":"bqupg","text":"Test description","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}}],"entityMap":{}}', description: 'Test description', + description_title: 'Test Description Title', start_date: '2022-09-01', end_date: '2022-09-30', engagement_status: { diff --git a/met-web/tests/unit/components/widgets/VideoWidget.test.tsx b/met-web/tests/unit/components/widgets/VideoWidget.test.tsx index 53b885cfb..bbddc3087 100644 --- a/met-web/tests/unit/components/widgets/VideoWidget.test.tsx +++ b/met-web/tests/unit/components/widgets/VideoWidget.test.tsx @@ -88,7 +88,8 @@ describe('Video Widget tests', () => { await waitFor(() => expect(screen.getByText('Select Widget')).toBeVisible()); fireEvent.click(screen.getByTestId(`widget-drawer-option/${WidgetType.Video}`)); await waitFor(() => { - expect(screen.getByText('Description')).toBeVisible(); + expect(screen.getByText('Title (Optional)')).toBeVisible(); + expect(screen.getByText('Description (Optional)')).toBeVisible(); }); } @@ -112,7 +113,8 @@ describe('Video Widget tests', () => { await addVideoWidget(); expect(getWidgetsMock).toHaveBeenCalled(); - expect(screen.getByText('Description')).toBeVisible(); + expect(screen.getByText('Title (Optional)')).toBeVisible(); + expect(screen.getByText('Description (Optional)')).toBeVisible(); expect(screen.getByText('Video Link')).toBeVisible(); }); From 81211333243294d77a3ae16f885169a8e56ece66 Mon Sep 17 00:00:00 2001 From: Jareth Whitney Date: Wed, 18 Sep 2024 17:09:06 -0700 Subject: [PATCH 2/6] feature/deseng692 and deseng691: Added API files and postman collection. --- docs/MET_database_ERD.md | 2 + ...dded_title_column_to_widget_video_table.py | 26 + ...daf9_added_description_title_column_to_.py | 30 + met-api/src/met_api/models/engagement.py | 2 + .../met_api/models/engagement_translation.py | 1 + .../src/met_api/models/widget_translation.py | 2 + met-api/src/met_api/models/widget_video.py | 1 + met-api/src/met_api/schemas/engagement.py | 1 + .../met_api/schemas/engagement_translation.py | 1 + .../schemas/subscribe_item_translation.json | 9 +- .../schemas/schemas/video_widget_update.json | 40 +- .../src/met_api/schemas/widget_translation.py | 1 + met-api/src/met_api/schemas/widget_video.py | 2 +- .../met_api/services/engagement_service.py | 1 + .../engagement_translation_service.py | 1 + .../services/widget_translation_service.py | 1 + .../met_api/services/widget_video_service.py | 1 + met-api/tests/unit/api/test_widget_video.py | 2 + met-api/tests/utilities/factory_scenarios.py | 7 +- met-api/tests/utilities/factory_utils.py | 2 + tools/postman/MET.postman_collection.json | 811 +++++++++--------- 21 files changed, 514 insertions(+), 430 deletions(-) create mode 100644 met-api/migrations/versions/9c1743047332_added_title_column_to_widget_video_table.py create mode 100644 met-api/migrations/versions/df693f5ddaf9_added_description_title_column_to_.py diff --git a/docs/MET_database_ERD.md b/docs/MET_database_ERD.md index 7f3d2e658..0476322d3 100644 --- a/docs/MET_database_ERD.md +++ b/docs/MET_database_ERD.md @@ -4,6 +4,7 @@ erDiagram integer id PK string name string description + string description_title timestamp start_date timestamp end_date integer status_id FK "The id from engagement status" @@ -123,6 +124,7 @@ erDiagram integer widget_id FK "The id from widget" integer engagement_id FK "The id from engagement" string video_url + string title string description timestamp created_date timestamp updated_date diff --git a/met-api/migrations/versions/9c1743047332_added_title_column_to_widget_video_table.py b/met-api/migrations/versions/9c1743047332_added_title_column_to_widget_video_table.py new file mode 100644 index 000000000..2e4c671a4 --- /dev/null +++ b/met-api/migrations/versions/9c1743047332_added_title_column_to_widget_video_table.py @@ -0,0 +1,26 @@ +"""added title column to widget_video table. + +Revision ID: 9c1743047332 +Revises: e706db763790 +Create Date: 2024-09-16 13:09:41.765003 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '9c1743047332' +down_revision = 'e706db763790' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('widget_translation', sa.Column('video_title', sa.Text(), nullable=True)) + op.add_column('widget_video', sa.Column('title', sa.Text(), nullable=True)) + + +def downgrade(): + op.drop_column('widget_translation', 'video_title') + op.drop_column('widget_video', 'title') diff --git a/met-api/migrations/versions/df693f5ddaf9_added_description_title_column_to_.py b/met-api/migrations/versions/df693f5ddaf9_added_description_title_column_to_.py new file mode 100644 index 000000000..464cb3384 --- /dev/null +++ b/met-api/migrations/versions/df693f5ddaf9_added_description_title_column_to_.py @@ -0,0 +1,30 @@ +"""Added description_title column to engagement table. + +Revision ID: df693f5ddaf9 +Revises: 9c1743047332 +Create Date: 2024-09-18 15:33:39.791300 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'df693f5ddaf9' +down_revision = '9c1743047332' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('engagement_translation', sa.Column('description_title', sa.String(length=255), nullable=True)) + op.add_column('engagement', sa.Column('description_title', sa.String(length=255), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('engagement_translation', 'description_title') + op.drop_column('engagement', 'description_title') + # ### end Alembic commands ### diff --git a/met-api/src/met_api/models/engagement.py b/met-api/src/met_api/models/engagement.py index 95483cb09..4127d0399 100644 --- a/met-api/src/met_api/models/engagement.py +++ b/met-api/src/met_api/models/engagement.py @@ -38,6 +38,7 @@ class Engagement(BaseModel): name = db.Column(db.String(50)) description = db.Column(db.Text, unique=False, nullable=False) rich_description = db.Column(JSON, unique=False, nullable=False) + description_title = db.Column(db.String(255), unique=False, nullable=False) start_date = db.Column(db.DateTime) end_date = db.Column(db.DateTime) status_id = db.Column(db.Integer, ForeignKey( @@ -131,6 +132,7 @@ def update_engagement(cls, engagement: EngagementSchema) -> Engagement: 'name': engagement.get('name', None), 'description': engagement.get('description', None), 'rich_description': engagement.get('rich_description', None), + 'description_title': engagement.get('description_title', None), 'start_date': engagement.get('start_date', None), 'end_date': engagement.get('end_date', None), 'status_id': engagement.get('status_id', None), diff --git a/met-api/src/met_api/models/engagement_translation.py b/met-api/src/met_api/models/engagement_translation.py index 8fe6691a7..ab395b231 100644 --- a/met-api/src/met_api/models/engagement_translation.py +++ b/met-api/src/met_api/models/engagement_translation.py @@ -22,6 +22,7 @@ class EngagementTranslation(BaseModel): name = db.Column(db.String(50)) description = db.Column(db.Text()) rich_description = db.Column(JSON, unique=False, nullable=True) + description_title = db.Column(db.String(255)) content = db.Column(db.Text()) rich_content = db.Column(JSON, unique=False, nullable=True) consent_message = db.Column(JSON, unique=False, nullable=True) diff --git a/met-api/src/met_api/models/widget_translation.py b/met-api/src/met_api/models/widget_translation.py index 88d6b7d68..75efe1a34 100644 --- a/met-api/src/met_api/models/widget_translation.py +++ b/met-api/src/met_api/models/widget_translation.py @@ -28,6 +28,7 @@ class WidgetTranslation(BaseModel): # pylint: disable=too-few-public-methods poll_title = db.Column(db.String(255)) poll_description = db.Column(db.String(2048)) video_url = db.Column(db.String(255)) + video_title = db.Column(db.Text(255)) video_description = db.Column(db.Text()) @classmethod @@ -62,6 +63,7 @@ def __create_new_widget_translation_entity(translation): poll_title=translation.get('poll_title', None), poll_description=translation.get('poll_description', None), video_url=translation.get('video_url', None), + video_title=translation.get('video_title', None), video_description=translation.get('video_description', None), ) diff --git a/met-api/src/met_api/models/widget_video.py b/met-api/src/met_api/models/widget_video.py index 82ad1d672..e20f4e3a5 100644 --- a/met-api/src/met_api/models/widget_video.py +++ b/met-api/src/met_api/models/widget_video.py @@ -18,6 +18,7 @@ class WidgetVideo(BaseModel): # pylint: disable=too-few-public-methods, too-man 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) + title = db.Column(db.String(255), nullable=True) description = db.Column(db.Text()) @classmethod diff --git a/met-api/src/met_api/schemas/engagement.py b/met-api/src/met_api/schemas/engagement.py index e405aeed0..33f1a01be 100644 --- a/met-api/src/met_api/schemas/engagement.py +++ b/met-api/src/met_api/schemas/engagement.py @@ -29,6 +29,7 @@ class Meta: # pylint: disable=too-few-public-methods name = fields.Str(data_key='name', required=True, validate=validate.Length(min=1, error='Name cannot be blank')) description = fields.Str(data_key='description') rich_description = fields.Str(data_key='rich_description') + description_title = fields.Str(data_key='description_title') start_date = fields.Date(data_key='start_date', required=True) end_date = fields.Date(data_key='end_date', required=True) status_id = fields.Int(data_key='status_id') diff --git a/met-api/src/met_api/schemas/engagement_translation.py b/met-api/src/met_api/schemas/engagement_translation.py index 6b2feac44..896d040da 100644 --- a/met-api/src/met_api/schemas/engagement_translation.py +++ b/met-api/src/met_api/schemas/engagement_translation.py @@ -17,6 +17,7 @@ class Meta: # pylint: disable=too-few-public-methods name = fields.Str(data_key='name') description = fields.Str(data_key='description') rich_description = fields.Str(data_key='rich_description') + description_title = fields.Str(data_key='description_title') content = fields.Str(data_key='content') rich_content = fields.Str(data_key='rich_content') consent_message = fields.Str(data_key='consent_message') diff --git a/met-api/src/met_api/schemas/schemas/subscribe_item_translation.json b/met-api/src/met_api/schemas/schemas/subscribe_item_translation.json index 4e99f8537..6812bbfb2 100644 --- a/met-api/src/met_api/schemas/schemas/subscribe_item_translation.json +++ b/met-api/src/met_api/schemas/schemas/subscribe_item_translation.json @@ -11,8 +11,9 @@ "subscribe_item_id": 1, "description": "Subscription description in Spanish", "rich_description": "Rich text subscription description in Spanish", + "description_title": "Description title in Spanish.", "call_to_action_text": "Subscribe Now", - "pre_populate" : false + "pre_populate": false }, { "language_id": 2, @@ -47,6 +48,12 @@ "title": "Rich Description", "description": "A more detailed and formatted translation of the subscribe item." }, + "description_title": { + "$id": "#/properties/description_title", + "type": "string", + "title": "Description Title", + "description": "A title that displays above your engagement description." + }, "call_to_action_text": { "$id": "#/properties/call_to_action_text", "type": "string", diff --git a/met-api/src/met_api/schemas/schemas/video_widget_update.json b/met-api/src/met_api/schemas/schemas/video_widget_update.json index 76c152338..22fd6979c 100644 --- a/met-api/src/met_api/schemas/schemas/video_widget_update.json +++ b/met-api/src/met_api/schemas/schemas/video_widget_update.json @@ -7,25 +7,33 @@ "default": {}, "examples": [ { - "description": "A video widget description", - "video_url": "https://www.youtube.com" + "description": "A video widget description", + "title": "A video widget title", + "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"] - } + "description": { + "$id": "#/properties/description", + "type": "string", + "title": "Video description", + "description": "The description of this video.", + "examples": ["A video widget description"] + }, + "title": { + "$id": "#/properties/title", + "type": "string", + "title": "Video title", + "description": "The title of this video.", + "examples": ["A video widget title"] + }, + "video_url": { + "$id": "#/properties/video_url", + "type": "string", + "title": "Video url", + "description": "The url link to this video.", + "examples": ["https://www.youtube.com"] + } } } diff --git a/met-api/src/met_api/schemas/widget_translation.py b/met-api/src/met_api/schemas/widget_translation.py index f7588d29b..123d56834 100644 --- a/met-api/src/met_api/schemas/widget_translation.py +++ b/met-api/src/met_api/schemas/widget_translation.py @@ -20,4 +20,5 @@ class Meta: # pylint: disable=too-few-public-methods poll_title = fields.Str(data_key='poll_title') poll_description = fields.Str(data_key='poll_description') video_url = fields.Str(data_key='video_url') + video_title = fields.Str(data_key='video_title') video_description = fields.Str(data_key='video_description') diff --git a/met-api/src/met_api/schemas/widget_video.py b/met-api/src/met_api/schemas/widget_video.py index 5f8b3af2c..4f7f414c0 100644 --- a/met-api/src/met_api/schemas/widget_video.py +++ b/met-api/src/met_api/schemas/widget_video.py @@ -25,4 +25,4 @@ 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') + fields = ('id', 'widget_id', 'engagement_id', 'video_url', 'title', 'description') diff --git a/met-api/src/met_api/services/engagement_service.py b/met-api/src/met_api/services/engagement_service.py index ad2a687fa..35f35d16e 100644 --- a/met-api/src/met_api/services/engagement_service.py +++ b/met-api/src/met_api/services/engagement_service.py @@ -175,6 +175,7 @@ def _create_engagement_model(engagement_data: dict) -> EngagementModel: name=engagement_data.get('name', None), description=engagement_data.get('description', None), rich_description=engagement_data.get('rich_description', None), + description_title=engagement_data.get('description_title', None), start_date=engagement_data.get('start_date', None), end_date=engagement_data.get('end_date', None), status_id=Status.Draft.value, diff --git a/met-api/src/met_api/services/engagement_translation_service.py b/met-api/src/met_api/services/engagement_translation_service.py index d902c9b68..32849c796 100644 --- a/met-api/src/met_api/services/engagement_translation_service.py +++ b/met-api/src/met_api/services/engagement_translation_service.py @@ -136,6 +136,7 @@ def _get_default_language_values(engagement, content, translation_data): translation_data['name'] = engagement.name translation_data['description'] = engagement.description translation_data['rich_description'] = engagement.rich_description + translation_data['description_title'] = engagement.description_title translation_data['content'] = content.text_content translation_data['rich_content'] = content.json_content translation_data['consent_message'] = engagement.consent_message diff --git a/met-api/src/met_api/services/widget_translation_service.py b/met-api/src/met_api/services/widget_translation_service.py index 58d4bdc39..4aa037ed6 100644 --- a/met-api/src/met_api/services/widget_translation_service.py +++ b/met-api/src/met_api/services/widget_translation_service.py @@ -130,6 +130,7 @@ def _get_default_language_values(widget, translation_data): widget_video = WidgetVideoModel.get_video(widget_id) if widget_video: translation_data['video_url'] = widget_video[0].video_url + translation_data['video_title'] = widget_video[0].title translation_data['video_description'] = widget_video[0].description return translation_data diff --git a/met-api/src/met_api/services/widget_video_service.py b/met-api/src/met_api/services/widget_video_service.py index 4595ef538..91d2c3899 100644 --- a/met-api/src/met_api/services/widget_video_service.py +++ b/met-api/src/met_api/services/widget_video_service.py @@ -47,6 +47,7 @@ def _create_video_model(widget_id, video_data: dict): 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.title = video_data.get('title') video_model.description = video_data.get('description') video_model.flush() return video_model diff --git a/met-api/tests/unit/api/test_widget_video.py b/met-api/tests/unit/api/test_widget_video.py index d2c6aa10e..91cc19cff 100644 --- a/met-api/tests/unit/api/test_widget_video.py +++ b/met-api/tests/unit/api/test_widget_video.py @@ -120,6 +120,7 @@ def test_patch_video(client, jwt, session, headers = factory_auth_header(jwt=jwt, claims=claims) video_edits = { + 'title': fake.text(max_nb_chars=20), 'description': fake.text(max_nb_chars=20), 'video_url': fake.url(), } @@ -136,6 +137,7 @@ def test_patch_video(client, jwt, session, content_type=ContentType.JSON.value ) assert rv.status_code == HTTPStatus.OK + assert rv.json[0].get('title') == video_edits.get('title') assert rv.json[0].get('description') == video_edits.get('description') with patch.object(WidgetVideoService, 'update_video', diff --git a/met-api/tests/utilities/factory_scenarios.py b/met-api/tests/utilities/factory_scenarios.py index a7c5f5fc3..77c96e4e9 100644 --- a/met-api/tests/utilities/factory_scenarios.py +++ b/met-api/tests/utilities/factory_scenarios.py @@ -194,7 +194,8 @@ class TestEngagementInfo(dict, Enum): 'content': 'Content Sample', 'rich_content': '"{\"blocks\":[{\"key\":\"fclgj\",\"text\":\"Rich Content Sample\",\ \"type\":\"unstyled\",\"depth\":0,\ - \"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}"' + \"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}"', + 'description_title': 'My Test Description Title', } engagement_draft = { @@ -210,6 +211,7 @@ class TestEngagementInfo(dict, Enum): 'rich_description': '"{\"blocks\":[{\"key\":\"2ku94\",\"text\":\"Rich Description Sample\",\ \"type\":\"unstyled\",\ \"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}"', + 'description_title': 'My Test Description Title', 'content': 'Content Sample', 'rich_content': '"{\"blocks\":[{\"key\":\"fclgj\",\"text\":\"Rich Content Sample\",\ \"type\":\"unstyled\",\"depth\":0,\ @@ -229,6 +231,7 @@ class TestEngagementInfo(dict, Enum): 'rich_description': '"{\"blocks\":[{\"key\":\"2ku94\",\"text\":\"Rich Description Sample\",\ \"type\":\"unstyled\",\ \"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}"', + 'description_title': 'My Test Description Title', 'content': 'Content Sample', 'rich_content': '"{\"blocks\":[{\"key\":\"fclgj\",\"text\":\"Rich Content Sample\",\ \"type\":\"unstyled\",\"depth\":0,\ @@ -256,6 +259,7 @@ class TestEngagementInfo(dict, Enum): 'rich_description': '"{\"blocks\":[{\"key\":\"2ku94\",\"text\":\"Rich Description Sample\",\ \"type\":\"unstyled\",\ \"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}"', + 'description_title': 'My Test Description Title', } @@ -991,6 +995,7 @@ class TestEngagementTranslationInfo(dict, Enum): 'rich_content': '"{\"blocks\":[{\"key\":\"fclgj\",\"text\":\"Rich Content Sample\",\ \"type\":\"unstyled\",\"depth\":0,\ \"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}"', + 'description_title': 'My Test Description Title', } diff --git a/met-api/tests/utilities/factory_utils.py b/met-api/tests/utilities/factory_utils.py index c21f85989..beef83f83 100644 --- a/met-api/tests/utilities/factory_utils.py +++ b/met-api/tests/utilities/factory_utils.py @@ -169,6 +169,7 @@ def factory_engagement_model(eng_info: dict = TestEngagementInfo.engagement1, na name=name if name else fake.name(), description=eng_info.get('description'), rich_description=eng_info.get('rich_description'), + description_title=eng_info.get('description_title'), created_by=eng_info.get('created_by'), updated_by=eng_info.get('updated_by'), status_id=status if status else eng_info.get('status'), @@ -525,6 +526,7 @@ def factory_video_model(video_info: dict = TestWidgetVideo.video1): """Produce a comment model.""" video = WidgetVideoModel( video_url=video_info.get('video_url'), + title=video_info.get('title'), description=video_info.get('description'), widget_id=video_info.get('widget_id'), engagement_id=video_info.get('engagement_id'), diff --git a/tools/postman/MET.postman_collection.json b/tools/postman/MET.postman_collection.json index b5f53845a..6ff9513aa 100644 --- a/tools/postman/MET.postman_collection.json +++ b/tools/postman/MET.postman_collection.json @@ -249,294 +249,277 @@ { "name": "Engagements", "item": [ - { - "name": "GET Engagements", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Response time is less than 10000ms\", function () {\r", - " pm.expect(pm.response.responseTime).to.be.below(10000);\r", - "});\r", - "\r", - "pm.test(\"Status code is 200\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test('should return JSON', function () {\r", - " pm.response.to.have.header('Content-Type', 'application/json');\r", - "});\r", - "\r", - "pm.test(\"Validate Engagement JSON Data\", () => {\r", - " var jsonData = pm.response.json().result;\r", - " console.log(jsonData);\r", - " for (var i=0; i {\r", - " var jsonData = pm.response.json().result;\r", - " pm.expect(jsonData.id).to.exist\r", - " pm.expect(jsonData.name).to.exist\r", - " pm.expect(jsonData.banner_filename).to.exist\r", - " pm.expect(jsonData.created_by).to.exist\r", - " pm.expect(jsonData.content).to.exist\r", - " pm.expect(jsonData.rich_content).to.exist\r", - " pm.expect(jsonData.rich_description).to.exist\r", - " pm.expect(jsonData.description).to.exist\r", - "\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "clientId", - "value": "met-web", - "type": "string" - }, - { - "key": "accessTokenUrl", - "value": "https://met-oidc-dev.apps.gold.devops.gov.bc.ca/auth/realms/met/protocol/openid-connect/token", - "type": "string" - }, - { - "key": "authUrl", - "value": "https://met-oidc-dev.apps.gold.devops.gov.bc.ca/auth/realms/met/protocol/openid-connect/auth", - "type": "string" - }, - { - "key": "redirect_uri", - "value": "https://met-web-dev.apps.gold.devops.gov.bc.ca/", - "type": "string" - }, - { - "key": "tokenName", - "value": "met", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - } - ] - }, - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\": \"{{ENGAGEMENT_NAME}}\",\r\n \"start_date\": \"{{ENGAGEMENT_START_DATE}}\",\r\n \"status_id\": {{ENGAGEMENT_STATUS_ID}},\r\n \"end_date\": \"{{ENGAGEMENT_END_DATE}}\",\r\n \"description\": \"{{ENGAGEMENT_DESCRIPTION}}\",\r\n \"rich_description\": \"{\\\"blocks\\\":[{\\\"key\\\":\\\"668ab\\\",\\\"text\\\":\\\"dsadsa\\\",\\\"type\\\":\\\"unstyled\\\",\\\"depth\\\":0,\\\"inlineStyleRanges\\\":[],\\\"entityRanges\\\":[],\\\"data\\\":{}}],\\\"entityMap\\\":{}}\",\r\n \"content\": \"dsadsa\",\r\n \"rich_content\": \"{\\\"blocks\\\":[{\\\"key\\\":\\\"1v676\\\",\\\"text\\\":\\\"dsadsa\\\",\\\"type\\\":\\\"unstyled\\\",\\\"depth\\\":0,\\\"inlineStyleRanges\\\":[],\\\"entityRanges\\\":[],\\\"data\\\":{}}],\\\"entityMap\\\":{}}\",\r\n \"banner_filename\": \"{{ENGAGEMENT_BANNER_FILENAME}}\"\r\n}\r\n", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{API_URL}}/api/engagements/", - "host": [ - "{{API_URL}}" - ], - "path": [ - "api", - "engagements", - "" - ] - } - }, - "response": [] - }, - { - "name": "Get Engagement", - "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - }, - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Response time is less than 10000ms\", function () {\r", - " pm.expect(pm.response.responseTime).to.be.below(10000);\r", - "});\r", - "\r", - "pm.test(\"Status code is 200\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test('should return JSON', function () {\r", - " pm.response.to.have.header('Content-Type', 'application/json');\r", - "});\r", - "\r", - "pm.test(\"Validate Engagement JSON Data\", () => {\r", - " var jsonData = pm.response.json().result;\r", - " console.log(jsonData);\r", - " pm.expect(jsonData.id).to.exist\r", - " pm.expect(jsonData.name).to.exist\r", - " pm.expect(jsonData.banner_filename).to.exist\r", - " pm.expect(jsonData.created_by).to.exist\r", - " pm.expect(jsonData.created_date).to.exist\r", - " pm.expect(jsonData.content).to.exist\r", - " pm.expect(jsonData.rich_description).to.exist\r", - " pm.expect(jsonData.description).to.exist\r", - "\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "followAuthorizationHeader": false - }, - "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "useBrowser", - "value": false, - "type": "boolean" - }, - { - "key": "clientId", - "value": "met-web", - "type": "string" - }, - { - "key": "accessTokenUrl", - "value": "https://met-oidc-dev.apps.gold.devops.gov.bc.ca/auth/realms/met/protocol/openid-connect/token", - "type": "string" - }, - { - "key": "authUrl", - "value": "https://met-oidc-dev.apps.gold.devops.gov.bc.ca/auth/realms/met/protocol/openid-connect/auth", - "type": "string" - }, - { - "key": "redirect_uri", - "value": "https://met-web-dev.apps.gold.devops.gov.bc.ca/", - "type": "string" - }, - { - "key": "tokenName", - "value": "met", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - } - ] - }, - "method": "GET", - "header": [], - "url": { - "raw": "{{API_URL}}/api/engagements/{{ENGAGEMENT_ID}}", - "host": [ - "{{API_URL}}" - ], - "path": [ - "api", - "engagements", - "{{ENGAGEMENT_ID}}" - ] - } - }, - "response": [] - } - ] + { + "name": "GET Engagements", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Response time is less than 10000ms\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(10000);\r", + "});\r", + "\r", + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test('should return JSON', function () {\r", + " pm.response.to.have.header('Content-Type', 'application/json');\r", + "});\r", + "\r", + "pm.test(\"Validate Engagement JSON Data\", () => {\r", + " var jsonData = pm.response.json().result;\r", + " console.log(jsonData);\r", + " for (var i=0; i {\r", + " var jsonData = pm.response.json().result;\r", + " pm.expect(jsonData.id).to.exist\r", + " pm.expect(jsonData.name).to.exist\r", + " pm.expect(jsonData.banner_filename).to.exist\r", + " pm.expect(jsonData.created_by).to.exist\r", + " pm.expect(jsonData.content).to.exist\r", + " pm.expect(jsonData.rich_content).to.exist\r", + " pm.expect(jsonData.rich_description).to.exist\r", + " pm.expect(jsonData.description).to.exist\r", + " pm.expect(jsonData.description_title).to.exist\r", + "\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "oauth2", + "oauth2": [ + { + "key": "clientId", + "value": "met-web", + "type": "string" + }, + { + "key": "accessTokenUrl", + "value": "https://met-oidc-dev.apps.gold.devops.gov.bc.ca/auth/realms/met/protocol/openid-connect/token", + "type": "string" + }, + { + "key": "authUrl", + "value": "https://met-oidc-dev.apps.gold.devops.gov.bc.ca/auth/realms/met/protocol/openid-connect/auth", + "type": "string" + }, + { + "key": "redirect_uri", + "value": "https://met-web-dev.apps.gold.devops.gov.bc.ca/", + "type": "string" + }, + { + "key": "tokenName", + "value": "met", + "type": "string" + }, + { + "key": "addTokenTo", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"{{ENGAGEMENT_NAME}}\",\r\n \"start_date\": \"{{ENGAGEMENT_START_DATE}}\",\r\n \"status_id\": {{ENGAGEMENT_STATUS_ID}},\r\n \"end_date\": \"{{ENGAGEMENT_END_DATE}}\",\r\n \"description\": \"{{ENGAGEMENT_DESCRIPTION}}\",\r\n \"description_title\": \"{{ENGAGEMENT_DESCRIPTION_TITLE}}\",\r\n \"rich_description\": \"{\\\"blocks\\\":[{\\\"key\\\":\\\"668ab\\\",\\\"text\\\":\\\"dsadsa\\\",\\\"type\\\":\\\"unstyled\\\",\\\"depth\\\":0,\\\"inlineStyleRanges\\\":[],\\\"entityRanges\\\":[],\\\"data\\\":{}}],\\\"entityMap\\\":{}}\",\r\n \"content\": \"dsadsa\",\r\n \"rich_content\": \"{\\\"blocks\\\":[{\\\"key\\\":\\\"1v676\\\",\\\"text\\\":\\\"dsadsa\\\",\\\"type\\\":\\\"unstyled\\\",\\\"depth\\\":0,\\\"inlineStyleRanges\\\":[],\\\"entityRanges\\\":[],\\\"data\\\":{}}],\\\"entityMap\\\":{}}\",\r\n \"banner_filename\": \"{{ENGAGEMENT_BANNER_FILENAME}}\"\r\n}\r\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/engagements/", + "host": ["{{API_URL}}"], + "path": ["api", "engagements", ""] + } + }, + "response": [] + }, + { + "name": "Get Engagement", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [""], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Response time is less than 10000ms\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(10000);\r", + "});\r", + "\r", + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test('should return JSON', function () {\r", + " pm.response.to.have.header('Content-Type', 'application/json');\r", + "});\r", + "\r", + "pm.test(\"Validate Engagement JSON Data\", () => {\r", + " var jsonData = pm.response.json().result;\r", + " console.log(jsonData);\r", + " pm.expect(jsonData.id).to.exist\r", + " pm.expect(jsonData.name).to.exist\r", + " pm.expect(jsonData.banner_filename).to.exist\r", + " pm.expect(jsonData.created_by).to.exist\r", + " pm.expect(jsonData.created_date).to.exist\r", + " pm.expect(jsonData.content).to.exist\r", + " pm.expect(jsonData.rich_description).to.exist\r", + " pm.expect(jsonData.description).to.exist\r", + "\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "followAuthorizationHeader": false + }, + "request": { + "auth": { + "type": "oauth2", + "oauth2": [ + { + "key": "useBrowser", + "value": false, + "type": "boolean" + }, + { + "key": "clientId", + "value": "met-web", + "type": "string" + }, + { + "key": "accessTokenUrl", + "value": "https://met-oidc-dev.apps.gold.devops.gov.bc.ca/auth/realms/met/protocol/openid-connect/token", + "type": "string" + }, + { + "key": "authUrl", + "value": "https://met-oidc-dev.apps.gold.devops.gov.bc.ca/auth/realms/met/protocol/openid-connect/auth", + "type": "string" + }, + { + "key": "redirect_uri", + "value": "https://met-web-dev.apps.gold.devops.gov.bc.ca/", + "type": "string" + }, + { + "key": "tokenName", + "value": "met", + "type": "string" + }, + { + "key": "addTokenTo", + "value": "header", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{API_URL}}/api/engagements/{{ENGAGEMENT_ID}}", + "host": ["{{API_URL}}"], + "path": ["api", "engagements", "{{ENGAGEMENT_ID}}"] + } + }, + "response": [] + } + ] }, { "name": "Comment", @@ -984,127 +967,133 @@ } ], "variable": [ - { - "key": "API_URL", - "value": "", - "type": "string" - }, - { - "key": "SURVEY_ID", - "value": "", - "type": "string" - }, - { - "key": "SURVEY_NAME", - "value": "", - "type": "string" - }, - { - "key": "COMMENT_ID", - "value": "", - "type": "string" - }, - { - "key": "ENGAGEMENT_ID", - "value": "", - "type": "string" - }, - { - "key": "ENGAGEMENT_NAME", - "value": "", - "type": "string" - }, - { - "key": "ENGAGEMENT_DESCRIPTION", - "value": "", - "type": "string", - "disabled": true - }, - { - "key": "ENGAGEMENT_RICH_DESCRIPTION", - "value": "Rich Description", - "type": "string", - "disabled": true - }, - { - "key": "ENGAGEMENT_START_DATE", - "value": "2022-09-23", - "type": "string" - }, - { - "key": "ENGAGEMENT_END_DATE", - "value": "2022-09-23", - "type": "string" - }, - { - "key": "ENGAGEMENT_STATUS_ID", - "value": "1", - "type": "string" - }, - { - "key": "ENGAGEMENT_CREATED_BY", - "value": "David", - "type": "string" - }, - { - "key": "ENGAGEMENT_CREATED_DATE", - "value": "2022-09-23", - "type": "string" - }, - { - "key": "ENGAGEMENT_BANNER_FILENAME", - "value": "", - "type": "string" - }, - { - "key": "ENGAGEMENT_CONTENT", - "value": "Content", - "type": "string" - }, - { - "key": "ENGAGEMENT_RICH_CONTENT", - "value": "Rich Content", - "type": "string" - }, - { - "key": "ENGAGEMENT_PUBLISHED_DATE", - "value": "2022-09-23", - "type": "string" - }, - { - "key": "ENGAGEMENT_UPDATED_DATE", - "value": "2022-09-23", - "type": "string" - }, - { - "key": "FEEDBACK_RATING", - "value": "", - "type": "string" - }, - { - "key": "FEEDBACK_COMMENT", - "value": "US", - "type": "string" - }, - { - "key": "FEEDBACK_SUBMISSION_PATH", - "value": "US", - "type": "string" - }, - { - "key": "FEEDBACK_SOURCE", - "value": "", - "type": "string" - }, - { - "key": "FEEDBACK_CREATED_DATE", - "value": "", - "type": "string" - }, - { - "key": "FEEDBACK_COMMENT_TYPE", - "value": "", - "type": "string" - } - ] + { + "key": "API_URL", + "value": "", + "type": "string" + }, + { + "key": "SURVEY_ID", + "value": "", + "type": "string" + }, + { + "key": "SURVEY_NAME", + "value": "", + "type": "string" + }, + { + "key": "COMMENT_ID", + "value": "", + "type": "string" + }, + { + "key": "ENGAGEMENT_ID", + "value": "", + "type": "string" + }, + { + "key": "ENGAGEMENT_NAME", + "value": "", + "type": "string" + }, + { + "key": "ENGAGEMENT_DESCRIPTION", + "value": "", + "type": "string", + "disabled": true + }, + { + "key": "ENGAGEMENT_DESCRIPTION_TITLE", + "value": "", + "type": "string", + "disabled": true + }, + { + "key": "ENGAGEMENT_RICH_DESCRIPTION", + "value": "Rich Description", + "type": "string", + "disabled": true + }, + { + "key": "ENGAGEMENT_START_DATE", + "value": "2022-09-23", + "type": "string" + }, + { + "key": "ENGAGEMENT_END_DATE", + "value": "2022-09-23", + "type": "string" + }, + { + "key": "ENGAGEMENT_STATUS_ID", + "value": "1", + "type": "string" + }, + { + "key": "ENGAGEMENT_CREATED_BY", + "value": "David", + "type": "string" + }, + { + "key": "ENGAGEMENT_CREATED_DATE", + "value": "2022-09-23", + "type": "string" + }, + { + "key": "ENGAGEMENT_BANNER_FILENAME", + "value": "", + "type": "string" + }, + { + "key": "ENGAGEMENT_CONTENT", + "value": "Content", + "type": "string" + }, + { + "key": "ENGAGEMENT_RICH_CONTENT", + "value": "Rich Content", + "type": "string" + }, + { + "key": "ENGAGEMENT_PUBLISHED_DATE", + "value": "2022-09-23", + "type": "string" + }, + { + "key": "ENGAGEMENT_UPDATED_DATE", + "value": "2022-09-23", + "type": "string" + }, + { + "key": "FEEDBACK_RATING", + "value": "", + "type": "string" + }, + { + "key": "FEEDBACK_COMMENT", + "value": "US", + "type": "string" + }, + { + "key": "FEEDBACK_SUBMISSION_PATH", + "value": "US", + "type": "string" + }, + { + "key": "FEEDBACK_SOURCE", + "value": "", + "type": "string" + }, + { + "key": "FEEDBACK_CREATED_DATE", + "value": "", + "type": "string" + }, + { + "key": "FEEDBACK_COMMENT_TYPE", + "value": "", + "type": "string" + } + ] } \ No newline at end of file From a4348669ef1365d268866f582d00c707432ed2ee Mon Sep 17 00:00:00 2001 From: Jareth Whitney Date: Wed, 18 Sep 2024 17:13:32 -0700 Subject: [PATCH 3/6] feature/deseng692 and deseng671: Updated changelog. --- CHANGELOG.MD | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 92554611f..acd65576a 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,4 +1,12 @@ +## September 18, 2024 + +- **Feature** New Video Widget front end [🎟️ DESENG-692](https://citz-gdx.atlassian.net/browse/DESENG-692) + - Implemented Figma design + - Created custom layover bar for videos that shows video provider and has a custom logo + - Transcripts will not be implemented at this time + ## September 12, 2024 + - **Feature** New Summary page in authoring section [🎟️ DESENG-671](https://citz-gdx.atlassian.net/browse/DESENG-671) - Fetches values - Saves values to database From edc18335104beae8757abc46990ae764c20657ba Mon Sep 17 00:00:00 2001 From: Jareth Whitney Date: Wed, 18 Sep 2024 17:58:59 -0700 Subject: [PATCH 4/6] feature/deseng692: Updateded strictness of URL checks as per the Copilot suggestion. --- .../old-view/widgets/Video/VideoWidgetView.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/met-web/src/components/engagement/old-view/widgets/Video/VideoWidgetView.tsx b/met-web/src/components/engagement/old-view/widgets/Video/VideoWidgetView.tsx index e902d48d0..e09f96854 100644 --- a/met-web/src/components/engagement/old-view/widgets/Video/VideoWidgetView.tsx +++ b/met-web/src/components/engagement/old-view/widgets/Video/VideoWidgetView.tsx @@ -84,19 +84,20 @@ const VideoWidgetView = ({ widget }: VideoWidgetProps) => { }; const getVideoSource = (url: string) => { - if (url.includes('youtube.com') || url.includes('youtu.be')) { + const hostname = new URL(url).hostname; + if ('youtube.com' === hostname || 'youtu.be' === hostname) { return 'YouTube'; - } else if (url.includes('vimeo.com')) { + } else if ('vimeo.com' === hostname) { return 'Vimeo'; - } else if (url.includes('facebook.com')) { + } else if ('facebook.com' === hostname) { return 'Facebook'; - } else if (url.includes('twitch.tv')) { + } else if ('twitch.tv' === hostname) { return 'Twitch'; - } else if (url.includes('soundcloud.com')) { + } else if ('soundcloud.com' === hostname) { return 'SoundCloud'; - } else if (url.includes('mixcloud.com')) { + } else if ('mixcloud.com' === hostname) { return 'Mixcloud'; - } else if (url.includes('dailymotion.com')) { + } else if ('dailymotion.com' === hostname) { return 'DailyMotion'; } else { return 'Unknown'; From da88a8ae480e1736c8347cea07aeb70cdb0b2b5b Mon Sep 17 00:00:00 2001 From: Jareth Whitney Date: Thu, 19 Sep 2024 22:24:56 -0700 Subject: [PATCH 5/6] feature/deseng692: Made revisions to PR as per Nat and Alex's comments, fixed issues with deferred data aborted errors. --- CHANGELOG.MD | 5 + .../create/authoring/AuthoringContext.tsx | 9 +- .../create/authoring/AuthoringSummary.tsx | 36 ++--- .../create/authoring/AuthoringTemplate.tsx | 49 ++++--- .../form/EngagementWidgets/Video/Form.tsx | 5 +- .../widgets/Video/VideoWidgetView.tsx | 130 ++++++++---------- met-web/src/routes/AuthenticatedRoutes.tsx | 4 +- 7 files changed, 108 insertions(+), 130 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index acd65576a..5e80b1a5e 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -5,6 +5,11 @@ - Created custom layover bar for videos that shows video provider and has a custom logo - Transcripts will not be implemented at this time +- Summary page in authoring section [🎟️ DESENG-671](https://citz-gdx.atlassian.net/browse/DESENG-671) + - Streamlined data loaders and actions to account for page changes + - Updated data structure so that summary data is pulled from engagement table + - Added 'description_title' column to engagement table + ## September 12, 2024 - **Feature** New Summary page in authoring section [🎟️ DESENG-671](https://citz-gdx.atlassian.net/browse/DESENG-671) diff --git a/met-web/src/components/engagement/admin/create/authoring/AuthoringContext.tsx b/met-web/src/components/engagement/admin/create/authoring/AuthoringContext.tsx index 35af10549..d5c116d3c 100644 --- a/met-web/src/components/engagement/admin/create/authoring/AuthoringContext.tsx +++ b/met-web/src/components/engagement/admin/create/authoring/AuthoringContext.tsx @@ -82,7 +82,7 @@ export const AuthoringContext = () => { title: data.title, icon_name: data.icon_name, metadata_value: data.metadata_value, - send_report: data.send_report ? getSendReportValue(data.send_report) : '', + send_report: (data.send_report || '').toString(), slug: data.slug, request_type: data.request_type, text_content: data.text_content, @@ -95,13 +95,6 @@ export const AuthoringContext = () => { ); }; - const getSendReportValue = (valueToInterpret: boolean) => { - if (undefined === valueToInterpret) { - return ''; - } - return valueToInterpret ? 'true' : 'false'; - }; - return ( diff --git a/met-web/src/components/engagement/admin/create/authoring/AuthoringSummary.tsx b/met-web/src/components/engagement/admin/create/authoring/AuthoringSummary.tsx index 03f4cc729..b2f34c65b 100644 --- a/met-web/src/components/engagement/admin/create/authoring/AuthoringSummary.tsx +++ b/met-web/src/components/engagement/admin/create/authoring/AuthoringSummary.tsx @@ -1,6 +1,6 @@ import { Grid } from '@mui/material'; import React, { useEffect } from 'react'; -import { useOutletContext, useRouteLoaderData, useNavigate } from 'react-router-dom'; +import { useLoaderData, useOutletContext } from 'react-router-dom'; import { TextField } from 'components/common/Input'; import { AuthoringTemplateOutletContext } from './types'; import { colors } from 'styles/Theme'; @@ -16,20 +16,9 @@ import { EngagementUpdateData } from './AuthoringContext'; import { Engagement } from 'models/engagement'; const AuthoringSummary = () => { - const { setValue, control, reset, getValues, setDefaultValues, fetcher, slug }: AuthoringTemplateOutletContext = + const { setValue, control, reset, getValues, setDefaultValues, fetcher }: AuthoringTemplateOutletContext = useOutletContext(); // Access the form functions and values from the authoring template. - // Update the loader data when the authoring section is changed, by triggering navigation(). - const navigate = useNavigate(); - useEffect(() => { - engagementData.then(() => navigate('.', { replace: true })); - }, [slug]); - - // Get the engagement data - const { engagement: engagementData } = useRouteLoaderData('single-engagement') as { - engagement: Promise; - }; - // Check if the form has succeeded or failed after a submit, and issue a message to the user. const dispatch = useAppDispatch(); useEffect(() => { @@ -47,6 +36,8 @@ const AuthoringSummary = () => { } }, [fetcher.data]); + const { engagement } = useLoaderData() as { engagement: Promise }; + const untouchedDefaultValues: EngagementUpdateData = { id: 0, status_id: 0, @@ -74,21 +65,22 @@ const AuthoringSummary = () => { // Reset values to default and retrieve relevant content from loader. useEffect(() => { reset(untouchedDefaultValues); - engagementData.then((engagement) => { - setValue('id', Number(engagement.id)); + engagement.then((eng) => { + setValue('id', Number(eng.id)); // Make sure it is valid JSON. - if (tryParse(engagement.rich_description)) { - setValue('rich_description', engagement.rich_description); + if (tryParse(eng.rich_description)) { + setValue('rich_description', eng.rich_description); } - setValue('description_title', engagement.description_title || 'Hello world'); - setValue('description', engagement.description); + setValue('description_title', eng.description_title || 'Hello world'); + setValue('description', eng.description); setValue( 'summary_editor_state', - EditorState.createWithContent(convertFromRaw(JSON.parse(engagement.rich_description))), + EditorState.createWithContent(convertFromRaw(JSON.parse(eng.rich_description))), ); - setDefaultValues(getValues()); // Update default values so that our loaded values are default. + // Update default values so that our loaded values are default. + setDefaultValues(getValues()); }); - }, [engagementData]); + }, [engagement]); // Define the styles const metBigLabelStyles = { diff --git a/met-web/src/components/engagement/admin/create/authoring/AuthoringTemplate.tsx b/met-web/src/components/engagement/admin/create/authoring/AuthoringTemplate.tsx index 95ab178d2..851247147 100644 --- a/met-web/src/components/engagement/admin/create/authoring/AuthoringTemplate.tsx +++ b/met-web/src/components/engagement/admin/create/authoring/AuthoringTemplate.tsx @@ -1,17 +1,15 @@ import React, { Suspense, useMemo, useState } from 'react'; -import { useOutletContext, Form, useParams, useRouteLoaderData, Await, Outlet } from 'react-router-dom'; +import { useOutletContext, Form, useParams, Await, Outlet, useLoaderData } from 'react-router-dom'; import AuthoringBottomNav from './AuthoringBottomNav'; import { EngagementUpdateData } from './AuthoringContext'; import { useFormContext } from 'react-hook-form'; import UnsavedWorkConfirmation from 'components/common/Navigation/UnsavedWorkConfirmation'; import { AuthoringContextType, StatusLabelProps } from './types'; -import { Engagement } from 'models/engagement'; import { AutoBreadcrumbs } from 'components/common/Navigation/Breadcrumb'; import { ResponsiveContainer } from 'components/common/Layout'; import { EngagementStatus } from 'constants/engagementStatus'; import { EyebrowText, Header2 } from 'components/common/Typography'; import { useAppSelector } from 'hooks'; -import { getTenantLanguages } from 'services/languageService'; import { Language } from 'models/language'; import { getAuthoringRoutes } from './AuthoringNavElements'; import { FormControlLabel, Grid, Radio, RadioGroup } from '@mui/material'; @@ -20,6 +18,8 @@ import { getEditorStateFromRaw } from 'components/common/RichTextEditor/utils'; import { When } from 'react-if'; import WidgetPicker from '../widgets'; import { WidgetLocation } from 'models/widget'; +import { Engagement } from 'models/engagement'; +import { getTenantLanguages } from 'services/languageService'; export const StatusLabel = ({ text, completed }: StatusLabelProps) => { const statusLabelStyle = { @@ -39,7 +39,7 @@ export const getLanguageValue = (currentLanguage: string, languages: Language[]) const AuthoringTemplate = () => { const { onSubmit, defaultValues, setDefaultValues, fetcher }: AuthoringContextType = useOutletContext(); const { engagementId } = useParams() as { engagementId: string }; // We need the engagement ID quickly, so let's grab it from useParams - const { engagement } = useRouteLoaderData('single-engagement') as { engagement: Engagement }; + const { engagement } = useLoaderData() as { engagement: Promise }; const [currentLanguage, setCurrentLanguage] = useState(useAppSelector((state) => state.language.id)); const [contentTabsEnabled, setContentTabsEnabled] = useState('false'); // todo: replace default value with stored value in engagement. const defaultTabValues = { @@ -50,6 +50,7 @@ const AuthoringTemplate = () => { }; const [tabs, setTabs] = useState([defaultTabValues]); const [singleContentValues, setSingleContentValues] = useState({ ...defaultTabValues, heading: '' }); + const tenant = useAppSelector((state) => state.tenant); const languages = useMemo(() => getTenantLanguages(tenant.id), [tenant.id]); // todo: Using tenant language list until language data is integrated with the engagement. const authoringRoutes = getAuthoringRoutes(Number(engagementId), tenant); @@ -61,6 +62,7 @@ const AuthoringTemplate = () => { const pathSlug = pathArray[pathArray.length - 1]; return pathSlug === slug; })?.name; + const { handleSubmit, setValue, @@ -95,17 +97,17 @@ const AuthoringTemplate = () => { return ( - - - {(engagement: Engagement) => ( -
- - {/* todo: For the section status label when it's ready */} - {/* */} -
- )} -
-
+
+ + + {(engagement: Engagement) => ( + + )} + + + {/* todo: For the section status label when it's ready */} + {/* */} +

{pageTitle}

Under construction - the settings in this section have no effect. @@ -151,13 +153,16 @@ const AuthoringTemplate = () => { - - - - {(languages: Language[]) => getLanguageValue(currentLanguage, languages) + ' Content'} - - - + + + {(languages: Language[]) => ( + + {`${getLanguageValue(currentLanguage, languages)} Content`} + + )} + + + diff --git a/met-web/src/components/engagement/form/EngagementWidgets/Video/Form.tsx b/met-web/src/components/engagement/form/EngagementWidgets/Video/Form.tsx index 70af54106..bddd953be 100644 --- a/met-web/src/components/engagement/form/EngagementWidgets/Video/Form.tsx +++ b/met-web/src/components/engagement/form/EngagementWidgets/Video/Form.tsx @@ -136,7 +136,7 @@ const Form = () => { - + { name="title" variant="outlined" label=" " + aria-label="Title: optional." InputLabelProps={{ shrink: false, }} @@ -162,6 +163,7 @@ const Form = () => { name="description" variant="outlined" label=" " + aria-label="Description: optional." InputLabelProps={{ shrink: false, }} @@ -179,6 +181,7 @@ const Form = () => { name="videoUrl" variant="outlined" label=" " + aria-label="Video URL: required." InputLabelProps={{ shrink: false, }} diff --git a/met-web/src/components/engagement/old-view/widgets/Video/VideoWidgetView.tsx b/met-web/src/components/engagement/old-view/widgets/Video/VideoWidgetView.tsx index e09f96854..11d228069 100644 --- a/met-web/src/components/engagement/old-view/widgets/Video/VideoWidgetView.tsx +++ b/met-web/src/components/engagement/old-view/widgets/Video/VideoWidgetView.tsx @@ -19,13 +19,25 @@ import { faMixcloud, faDailymotion, } from '@fortawesome/free-brands-svg-icons'; -import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; +import { faQuestionCircle, IconDefinition } from '@fortawesome/free-solid-svg-icons'; import { Palette } from 'styles/Theme'; interface VideoWidgetProps { widget: Widget; } +interface WidgetVideoSource { + domain: string; + name: string; + icon: IconDefinition; +} + +interface VideoOverlayProps { + videoOverlayTitle: string; + source: string; + videoSources: WidgetVideoSource[]; +} + const VideoWidgetView = ({ widget }: VideoWidgetProps) => { const theme = useTheme(); const dispatch = useAppDispatch(); @@ -63,6 +75,29 @@ const VideoWidgetView = ({ widget }: VideoWidgetProps) => { } }; + useEffect(() => { + fetchVideo(); + }, [widget]); + + if (isLoading) { + return ( + + + + + + + + + + + ); + } + + if (!videoWidget) { + return null; + } + const formatVideoDuration = (duration: number) => { let minutes = 0; let hours = 0; @@ -83,26 +118,18 @@ const VideoWidgetView = ({ widget }: VideoWidgetProps) => { } }; - const getVideoSource = (url: string) => { - const hostname = new URL(url).hostname; - if ('youtube.com' === hostname || 'youtu.be' === hostname) { - return 'YouTube'; - } else if ('vimeo.com' === hostname) { - return 'Vimeo'; - } else if ('facebook.com' === hostname) { - return 'Facebook'; - } else if ('twitch.tv' === hostname) { - return 'Twitch'; - } else if ('soundcloud.com' === hostname) { - return 'SoundCloud'; - } else if ('mixcloud.com' === hostname) { - return 'Mixcloud'; - } else if ('dailymotion.com' === hostname) { - return 'DailyMotion'; - } else { - return 'Unknown'; - } - }; + const videoSources: WidgetVideoSource[] = [ + { domain: 'youtu.be', name: 'YouTube', icon: faYoutube }, + { domain: 'vimeo.com', name: 'Vimeo', icon: faVimeo }, + { domain: 'twitch.tv', name: 'Twitch', icon: faTwitch }, + { domain: 'soundcloud.com', name: 'SoundCloud', icon: faSoundcloud }, + { domain: 'mixcloud.com', name: 'Mixcloud', icon: faMixcloud }, + { domain: 'dailymotion.com', name: 'DailyMotion', icon: faDailymotion }, + { domain: 'facebook.com', name: 'Facebook', icon: faFacebookF }, + ]; + + const hostname = new URL(videoWidget.video_url).hostname; + const videoSource = videoSources.find((source) => source.domain === hostname)?.name || 'Unknown'; const getVideoTitle = (player: ReactPlayer, source: string, widgetTitle: string) => { if ('YouTube' === source) { @@ -118,29 +145,6 @@ const VideoWidgetView = ({ widget }: VideoWidgetProps) => { } }; - useEffect(() => { - fetchVideo(); - }, [widget]); - - if (isLoading) { - return ( - - - - - - - - - - - ); - } - - if (!videoWidget) { - return null; - } - return ( @@ -164,11 +168,12 @@ const VideoWidgetView = ({ widget }: VideoWidgetProps) => { {/* Overlay covers play button for Mixcloud so it shouldn't be displayed */} - + @@ -181,7 +186,6 @@ const VideoWidgetView = ({ widget }: VideoWidgetProps) => { url={videoWidget.video_url} controls onReady={(player) => { - const videoSource = getVideoSource(videoWidget.video_url); setVideoOverlayTitle(getVideoTitle(player, videoSource, videoWidget.title)); }} onPlay={() => setShowOverlay(false)} @@ -200,7 +204,7 @@ const VideoWidgetView = ({ widget }: VideoWidgetProps) => { {videoDuration} @@ -209,31 +213,7 @@ const VideoWidgetView = ({ widget }: VideoWidgetProps) => { ); }; -interface VideoOverlayProps { - videoOverlayTitle: string; - source: string; -} - -const VideoOverlay = ({ videoOverlayTitle, source }: VideoOverlayProps) => { - const getIcon = (source: string) => { - if ('YouTube' === source) { - return faYoutube; - } else if ('Vimeo' === source) { - return faVimeo; - } else if ('Facebook' === source) { - return faFacebookF; - } else if ('Twitch' === source) { - return faTwitch; - } else if ('SoundCloud' === source) { - return faSoundcloud; - } else if ('Mixcloud' === source) { - return faMixcloud; - } else if ('DailyMotion' === source) { - return faDailymotion; - } else { - return faQuestionCircle; - } - }; +const VideoOverlay = ({ videoOverlayTitle, source, videoSources }: VideoOverlayProps) => { return ( { }} > vs.name === source)?.icon || faQuestionCircle} style={{ fontSize: '1.9rem', paddingRight: '0.65rem', color: '#FCBA19' }} />{' '} - {videoOverlayTitle} + {videoOverlayTitle} ); diff --git a/met-web/src/routes/AuthenticatedRoutes.tsx b/met-web/src/routes/AuthenticatedRoutes.tsx index 8651ea503..6150a41ff 100644 --- a/met-web/src/routes/AuthenticatedRoutes.tsx +++ b/met-web/src/routes/AuthenticatedRoutes.tsx @@ -52,7 +52,6 @@ import AuthoringFeedback from 'components/engagement/admin/create/authoring/Auth import AuthoringResults from 'components/engagement/admin/create/authoring/AuthoringResults'; import AuthoringSubscribe from 'components/engagement/admin/create/authoring/AuthoringSubscribe'; import AuthoringMore from 'components/engagement/admin/create/authoring/AuthoringMore'; -import { authoringLoader } from 'components/engagement/admin/create/authoring/authoringLoader'; const AuthenticatedRoutes = () => { return ( @@ -131,7 +130,7 @@ const AuthenticatedRoutes = () => { element={} > }> - } loader={authoringLoader} id="authoring-loader"> + } id="authoring-loader" loader={engagementLoader}> } @@ -145,6 +144,7 @@ const AuthenticatedRoutes = () => { /> } handle={{ From 132ccb0f777af603dd0d6a20fc00793f1387f936 Mon Sep 17 00:00:00 2001 From: Jareth Whitney Date: Fri, 20 Sep 2024 12:39:21 -0700 Subject: [PATCH 6/6] feature/deseng692: Simplified description title controller, small misc changes as per Nat. --- .../create/authoring/AuthoringSummary.tsx | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/met-web/src/components/engagement/admin/create/authoring/AuthoringSummary.tsx b/met-web/src/components/engagement/admin/create/authoring/AuthoringSummary.tsx index b2f34c65b..e52d526cb 100644 --- a/met-web/src/components/engagement/admin/create/authoring/AuthoringSummary.tsx +++ b/met-web/src/components/engagement/admin/create/authoring/AuthoringSummary.tsx @@ -13,7 +13,7 @@ import { useAppDispatch } from 'hooks'; import { openNotification } from 'services/notificationService/notificationSlice'; import dayjs from 'dayjs'; import { EngagementUpdateData } from './AuthoringContext'; -import { Engagement } from 'models/engagement'; +import { EngagementLoaderData } from 'components/engagement/public/view'; const AuthoringSummary = () => { const { setValue, control, reset, getValues, setDefaultValues, fetcher }: AuthoringTemplateOutletContext = @@ -36,7 +36,7 @@ const AuthoringSummary = () => { } }, [fetcher.data]); - const { engagement } = useLoaderData() as { engagement: Promise }; + const { engagement } = useLoaderData() as EngagementLoaderData; const untouchedDefaultValues: EngagementUpdateData = { id: 0, @@ -71,7 +71,7 @@ const AuthoringSummary = () => { if (tryParse(eng.rich_description)) { setValue('rich_description', eng.rich_description); } - setValue('description_title', eng.description_title || 'Hello world'); + setValue('description_title', eng.description_title || ''); setValue('description', eng.description); setValue( 'summary_editor_state', @@ -129,11 +129,6 @@ const AuthoringSummary = () => { list: { options: ['unordered', 'ordered'] }, }; - const handleTitleChange = (value: string) => { - setValue('description_title', value); - return value; - }; - const handleEditorChange = (newEditorState: EditorState) => { const plainText = newEditorState.getCurrentContent().getPlainText(); const stringifiedEditorState = JSON.stringify(convertToRaw(newEditorState.getCurrentContent())); @@ -173,15 +168,10 @@ const AuthoringSummary = () => { { - field.onChange(handleTitleChange(value)); - }} /> ); }}