From df4434c867743bf4e8c0ce12644362af77a15d82 Mon Sep 17 00:00:00 2001 From: Arnei Date: Tue, 15 Oct 2024 10:52:04 +0200 Subject: [PATCH] Unify save requests When the user was saving (or saving and processing), we would sent two sequential POST requests, one for metadata and one for all the rest. This unifies the requests into a single one. Requires backend changes. --- src/main/MainMenu.tsx | 2 - src/main/Metadata.tsx | 39 +------------ src/main/Save.tsx | 53 ++++++----------- src/main/WorkflowConfiguration.tsx | 53 +++++------------ src/redux/metadataSlice.ts | 72 +----------------------- src/redux/workflowPostAndProcessSlice.ts | 1 + src/redux/workflowPostSlice.ts | 1 + src/types.ts | 3 + 8 files changed, 39 insertions(+), 185 deletions(-) diff --git a/src/main/MainMenu.tsx b/src/main/MainMenu.tsx index dcbd193b2..f2c5cc99f 100644 --- a/src/main/MainMenu.tsx +++ b/src/main/MainMenu.tsx @@ -17,7 +17,6 @@ import { basicButtonStyle } from "../cssStyles"; import { setIsPlaying } from "../redux/videoSlice"; import { useTranslation } from "react-i18next"; -import { resetPostRequestState as metadataResetPostRequestState } from "../redux/metadataSlice"; import { resetPostRequestState } from "../redux/workflowPostSlice"; import { setIsDisplayEditView } from "../redux/subtitleSlice"; @@ -126,7 +125,6 @@ export const MainMenuButton: React.FC = ({ dispatch(setIsPlaying(false)); // Reset states dispatch(resetPostRequestState()); - dispatch(metadataResetPostRequestState()); }; const mainMenuButtonStyle = css({ diff --git a/src/main/Metadata.tsx b/src/main/Metadata.tsx index 49cfcb3b1..e1ced778d 100644 --- a/src/main/Metadata.tsx +++ b/src/main/Metadata.tsx @@ -6,15 +6,12 @@ import { calendarStyle, errorBoxStyle, selectFieldStyle, titleStyle, titleStyleB import { useAppDispatch, useAppSelector } from "../redux/store"; import { fetchMetadata, - postMetadata, selectCatalogs, Catalog, MetadataField, setFieldValue, selectGetError, selectGetStatus, - selectPostError, - selectPostStatus, setFieldReadonly, } from "../redux/metadataSlice"; @@ -49,8 +46,6 @@ const Metadata: React.FC = () => { const catalogs = useAppSelector(selectCatalogs); const getStatus = useAppSelector(selectGetStatus); const getError = useAppSelector(selectGetError); - const postStatus = useAppSelector(selectPostStatus); - const postError = useAppSelector(selectPostError); const theme = useTheme(); // Try to fetch URL from external API @@ -433,7 +428,7 @@ const Metadata: React.FC = () => { /** * Callback for when the form is submitted - * Saves values in redux state and sends them to Opencast + * Saves values in redux state * @param values */ // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -458,8 +453,6 @@ const Metadata: React.FC = () => { } }); - // Send updated values to Opencast - dispatch(postMetadata()); }); }; @@ -730,36 +723,6 @@ const Metadata: React.FC = () => { return renderCatalog(catalog, i, {}); })} - {/* -
- {t("metadata.submit-helpertext", { buttonName: t("metadata.submit-button") })} -
- - -
- - -
*/} - -
- A problem occurred during communication with Opencast.
- Changes could not be saved to Opencast.

- {postError ? "Details: " + postError : "No error details are available."}
-
- {/* For debugging the forms current values*/} {/* {({ values }) => ( diff --git a/src/main/Save.tsx b/src/main/Save.tsx index cc78e929b..b1241c2e2 100644 --- a/src/main/Save.tsx +++ b/src/main/Save.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect } from "react"; import { css } from "@emotion/react"; import { @@ -22,8 +22,9 @@ import { CallbackButton, PageButton } from "./Finish"; import { useTranslation } from "react-i18next"; import { - postMetadata, selectPostError, selectPostStatus, setHasChanges as metadataSetHasChanges, + setHasChanges as metadataSetHasChanges, selectHasChanges as metadataSelectHasChanges, + selectCatalogs, } from "../redux/metadataSlice"; import { selectSubtitles, selectHasChanges as selectSubtitleHasChanges, @@ -45,8 +46,6 @@ const Save: React.FC = () => { const postWorkflowStatus = useAppSelector(selectStatus); const postError = useAppSelector(selectError); - const postMetadataStatus = useAppSelector(selectPostStatus); - const postMetadataError = useAppSelector(selectPostError); const theme = useTheme(); const metadataHasChanges = useAppSelector(metadataSelectHasChanges); const hasChanges = useAppSelector(selectHasChanges); @@ -62,7 +61,7 @@ const Save: React.FC = () => { const render = () => { // Post (successful) save - if (postWorkflowStatus === "success" && postMetadataStatus === "success" + if (postWorkflowStatus === "success" && !hasChanges && !metadataHasChanges && !subtitleHasChanges) { return ( <> @@ -95,12 +94,6 @@ const Save: React.FC = () => { {t("various.error-text")}
{postError ? t("various.error-details-text", { errorMessage: postError }) : t("various.error-text")}
-
- {t("various.error-text")}
- {postMetadataError ? - t("various.error-details-text", { errorMessage: postMetadataError }) : t("various.error-text") - }
-
); }; @@ -118,24 +111,23 @@ export const SaveButton: React.FC = () => { const segments = useAppSelector(selectSegments); const tracks = useAppSelector(selectTracks); const subtitles = useAppSelector(selectSubtitles); + const metadata = useAppSelector(selectCatalogs); const workflowStatus = useAppSelector(selectStatus); - const metadataStatus = useAppSelector(selectPostStatus); const theme = useTheme(); - const [metadataSaveStarted, setMetadataSaveStarted] = useState(false); // Update based on current fetching status let Icon = LuSave; let spin = false; let tooltip = null; - if (workflowStatus === "failed" || metadataStatus === "failed") { + if (workflowStatus === "failed") { Icon = LuAlertCircle; spin = false; tooltip = t("save.confirmButton-failed-tooltip"); - } else if (workflowStatus === "success" && metadataStatus === "success") { + } else if (workflowStatus === "success") { Icon = LuCheck; spin = false; tooltip = t("save.confirmButton-success-tooltip"); - } else if (workflowStatus === "loading" || metadataStatus === "loading") { + } else if (workflowStatus === "loading") { Icon = LuLoader; spin = true; tooltip = t("save.confirmButton-attempting-tooltip"); @@ -160,36 +152,23 @@ export const SaveButton: React.FC = () => { return subtitlesForPosting; }; - // Dispatches first save request - // Subsequent save requests should be wrapped in useEffect hooks, - // so they are only sent after the previous one has finished const save = () => { - setMetadataSaveStarted(true); - dispatch(postMetadata()); + dispatch(postVideoInformation({ + segments: segments, + tracks: tracks, + subtitles: prepareSubtitles(), + metadata: metadata, + })); }; - // Subsequent save request - useEffect(() => { - if (metadataStatus === "success" && metadataSaveStarted) { - setMetadataSaveStarted(false); - dispatch(postVideoInformation({ - segments: segments, - tracks: tracks, - subtitles: prepareSubtitles(), - })); - - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [metadataStatus]); - // Let users leave the page without warning after a successful save useEffect(() => { - if (workflowStatus === "success" && metadataStatus === "success") { + if (workflowStatus === "success") { dispatch(videoSetHasChanges(false)); dispatch(metadataSetHasChanges(false)); dispatch(subtitleSetHasChanges(false)); } - }, [dispatch, metadataStatus, workflowStatus]); + }, [dispatch, workflowStatus]); return ( diff --git a/src/main/WorkflowConfiguration.tsx b/src/main/WorkflowConfiguration.tsx index fabdbd4bd..1c8930cf4 100644 --- a/src/main/WorkflowConfiguration.tsx +++ b/src/main/WorkflowConfiguration.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect } from "react"; import { css } from "@emotion/react"; import { @@ -24,10 +24,8 @@ import { setEnd } from "../redux/endSlice"; import { useTranslation } from "react-i18next"; import { - postMetadata, - selectPostError, - selectPostStatus, setHasChanges as metadataSetHasChanges, + selectCatalogs, } from "../redux/metadataSlice"; import { selectSubtitles, @@ -45,8 +43,6 @@ const WorkflowConfiguration: React.FC = () => { const postAndProcessWorkflowStatus = useAppSelector(selectStatus); const postAndProcessError = useAppSelector(selectError); - const postMetadataStatus = useAppSelector(selectPostStatus); - const postMetadataError = useAppSelector(selectPostError); const theme = useTheme(); const workflowConfigurationStyle = css({ @@ -73,12 +69,6 @@ const WorkflowConfiguration: React.FC = () => { { errorMessage: postAndProcessError }) : t("various.error-text")}
-
- {t("various.error-text")}
- {postMetadataError ? t("various.error-details-text", - { errorMessage: postMetadataError }) : - t("various.error-text")}
-
); }; @@ -96,20 +86,19 @@ export const SaveAndProcessButton: React.FC<{ text: string; }> = ({ text }) => { const segments = useAppSelector(selectSegments); const tracks = useAppSelector(selectTracks); const subtitles = useAppSelector(selectSubtitles); + const metadata = useAppSelector(selectCatalogs); const workflowStatus = useAppSelector(selectStatus); - const metadataStatus = useAppSelector(selectPostStatus); - const [metadataSaveStarted, setMetadataSaveStarted] = useState(false); const theme = useTheme(); // Let users leave the page without warning after a successful save useEffect(() => { - if (workflowStatus === "success" && metadataStatus === "success") { + if (workflowStatus === "success") { dispatch(setEnd({ hasEnded: true, value: "success" })); dispatch(videoSetHasChanges(false)); dispatch(metadataSetHasChanges(false)); dispatch(subtitleSetHasChanges(false)); } - }, [dispatch, metadataStatus, workflowStatus]); + }, [dispatch, workflowStatus]); const prepareSubtitles = () => { const subtitlesForPosting = []; @@ -124,38 +113,26 @@ export const SaveAndProcessButton: React.FC<{ text: string; }> = ({ text }) => { return subtitlesForPosting; }; - // Dispatches first save request - // Subsequent save requests should be wrapped in useEffect hooks, - // so they are only sent after the previous one has finished const saveAndProcess = () => { - setMetadataSaveStarted(true); - dispatch(postMetadata()); + dispatch(postVideoInformationWithWorkflow({ + segments: segments, + tracks: tracks, + workflow: [{ id: selectedWorkflowId }], + subtitles: prepareSubtitles(), + metadata: metadata, + })); }; - // Subsequent save request - useEffect(() => { - if (metadataStatus === "success" && metadataSaveStarted) { - setMetadataSaveStarted(false); - dispatch(postVideoInformationWithWorkflow({ - segments: segments, - tracks: tracks, - workflow: [{ id: selectedWorkflowId }], - subtitles: prepareSubtitles(), - })); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [metadataStatus]); - // Update based on current fetching status let Icon = LuDatabase; let spin = false; - if (workflowStatus === "failed" || metadataStatus === "failed") { + if (workflowStatus === "failed") { Icon = LuAlertCircle; spin = false; - } else if (workflowStatus === "success" && metadataStatus === "success") { + } else if (workflowStatus === "success") { Icon = LuCheck; spin = false; - } else if (workflowStatus === "loading" || metadataStatus === "loading") { + } else if (workflowStatus === "loading") { Icon = LuLoader; spin = true; diff --git a/src/redux/metadataSlice.ts b/src/redux/metadataSlice.ts index 4d7d27678..fe0424385 100644 --- a/src/redux/metadataSlice.ts +++ b/src/redux/metadataSlice.ts @@ -21,52 +21,19 @@ export interface MetadataField { collection: { [key: string]: any } | undefined, } -// interface metadata { -// title: string, -// subject: string, -// description: string, -// language: string, -// languageOptions: string[], -// rightsHolder: string, -// license: string, -// licenseOptions: string[], -// isPartOf: string, -// creator: string, -// creatorOptions: string[], -// contributor: string, -// contributorOptions: string[], -// startDate: Date, -// duration: string, -// location: string, -// source: string, -// created: Date, -// publisher: string, -// identifier: string, -// } - interface metadata { catalogs: Catalog[]; hasChanges: boolean; // Did user make changes to metadata view since last save } -interface postRequestState { - postStatus: "idle" | "loading" | "success" | "failed", - postError: string | undefined, - postErrorReason: "unknown", -} - // TODO: Create an "httpRequestState" array or something -const initialState: metadata & httpRequestState & postRequestState = { +const initialState: metadata & httpRequestState = { catalogs: [], hasChanges: false, status: "idle", error: undefined, errorReason: "unknown", - - postStatus: "idle", - postError: undefined, - postErrorReason: "unknown", }; export const fetchMetadata = createAsyncThunk("metadata/fetchMetadata", async () => { @@ -78,21 +45,6 @@ export const fetchMetadata = createAsyncThunk("metadata/fetchMetadata", async () return JSON.parse(response); }); -export const postMetadata = createAsyncThunk("metadata/postMetadata", async (_, { getState }) => { - if (!settings.id) { - throw new Error("Missing media package identifier"); - } - - // TODO: Get only metadataState instead of all states - const allStates = getState() as { metadataState: { catalogs: metadata["catalogs"]; }; }; - - await client.post(`${settings.opencast.url}/editor/${settings.id}/metadata.json`, - allStates.metadataState.catalogs - ); - - return; -}); - /** * Slice for managing a post request for saving current changes and starting a workflow */ @@ -110,9 +62,6 @@ const metadataSlice = createSlice({ setHasChanges: (state, action: PayloadAction) => { state.hasChanges = action.payload; }, - resetPostRequestState: state => { - state.postStatus = "idle"; - }, }, extraReducers: builder => { builder.addCase( @@ -130,27 +79,12 @@ const metadataSlice = createSlice({ state.status = "failed"; state.error = action.error.message; }); - builder.addCase( - postMetadata.pending, (state, _action) => { - state.postStatus = "loading"; - }); - builder.addCase( - postMetadata.fulfilled, (state, _action) => { - state.postStatus = "success"; - }); - builder.addCase( - postMetadata.rejected, (state, action) => { - state.postStatus = "failed"; - state.postError = action.error.message; - }); }, selectors: { selectCatalogs: state => state.catalogs, selectHasChanges: state => state.hasChanges, selectGetStatus: state => state.status, selectGetError: state => state.error, - selectPostStatus: state => state.postStatus, - selectPostError: state => state.postError, selectTitleFromEpisodeDc: state => { for (const catalog of state.catalogs) { if (catalog.flavor === "dublincore/episode") { @@ -167,15 +101,13 @@ const metadataSlice = createSlice({ }, }); -export const { setFieldValue, setHasChanges, setFieldReadonly, resetPostRequestState } = metadataSlice.actions; +export const { setFieldValue, setHasChanges, setFieldReadonly } = metadataSlice.actions; export const { selectCatalogs, selectHasChanges, selectGetStatus, selectGetError, - selectPostStatus, - selectPostError, selectTitleFromEpisodeDc, } = metadataSlice.selectors; diff --git a/src/redux/workflowPostAndProcessSlice.ts b/src/redux/workflowPostAndProcessSlice.ts index 92f352c60..6bb6e3491 100644 --- a/src/redux/workflowPostAndProcessSlice.ts +++ b/src/redux/workflowPostAndProcessSlice.ts @@ -22,6 +22,7 @@ export const postVideoInformationWithWorkflow = segments: convertSegments(argument.segments), tracks: argument.tracks, subtitles: argument.subtitles, + metadataJSON: JSON.stringify(argument.metadata), workflows: argument.workflow, } ); diff --git a/src/redux/workflowPostSlice.ts b/src/redux/workflowPostSlice.ts index a25f0ddcb..11564c797 100644 --- a/src/redux/workflowPostSlice.ts +++ b/src/redux/workflowPostSlice.ts @@ -20,6 +20,7 @@ export const postVideoInformation = segments: convertSegments(argument.segments), tracks: argument.tracks, subtitles: argument.subtitles, + metadataJSON: JSON.stringify(argument.metadata), } ); return response; diff --git a/src/types.ts b/src/types.ts index 06232b9f3..d54f904db 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { Catalog } from "./redux/metadataSlice"; + export interface Segment { id: string, start: number, @@ -68,6 +70,7 @@ export interface PostEditArgument { segments: Segment[] tracks: Track[] subtitles: SubtitlesFromOpencast[] + metadata: Catalog[] } export interface PostAndProcessEditArgument extends PostEditArgument{