From 1471fe9b85eeb020b91a7fba71cd35fbb8b75945 Mon Sep 17 00:00:00 2001 From: Arnei Date: Thu, 11 Apr 2024 10:02:45 +0200 Subject: [PATCH 01/30] Add typing to NewEventWizard.tsx Typescript for new event wizard and the components it uses. --- .../ModalTabsAndPages/NewAssetUploadPage.tsx | 22 +++++--- .../NewMetadataExtendedPage.tsx | 20 +++---- .../ModalTabsAndPages/NewMetadataPage.tsx | 21 +++++--- .../ModalTabsAndPages/NewProcessingPage.tsx | 23 ++++---- .../ModalTabsAndPages/NewSourcePage.tsx | 44 ++++++++++----- .../partials/wizards/NewEventSummary.tsx | 38 ++++++++++--- .../partials/wizards/NewEventWizard.tsx | 53 ++++++------------- .../partials/wizards/NewSeriesWizard.tsx | 12 ++--- app/src/configs/modalConfig.ts | 27 +++++++++- app/src/configs/sourceConfig.ts | 18 ++++++- app/src/slices/eventDetailsSlice.ts | 20 +++---- app/src/slices/eventSlice.ts | 37 ++++--------- app/src/utils/resourceUtils.ts | 19 +++---- 13 files changed, 203 insertions(+), 151 deletions(-) diff --git a/app/src/components/events/partials/ModalTabsAndPages/NewAssetUploadPage.tsx b/app/src/components/events/partials/ModalTabsAndPages/NewAssetUploadPage.tsx index 50558e93f1..7896961f98 100644 --- a/app/src/components/events/partials/ModalTabsAndPages/NewAssetUploadPage.tsx +++ b/app/src/components/events/partials/ModalTabsAndPages/NewAssetUploadPage.tsx @@ -3,18 +3,25 @@ import { useTranslation } from "react-i18next"; import WizardNavigationButtons from "../../../shared/wizard/WizardNavigationButtons"; import { getAssetUploadOptions } from "../../../../selectors/eventSelectors"; import { useAppSelector } from "../../../../store"; +import { FormikProps } from "formik"; /** * This component renders the asset upload page of the new event wizard * (only if its not set hidden (see newEventWizardConfig) or user chose UPLOAD as source mode) */ -const NewAssetUploadPage = ({ -// @ts-expect-error TS(7031): Binding element 'previousPage' implicitly has an '... Remove this comment to see the full error message - previousPage, -// @ts-expect-error TS(7031): Binding element 'nextPage' implicitly has an 'any'... Remove this comment to see the full error message - nextPage, -// @ts-expect-error TS(7031): Binding element 'formik' implicitly has an 'any' t... Remove this comment to see the full error message +interface RequiredFormProps { + sourceMode: string, + [key: string]: any, +} + +const NewAssetUploadPage = ({ formik, + nextPage, + previousPage +}: { + formik: FormikProps, + nextPage: (values: T) => void, + previousPage: (values: T, twoPagesBack?: boolean) => void, }) => { const { t } = useTranslation(); @@ -92,8 +99,7 @@ const NewAssetUploadPage = ({ className="button-like-anchor remove" onClick={() => { formik.setFieldValue(asset.id, null); -// @ts-expect-error TS(2531): Object is possibly 'null'. - document.getElementById(asset.id).value = ""; + (document.getElementById(asset.id) as HTMLInputElement).value = ""; }} /> diff --git a/app/src/components/events/partials/ModalTabsAndPages/NewMetadataExtendedPage.tsx b/app/src/components/events/partials/ModalTabsAndPages/NewMetadataExtendedPage.tsx index 00dc2a0b71..6d2dcff296 100644 --- a/app/src/components/events/partials/ModalTabsAndPages/NewMetadataExtendedPage.tsx +++ b/app/src/components/events/partials/ModalTabsAndPages/NewMetadataExtendedPage.tsx @@ -1,21 +1,23 @@ import React from "react"; import { useTranslation } from "react-i18next"; -import { Field } from "formik"; +import { Field, FormikProps } from "formik"; import RenderMultiField from "../../../shared/wizard/RenderMultiField"; import RenderField from "../../../shared/wizard/RenderField"; import WizardNavigationButtons from "../../../shared/wizard/WizardNavigationButtons"; import { isJson } from "../../../../utils/utils"; import { getMetadataCollectionFieldName } from "../../../../utils/resourceUtils"; +import { MetadataCatalog } from "../../../../slices/eventSlice"; -const NewMetadataExtendedPage = ({ -// @ts-expect-error TS(7031): Binding element 'previousPage' implicitly has an '... Remove this comment to see the full error message - previousPage, -// @ts-expect-error TS(7031): Binding element 'nextPage' implicitly has an 'any'... Remove this comment to see the full error message - nextPage, -// @ts-expect-error TS(7031): Binding element 'formik' implicitly has an 'any' t... Remove this comment to see the full error message +const NewMetadataExtendedPage = ({ formik, -// @ts-expect-error TS(7031): Binding element 'extendedMetadataFields' implicitl... Remove this comment to see the full error message + nextPage, + previousPage, extendedMetadataFields, +}: { + formik: FormikProps, + nextPage: (values: T) => void, + previousPage: (values: T, twoPagesBack?: boolean) => void, + extendedMetadataFields?: MetadataCatalog[], }) => { const { t } = useTranslation(); @@ -28,7 +30,6 @@ const NewMetadataExtendedPage = ({ //iterate through metadata catalogs !!extendedMetadataFields && extendedMetadataFields.length > 0 && -// @ts-expect-error TS(7006): Parameter 'catalog' implicitly has an 'any' type. extendedMetadataFields.map((catalog, index) => (
@@ -38,7 +39,6 @@ const NewMetadataExtendedPage = ({ {!!catalog.fields && -// @ts-expect-error TS(7006): Parameter 'field' implicitly has an 'any' type. catalog.fields.map((field, key) => ( {/* Render table row for each metadata field depending on type*/} {!!metadataFields.fields && -// @ts-expect-error TS(7006): Parameter 'field' implicitly has an 'any' type. metadataFields.fields.map((field, key) => ( ) diff --git a/app/src/components/shared/RenderDate.tsx b/app/src/components/shared/RenderDate.tsx new file mode 100644 index 0000000000..7bb4afded3 --- /dev/null +++ b/app/src/components/shared/RenderDate.tsx @@ -0,0 +1,8 @@ +import { useTranslation } from "react-i18next"; + +const RenderDate: React.FC<{ date: string }> = ({ date }) => { + const { t } = useTranslation(); + return <>{t("dateFormats.dateTime.short", { dateTime: new Date(date) })}; +}; + +export default RenderDate; diff --git a/app/src/components/shared/wizard/RenderField.tsx b/app/src/components/shared/wizard/RenderField.tsx index 7f882a15e2..d02545f4e2 100644 --- a/app/src/components/shared/wizard/RenderField.tsx +++ b/app/src/components/shared/wizard/RenderField.tsx @@ -6,6 +6,7 @@ import { useClickOutsideField } from "../../../hooks/wizardHooks"; import { isJson } from "../../../utils/utils"; import { getMetadataCollectionFieldName } from "../../../utils/resourceUtils"; import DropDown from "../DropDown"; +import RenderDate from "../RenderDate"; import { parseISO } from "date-fns"; const childRef = React.createRef(); @@ -210,7 +211,7 @@ const EditableDateValue = ({ ) : (
setEditMode(true)} className="show-edit"> - {t("dateFormats.dateTime.short", { dateTime: new Date(text) }) || ""} +
From a7f6d0738171dbcdb530acecd91867a3079b04ac Mon Sep 17 00:00:00 2001 From: Julian Kniephoff Date: Tue, 14 May 2024 09:32:37 +0200 Subject: [PATCH 15/30] Further simplify the static file server This avoids having to poorly reimplement `express.static` using some internal request rewriting logic. --- staticServer.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/staticServer.js b/staticServer.js index 352b4c9b36..59dcafc16b 100644 --- a/staticServer.js +++ b/staticServer.js @@ -2,24 +2,26 @@ const path = require("path"); const express = require("express"); const app = express(); -const port = process.env.PORT || 5000; -app.get("/*", express.static(path.join(__dirname, "test/app/GET"))); +for (const method of ["post", "put", "delete"]) { + app[method]("/*", (req, res, next) => { + setTimeout(next, 1000); + }); +} app.post("/*", (req, res, next) => { res.status(201); next(); }); -const serveStatic = (req, res) => { - const filePath = path.join(__dirname, "test/app", req.method.toUpperCase(), req.url); - setTimeout(() => { - res.sendFile(filePath); - }, 1000); -}; - -for (const method of ["put", "post", "delete"]) { - app[method]("/*", serveStatic); -} +app.use("/", [ + (req, res, next) => { + req.url = `/${req.method}${req.url}`; + req.method = "GET"; + next(); + }, + express.static(path.join(__dirname, "test/app")) +]); +const port = process.env.PORT || 5000; app.listen(port, () => console.log(`Listing on port ${port}`)); From a04425b5dcc9e31969c77d4ab8b3e0fa77bef4a3 Mon Sep 17 00:00:00 2001 From: Julian Kniephoff Date: Wed, 15 May 2024 12:52:09 +0200 Subject: [PATCH 16/30] Enable deleting user references from the admin UI Closes #327 by forward-porting opencast/opencast#5058. cf. opencast/opencast#5833 --- app/src/components/users/partials/UsersActionsCell.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/components/users/partials/UsersActionsCell.tsx b/app/src/components/users/partials/UsersActionsCell.tsx index 4e0c0bc98e..de00bfdb23 100644 --- a/app/src/components/users/partials/UsersActionsCell.tsx +++ b/app/src/components/users/partials/UsersActionsCell.tsx @@ -57,8 +57,8 @@ const UsersActionCell = ({ )} - {row.manageable && hasAccess("ROLE_UI_USERS_DELETE", user) && ( - <> + {(row.manageable || (row.provider !== "opencast" && row.provider !== "system")) + && hasAccess("ROLE_UI_USERS_DELETE", user) && <>
diff --git a/app/src/components/events/partials/ModalTabsAndPages/NewMetadataPage.tsx b/app/src/components/events/partials/ModalTabsAndPages/NewMetadataPage.tsx index d3b57fe06f..9fbb2fd3ef 100644 --- a/app/src/components/events/partials/ModalTabsAndPages/NewMetadataPage.tsx +++ b/app/src/components/events/partials/ModalTabsAndPages/NewMetadataPage.tsx @@ -1,19 +1,25 @@ import React from "react"; import { useTranslation } from "react-i18next"; -import { Field } from "formik"; +import { Field, FormikProps } from "formik"; import RenderField from "../../../shared/wizard/RenderField"; import WizardNavigationButtons from "../../../shared/wizard/WizardNavigationButtons"; import RenderMultiField from "../../../shared/wizard/RenderMultiField"; +import { MetadataCatalog } from "../../../../slices/eventSlice"; /** * This component renders the metadata page for new events and series in the wizards. */ -const NewMetadataPage = ({ - metadataFields, - nextPage, - formik, - header -}: any) => { +const NewMetadataPage = ({ + formik, + nextPage, + metadataFields, + header +}: { + formik: FormikProps, + nextPage: (values: T) => void, + metadataFields: MetadataCatalog, + header: string +}) => { const { t } = useTranslation(); return ( @@ -29,7 +35,6 @@ const NewMetadataPage = ({
diff --git a/app/src/components/events/partials/ModalTabsAndPages/NewProcessingPage.tsx b/app/src/components/events/partials/ModalTabsAndPages/NewProcessingPage.tsx index 169936a87e..a8ef7c0b21 100644 --- a/app/src/components/events/partials/ModalTabsAndPages/NewProcessingPage.tsx +++ b/app/src/components/events/partials/ModalTabsAndPages/NewProcessingPage.tsx @@ -7,18 +7,24 @@ import { setDefaultConfig } from "../../../../utils/workflowPanelUtils"; import DropDown from "../../../shared/DropDown"; import { useAppDispatch, useAppSelector } from "../../../../store"; import { fetchWorkflowDef } from "../../../../slices/workflowSlice"; +import { FormikProps } from "formik"; /** * This component renders the processing page for new events in the new event wizard. */ -const NewProcessingPage: React.FC<{ - previousPage: any //TODO: Add type - nextPage: any //TODO: Add type - formik: any //TODO: Add type -}> = ({ - previousPage, - nextPage, +interface RequiredFormProps { + sourceMode: string, + processingWorkflow: string, +} + +const NewProcessingPage = ({ formik, + nextPage, + previousPage, +}: { + formik: FormikProps, + nextPage: (values: T) => void, + previousPage: (values: T, twoPagesBack?: boolean) => void, }) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); @@ -40,8 +46,7 @@ const NewProcessingPage: React.FC<{ } }; -// @ts-expect-error TS(7006): Parameter 'value' implicitly has an 'any' type. - const setDefaultValues = (value) => { + const setDefaultValues = (value: string) => { let workflowId = value; // fill values with default configuration of chosen workflow let defaultConfiguration = setDefaultConfig(workflowDef, workflowId); diff --git a/app/src/components/events/partials/ModalTabsAndPages/NewSourcePage.tsx b/app/src/components/events/partials/ModalTabsAndPages/NewSourcePage.tsx index 0d118da4d8..ee6681c1ec 100644 --- a/app/src/components/events/partials/ModalTabsAndPages/NewSourcePage.tsx +++ b/app/src/components/events/partials/ModalTabsAndPages/NewSourcePage.tsx @@ -44,13 +44,30 @@ import { checkConflicts } from "../../../../slices/eventSlice"; /** * This component renders the source page for new events in the new event wizard. */ -const NewSourcePage = ({ -// @ts-expect-error TS(7031): Binding element 'previousPage' implicitly has an '... Remove this comment to see the full error message - previousPage, -// @ts-expect-error TS(7031): Binding element 'nextPage' implicitly has an 'any'... Remove this comment to see the full error message - nextPage, -// @ts-expect-error TS(7031): Binding element 'formik' implicitly has an 'any' t... Remove this comment to see the full error message +interface RequiredFormProps { + scheduleStartDate: string, + sourceMode: string, + // Schedule + location: string + scheduleEndDate: string + scheduleStartHour: string + scheduleEndHour: string + scheduleStartMinute: string + scheduleEndMinute: string + scheduleDurationHours: string + scheduleDurationMinutes: string + // checkConflicts + repeatOn: string[], +} + +const NewSourcePage = ({ formik, + nextPage, + previousPage, +}: { + formik: FormikProps, + nextPage: (values: T) => void, + previousPage: (values: T, twoPagesBack?: boolean) => void, }) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); @@ -63,7 +80,6 @@ const NewSourcePage = ({ dispatch(fetchRecordings("inputs")); // validate form because dependent default values need to be checked -// @ts-expect-error TS(7006): Parameter 'r' implicitly has an 'any' type. formik.validateForm().then((r) => console.info(r)); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -311,7 +327,7 @@ const Upload = ({ formik }) => { {/* One row for each metadata field*/} - {sourceMetadata.UPLOAD.metadata.map((field, key) => ( + {sourceMetadata.UPLOAD && sourceMetadata.UPLOAD.metadata.map((field, key) => ( ) : null )} + {!!formik.values.startDate && ( + )}
{t(field.label)} @@ -342,12 +358,12 @@ const Schedule = ({ formik, inputDevices diff --git a/app/src/components/events/partials/wizards/NewEventSummary.tsx b/app/src/components/events/partials/wizards/NewEventSummary.tsx index b41ce32c96..4371aca664 100644 --- a/app/src/components/events/partials/wizards/NewEventSummary.tsx +++ b/app/src/components/events/partials/wizards/NewEventSummary.tsx @@ -11,19 +11,42 @@ import MetadataExtendedSummaryTable from "./summaryTables/MetadataExtendedSummar import AccessSummaryTable from "./summaryTables/AccessSummaryTable"; import WizardNavigationButtons from "../../../shared/wizard/WizardNavigationButtons"; import { useAppSelector } from "../../../../store"; +import { FormikProps } from "formik"; +import { TransformedAcl } from "../../../../slices/aclDetailsSlice"; /** * This component renders the summary page for new events in the new event wizard. */ -const NewEventSummary = ({ -// @ts-expect-error TS(7031): Binding element 'previousPage' implicitly has an '... Remove this comment to see the full error message - previousPage, -// @ts-expect-error TS(7031): Binding element 'formik' implicitly has an 'any' t... Remove this comment to see the full error message +interface RequiredFormProps { + processingWorkflow: string + sourceMode: string + startDate?: string + location: string + scheduleStartDate: string + scheduleEndDate: string + scheduleStartHour: string + scheduleEndHour: string + scheduleStartMinute: string + scheduleEndMinute: string + scheduleDurationHours: string + scheduleDurationMinutes: string + repeatOn: string[] + deviceInputs?: string[] + configuration: { [key: string]: string } + acls: TransformedAcl[] + [key: string]: unknown, // Metadata fields +} + +const NewEventSummary = ({ formik, -// @ts-expect-error TS(7031): Binding element 'metaDataExtendedHidden' implicitl... Remove this comment to see the full error message + previousPage, metaDataExtendedHidden, -// @ts-expect-error TS(7031): Binding element 'assetUploadHidden' implicitly has... Remove this comment to see the full error message assetUploadHidden, +}: { + formik: FormikProps, + previousPage: (values: T, twoPagesBack?: boolean) => void, + metaDataExtendedHidden: boolean, + assetUploadHidden: boolean, }) => { const { t } = useTranslation(); @@ -147,6 +170,7 @@ const NewEventSummary = ({
{t("EVENTS.EVENTS.NEW.SOURCE.DATE_TIME.START_DATE")} @@ -157,6 +181,7 @@ const NewEventSummary = ({ })}
)} @@ -214,7 +239,6 @@ const NewEventSummary = ({
{formik.values.repeatOn -// @ts-expect-error TS(7006): Parameter 'day' implicitly has an 'any' type. .map((day) => t("EVENTS.EVENTS.NEW.WEEKDAYSLONG." + day) ) diff --git a/app/src/components/events/partials/wizards/NewEventWizard.tsx b/app/src/components/events/partials/wizards/NewEventWizard.tsx index 5a6c759c1c..ddedea7e88 100644 --- a/app/src/components/events/partials/wizards/NewEventWizard.tsx +++ b/app/src/components/events/partials/wizards/NewEventWizard.tsx @@ -18,8 +18,9 @@ import { getExtendedEventMetadata, } from "../../../../selectors/eventSelectors"; import { useAppDispatch, useAppSelector } from "../../../../store"; -import { postNewEvent } from "../../../../slices/eventSlice"; +import { MetadataCatalog, postNewEvent } from "../../../../slices/eventSlice"; import { getUserInformation } from "../../../../selectors/userInfoSelectors"; +import { UserInfoState } from "../../../../slices/userInfoSlice"; /** * This component manages the pages of the new event wizard and the submission of values @@ -189,8 +190,6 @@ const NewEventWizard: React.FC<{ )} @@ -221,48 +220,39 @@ const NewEventWizard: React.FC<{ // Transform all initial values needed from information provided by backend const getInitialValues = ( -// @ts-expect-error TS(7006): Parameter 'metadataFields' implicitly has an 'any'... Remove this comment to see the full error message - metadataFields, -// @ts-expect-error TS(7006): Parameter 'extendedMetadata' implicitly has an 'an... Remove this comment to see the full error message - extendedMetadata, + metadataFields: MetadataCatalog, + extendedMetadata: MetadataCatalog[], // @ts-expect-error TS(7006): Parameter 'uploadAssetOptions' implicitly has an '... Remove this comment to see the full error message uploadAssetOptions, -// @ts-expect-error TS(7006): Parameter 'uploadAssetOptions' implicitly has an '... Remove this comment to see the full error message - user + user: UserInfoState ) => { + let initialValues = initialFormValuesNewEvents; + // Transform metadata fields provided by backend (saved in redux) - let initialValues = getInitialMetadataFieldValues( + initialValues = {...initialValues, ...getInitialMetadataFieldValues( metadataFields, extendedMetadata - ); + )}; // Transform additional metadata for source (provided by constant in newEventConfig) if (!!sourceMetadata.UPLOAD) { sourceMetadata.UPLOAD.metadata.forEach((field) => { -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message initialValues[field.id] = field.value; }); } -// @ts-expect-error TS(2339): Property 'SINGLE_SCHEDULE' does not exist on type ... Remove this comment to see the full error message - if (!!sourceMetadata.SINGLE_SCHEDULE) { -// @ts-expect-error TS(2339): Property 'SINGLE_SCHEDULE' does not exist on type ... Remove this comment to see the full error message - sourceMetadata.SINGLE_SCHEDULE.metadata.forEach((field) => { -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message + if (!!sourceMetadata.SCHEDULE_SINGLE) { + sourceMetadata.SCHEDULE_SINGLE.metadata.forEach((field) => { initialValues[field.id] = field.value; }); } -// @ts-expect-error TS(2339): Property 'MULTIPLE_SCHEDULE' does not exist on typ... Remove this comment to see the full error message - if (!!sourceMetadata.MULTIPLE_SCHEDULE) { -// @ts-expect-error TS(2339): Property 'MULTIPLE_SCHEDULE' does not exist on typ... Remove this comment to see the full error message - sourceMetadata.MULTIPLE_SCHEDULE.metadata.forEach((field) => { -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message + if (!!sourceMetadata.SCHEDULE_MULTIPLE) { + sourceMetadata.SCHEDULE_MULTIPLE.metadata.forEach((field) => { initialValues[field.id] = field.value; }); } // Add possible files that can be uploaded in source step if (!!uploadAssetOptions) { -// @ts-expect-error TS(2339): Property 'uploadAssetsTrack' does not exist on typ... Remove this comment to see the full error message initialValues.uploadAssetsTrack = []; // initial value of upload asset needs to be null, because object (file) is saved there // @ts-expect-error TS(7006): Parameter 'option' implicitly has an 'any' type. @@ -274,35 +264,26 @@ const getInitialValues = ( file: null, }); } else { -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message initialValues[option.id] = null; } }); } - // Add all initial form values known upfront listed in newEventsConfig - for (const [key, value] of Object.entries(initialFormValuesNewEvents)) { -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message - initialValues[key] = value; - } + // // Add all initial form values known upfront listed in newEventsConfig + // for (const [key, value] of Object.entries(initialFormValuesNewEvents)) { + // initialValues[key] = value; + // } const defaultDate = new Date(); // fill times with some default values -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message initialValues["scheduleStartHour"] = (defaultDate.getHours() + 1).toString(); -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message initialValues["scheduleStartMinute"] = "00"; -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message initialValues["scheduleDurationHours"] = "00"; -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message initialValues["scheduleDurationMinutes"] = "55"; -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message initialValues["scheduleEndHour"] = (defaultDate.getHours() + 1).toString(); -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message initialValues["scheduleEndMinute"] = "55"; -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message initialValues["acls"] = [ { role: user.userRole, diff --git a/app/src/components/events/partials/wizards/NewSeriesWizard.tsx b/app/src/components/events/partials/wizards/NewSeriesWizard.tsx index 02185dd387..dc19ed9f00 100644 --- a/app/src/components/events/partials/wizards/NewSeriesWizard.tsx +++ b/app/src/components/events/partials/wizards/NewSeriesWizard.tsx @@ -182,19 +182,13 @@ const NewSeriesWizard: React.FC<{ // @ts-expect-error TS(7006): Parameter 'metadataFields' implicitly has an 'any'... Remove this comment to see the full error message const getInitialValues = (metadataFields, extendedMetadata, user) => { + let initialValues = initialFormValuesNewSeries; // Transform metadata fields provided by backend (saved in redux) - let initialValues = getInitialMetadataFieldValues( + initialValues = {...initialValues, ...getInitialMetadataFieldValues( metadataFields, extendedMetadata - ); - - // Add all initial form values known upfront listed in newSeriesConfig - for (const [key, value] of Object.entries(initialFormValuesNewSeries)) { -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message - initialValues[key] = value; - } + )}; -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message initialValues["acls"] = [ { role: user.userRole, diff --git a/app/src/configs/modalConfig.ts b/app/src/configs/modalConfig.ts index a2a1a20454..db15604308 100644 --- a/app/src/configs/modalConfig.ts +++ b/app/src/configs/modalConfig.ts @@ -2,6 +2,7 @@ // InitialValues of Formik form (others computed dynamically depending on responses from backend) import { initArray } from "../utils/utils"; import { EditedEvents, Event } from "../slices/eventSlice"; +import { TransformedAcl } from "../slices/aclDetailsSlice"; // Context for notifications shown in modals export const NOTIFICATION_CONTEXT = "modal-form"; @@ -9,7 +10,25 @@ export const NOTIFICATION_CONTEXT = "modal-form"; // Context for notifications shown in wizard access page export const NOTIFICATION_CONTEXT_ACCESS = "wizard-access"; -export const initialFormValuesNewEvents = { +export const initialFormValuesNewEvents: { + sourceMode: string, + scheduleStartDate: string, + scheduleEndDate: string, + scheduleStartHour: string, + scheduleStartMinute: string, + scheduleDurationHours: string, + scheduleDurationMinutes: string, + scheduleEndHour: string, + scheduleEndMinute: string, + repeatOn: string[], + location: string, + processingWorkflow: string, + configuration: { [key: string]: string }, + aclTemplate: string, + acls: TransformedAcl[], + uploadAssetsTrack?: any[] + [key: string]: unknown, // Metadata fields that are getting added later +} = { sourceMode: "UPLOAD", scheduleStartDate: new Date().toISOString(), scheduleEndDate: new Date().toISOString(), @@ -69,7 +88,11 @@ export const WORKFLOW_UPLOAD_ASSETS_NON_TRACK = "publish-uploaded-assets"; // All fields for new series form that are fix and not depending on response of backend // InitialValues of Formik form (others computed dynamically depending on responses from backend) -export const initialFormValuesNewSeries = { +export const initialFormValuesNewSeries: { + acls: TransformedAcl[], + theme: string, + [key: string]: any, // Metadata fields that are getting added later +} = { acls: [], theme: "", }; diff --git a/app/src/configs/sourceConfig.ts b/app/src/configs/sourceConfig.ts index d7faffde72..d574c887de 100644 --- a/app/src/configs/sourceConfig.ts +++ b/app/src/configs/sourceConfig.ts @@ -9,7 +9,23 @@ * - required: flag indicating if metadata field is required * - tabindex: tabindex of the metadata field */ -export const sourceMetadata = { +type Metadata = { + id: string, + label: string, + value: any, + type: string, + readOnly: boolean, + required: boolean, + tabindex: number, +} + +type SourceType = { + UPLOAD?: { metadata: Metadata[] }, + SCHEDULE_SINGLE?: { metadata: Metadata[] }, + SCHEDULE_MULTIPLE?: { metadata: Metadata[] }, +} + +export const sourceMetadata: SourceType = { UPLOAD: { metadata: [ { diff --git a/app/src/slices/eventDetailsSlice.ts b/app/src/slices/eventDetailsSlice.ts index cfec82d932..3f2db72120 100644 --- a/app/src/slices/eventDetailsSlice.ts +++ b/app/src/slices/eventDetailsSlice.ts @@ -92,6 +92,16 @@ type Device = { // url: string, } +export type UploadAssetOption = { + id: string, + title: string, // translation key + type: string, // "track", "attachment" etc. + flavorType: string, + flavorSubType: string, + accept: string, + displayOrder: number, +} + type EventDetailsState = { statusMetadata: 'uninitialized' | 'loading' | 'succeeded' | 'failed', errorMetadata: SerializedError | null, @@ -167,15 +177,7 @@ type EventDetailsState = { publications: number, }, transactionsReadOnly: boolean, - uploadAssetOptions: { - id: string, - title: string, // translation key - type: string, // "track", "attachment" etc. - flavorType: string, - flavorSubType: string, - accept: string, - displayOrder: number, - }[] | undefined, + uploadAssetOptions: UploadAssetOption[] | undefined, assetAttachments: Array< Assets & { type: string, }>, diff --git a/app/src/slices/eventSlice.ts b/app/src/slices/eventSlice.ts index 85beb0aec1..f46723dc51 100644 --- a/app/src/slices/eventSlice.ts +++ b/app/src/slices/eventSlice.ts @@ -102,7 +102,7 @@ type MetadataField = { export type MetadataFieldSelected = MetadataField & { selected: boolean } -type MetadataCatalog = { +export type MetadataCatalog = { title: string, flavor: string, fields: MetadataField[], @@ -149,7 +149,7 @@ type EventState = { metadata: MetadataCatalog, extendedMetadata: MetadataCatalog[], isFetchingAssetUploadOptions: boolean, - uploadAssetOptions: any[], // TODO: proper typing + uploadAssetOptions: any[], uploadAssetWorkflow: any, // TODO: proper typing schedulingInfo: { editedEvents: EditedEvents[], @@ -439,13 +439,15 @@ export const postNewEvent = createAsyncThunk('events/postNewEvent', async (param source = { type: values.sourceMode, }; - for (let i = 0; sourceMetadata.UPLOAD.metadata.length > i; i++) { - metadataFields = metadataFields.concat({ - id: sourceMetadata.UPLOAD.metadata[i].id, - value: values[sourceMetadata.UPLOAD.metadata[i].id], - type: sourceMetadata.UPLOAD.metadata[i].type, - tabindex: sourceMetadata.UPLOAD.metadata[i].tabindex, - }); + if (sourceMetadata.UPLOAD) { + for (let i = 0; sourceMetadata.UPLOAD.metadata.length > i; i++) { + metadataFields = metadataFields.concat({ + id: sourceMetadata.UPLOAD.metadata[i].id, + value: values[sourceMetadata.UPLOAD.metadata[i].id], + type: sourceMetadata.UPLOAD.metadata[i].type, + tabindex: sourceMetadata.UPLOAD.metadata[i].tabindex, + }); + } } } @@ -846,11 +848,7 @@ export const updateScheduledEventsBulk = createAsyncThunk('events/updateSchedule // check provided date range for conflicts export const checkConflicts = (values: { - acls: TransformedAcls, - configuration: { [key: string]: any }, - deviceInputs?: string[], location: string, - processingWorkflow: string, repeatOn: string[], scheduleDurationHours: string, scheduleDurationMinutes: string, @@ -861,19 +859,6 @@ export const checkConflicts = (values: { scheduleStartHour: string, scheduleStartMinute: string, sourceMode: string, - uploadAssetsTrack: { - accept: string, - displayOrder: number, - file: FileList, - flavorSubtType: string, - flavorType: string, - id: string, - multiple: boolean, - showAs: string, - title: string, - type: string, - }[], - [key: string]: unknown, }) => async (dispatch: AppDispatch) => { let check = true; diff --git a/app/src/utils/resourceUtils.ts b/app/src/utils/resourceUtils.ts index 7de96a704c..7bfea3bae0 100644 --- a/app/src/utils/resourceUtils.ts +++ b/app/src/utils/resourceUtils.ts @@ -9,6 +9,7 @@ import { NewUser } from "../slices/userSlice"; import { Recording } from "../slices/recordingSlice"; import { UserInfoState } from "../slices/userInfoSlice"; import { hasAccess } from "./utils"; +import { MetadataCatalog } from "../slices/eventSlice"; /** * This file contains methods that are needed in more than one resource thunk @@ -108,17 +109,13 @@ export const buildGroupBody = (values) => { // get initial metadata field values for formik in create resources wizards export const getInitialMetadataFieldValues = ( -// @ts-expect-error TS(7006): Parameter 'metadataFields' implicitly has an 'any'... Remove this comment to see the full error message - metadataFields, -// @ts-expect-error TS(7006): Parameter 'extendedMetadata' implicitly has an 'an... Remove this comment to see the full error message - extendedMetadata + metadataFields: MetadataCatalog, + extendedMetadata: MetadataCatalog[] ) => { - let initialValues = {}; + let initialValues: { [key: string]: string | string[] | boolean } = {}; if (!!metadataFields.fields && metadataFields.fields.length > 0) { -// @ts-expect-error TS(7006): Parameter 'field' implicitly has an 'any' type. metadataFields.fields.forEach((field) => { -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message initialValues[field.id] = field.value; }); } @@ -126,16 +123,14 @@ export const getInitialMetadataFieldValues = ( if (extendedMetadata.length > 0) { for (const metadataCatalog of extendedMetadata) { if (!!metadataCatalog.fields && metadataCatalog.fields.length > 0) { -// @ts-expect-error TS(7006): Parameter 'field' implicitly has an 'any' type. metadataCatalog.fields.forEach((field) => { - let value = field.value; - if (value === "true") { + let value = false; + if (field.value === "true") { value = true; - } else if (value === "false") { + } else if (field.value === "false") { value = false; } -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message initialValues[metadataCatalog.flavor + "_" + field.id] = value; }); } From 7ca4f9b1a9acc7487b289c049188bed9ad6bb3e6 Mon Sep 17 00:00:00 2001 From: Arnei Date: Fri, 12 Apr 2024 09:33:48 +0200 Subject: [PATCH 02/30] Type NewSeriesWizard Add typing to NewSeriesWizard.tsx and child components. --- .../ModalTabsAndPages/NewThemePage.tsx | 19 ++++++--- .../partials/wizards/NewSeriesSummary.tsx | 16 +++++-- .../partials/wizards/NewSeriesWizard.tsx | 42 ++++++++++++++----- app/src/slices/seriesSlice.ts | 19 +++++---- 4 files changed, 68 insertions(+), 28 deletions(-) diff --git a/app/src/components/events/partials/ModalTabsAndPages/NewThemePage.tsx b/app/src/components/events/partials/ModalTabsAndPages/NewThemePage.tsx index 6a83ff212b..f933e95b46 100644 --- a/app/src/components/events/partials/ModalTabsAndPages/NewThemePage.tsx +++ b/app/src/components/events/partials/ModalTabsAndPages/NewThemePage.tsx @@ -4,15 +4,24 @@ import { getSeriesThemes } from "../../../../selectors/seriesSeletctor"; import WizardNavigationButtons from "../../../shared/wizard/WizardNavigationButtons"; import DropDown from "../../../shared/DropDown"; import { useAppSelector } from "../../../../store"; +import { FormikProps } from "formik"; /** * This component renders the theme page for new series in the new series wizard. */ -const NewThemePage = ({ - formik, - nextPage, - previousPage, -}: any) => { +interface RequiredFormProps { + theme: string, +} + +const NewThemePage = ({ + formik, + nextPage, + previousPage, +}: { + formik: FormikProps, + nextPage: (values: T) => void, + previousPage: (values: T, twoPagesBack?: boolean) => void, +}) => { const { t } = useTranslation(); const seriesThemes = useAppSelector(state => getSeriesThemes(state)); diff --git a/app/src/components/events/partials/wizards/NewSeriesSummary.tsx b/app/src/components/events/partials/wizards/NewSeriesSummary.tsx index 73495388c7..4b3656230c 100644 --- a/app/src/components/events/partials/wizards/NewSeriesSummary.tsx +++ b/app/src/components/events/partials/wizards/NewSeriesSummary.tsx @@ -10,17 +10,25 @@ import MetadataExtendedSummaryTable from "./summaryTables/MetadataExtendedSummar import AccessSummaryTable from "./summaryTables/AccessSummaryTable"; import WizardNavigationButtons from "../../../shared/wizard/WizardNavigationButtons"; import { useAppSelector } from "../../../../store"; +import { FormikProps } from "formik"; +import { TransformedAcl } from "../../../../slices/aclDetailsSlice"; /** * This component renders the summary page for new series in the new series wizard. */ -const NewSeriesSummary = ({ -// @ts-expect-error TS(7031): Binding element 'formik' implicitly has an 'any' t... Remove this comment to see the full error message +interface RequiredFormProps { + theme: string, + acls: TransformedAcl[], +} + +const NewSeriesSummary = ({ formik, -// @ts-expect-error TS(7031): Binding element 'previousPage' implicitly has an '... Remove this comment to see the full error message previousPage, -// @ts-expect-error TS(7031): Binding element 'metaDataExtendedHidden' implicitl... Remove this comment to see the full error message metaDataExtendedHidden, +}: { + formik: FormikProps, + previousPage: (values: T, twoPagesBack?: boolean) => void, + metaDataExtendedHidden: boolean, }) => { const { t } = useTranslation(); diff --git a/app/src/components/events/partials/wizards/NewSeriesWizard.tsx b/app/src/components/events/partials/wizards/NewSeriesWizard.tsx index dc19ed9f00..3ca7dfa04b 100644 --- a/app/src/components/events/partials/wizards/NewSeriesWizard.tsx +++ b/app/src/components/events/partials/wizards/NewSeriesWizard.tsx @@ -16,6 +16,9 @@ import { getInitialMetadataFieldValues } from "../../../../utils/resourceUtils"; import { useAppDispatch, useAppSelector } from "../../../../store"; import { postNewSeries } from "../../../../slices/seriesSlice"; import { getUserInformation } from "../../../../selectors/userInfoSelectors"; +import { MetadataCatalog } from "../../../../slices/eventSlice"; +import { UserInfoState } from "../../../../slices/userInfoSlice"; +import { TransformedAcl } from "../../../../slices/aclDetailsSlice"; /** * This component manages the pages of the new series wizard and the submission of values @@ -35,7 +38,7 @@ const NewSeriesWizard: React.FC<{ const [page, setPage] = useState(0); const [snapshot, setSnapshot] = useState(initialValues); - const [pageCompleted, setPageCompleted] = useState({}); + const [pageCompleted, setPageCompleted] = useState<{ [key: number]: boolean }>({}); // Caption of steps used by Stepper const steps = [ @@ -69,13 +72,17 @@ const NewSeriesWizard: React.FC<{ // Validation schema of current page const currentValidationSchema = NewSeriesSchema[page]; -// @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type. - const nextPage = (values) => { + const nextPage = ( + values: { + [key: string]: any; + acls: TransformedAcl[]; + theme: string; + } + ) => { setSnapshot(values); // set page as completely filled out let updatedPageCompleted = pageCompleted; -// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message updatedPageCompleted[page] = true; setPageCompleted(updatedPageCompleted); @@ -86,8 +93,14 @@ const NewSeriesWizard: React.FC<{ } }; -// @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type. - const previousPage = (values, twoPagesBack) => { + const previousPage = ( + values: { + [key: string]: any; + acls: TransformedAcl[]; + theme: string; + }, + twoPagesBack?: boolean + ) => { setSnapshot(values); // if previous page is hidden or not always shown, then go back two pages if (steps[page - 1].hidden || twoPagesBack) { @@ -97,8 +110,14 @@ const NewSeriesWizard: React.FC<{ } }; -// @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type. - const handleSubmit = (values) => { + const handleSubmit = ( + values: + { + [key: string]: any; + acls: TransformedAcl[]; + theme: string; + } + ) => { const response = dispatch(postNewSeries({values, metadataInfo: metadataFields, extendedMetadata})); console.info(response); close(); @@ -180,8 +199,11 @@ const NewSeriesWizard: React.FC<{ ); }; -// @ts-expect-error TS(7006): Parameter 'metadataFields' implicitly has an 'any'... Remove this comment to see the full error message -const getInitialValues = (metadataFields, extendedMetadata, user) => { +const getInitialValues = ( + metadataFields: MetadataCatalog, + extendedMetadata: MetadataCatalog[], + user: UserInfoState +) => { let initialValues = initialFormValuesNewSeries; // Transform metadata fields provided by backend (saved in redux) initialValues = {...initialValues, ...getInitialMetadataFieldValues( diff --git a/app/src/slices/seriesSlice.ts b/app/src/slices/seriesSlice.ts index b2e6aa1a40..3a1f790f32 100644 --- a/app/src/slices/seriesSlice.ts +++ b/app/src/slices/seriesSlice.ts @@ -154,17 +154,18 @@ export const fetchSeriesThemes = createAsyncThunk('series/fetchSeriesThemes', as // post new series to backend export const postNewSeries = createAsyncThunk('series/postNewSeries', async (params: { values: { + [key: string]: any; acls: TransformedAcls, - contributor: string[], - creator: string[], - description: string, - language: string, - license: string, - publisher: string[], - rightsHolder: string, - subject: string, + // contributor: string[], + // creator: string[], + // description: string, + // language: string, + // license: string, + // publisher: string[], + // rightsHolder: string, + // subject: string, theme: string, - title: string, + // title: string, }, metadataInfo: MetadataCatalog, extendedMetadata: MetadataCatalog[] From f321d1437adb98a75fe5658e0440cabe357c6ec8 Mon Sep 17 00:00:00 2001 From: Arnei Date: Fri, 12 Apr 2024 11:30:45 +0200 Subject: [PATCH 03/30] Type uploadAssetOptions for new events Types the rest of NewEventWizard --- .../partials/wizards/NewEventSummary.tsx | 16 ++++--- .../partials/wizards/NewEventWizard.tsx | 30 ++++--------- app/src/configs/modalConfig.ts | 4 +- app/src/slices/eventDetailsSlice.ts | 5 ++- app/src/slices/eventSlice.ts | 43 ++++++++++++------- 5 files changed, 52 insertions(+), 46 deletions(-) diff --git a/app/src/components/events/partials/wizards/NewEventSummary.tsx b/app/src/components/events/partials/wizards/NewEventSummary.tsx index 4371aca664..a086577e30 100644 --- a/app/src/components/events/partials/wizards/NewEventSummary.tsx +++ b/app/src/components/events/partials/wizards/NewEventSummary.tsx @@ -61,16 +61,22 @@ const NewEventSummary = ({ ); // upload asset that user has provided -// @ts-expect-error TS(7034): Variable 'uploadAssetsNonTrack' implicitly has typ... Remove this comment to see the full error message - let uploadAssetsNonTrack = []; + let uploadAssetsNonTrack: { + name: string, + translate?: string, + type: string, + flavorType: string, + flavorSubType: string, + value: any, + }[] = []; for (let i = 0; uploadAssetsOptionsNonTrack.length > i; i++) { let fieldValue = formik.values[uploadAssetsOptionsNonTrack[i].id]; if (!!fieldValue) { -// @ts-expect-error TS(7005): Variable 'uploadAssetsNonTrack' implicitly has an ... Remove this comment to see the full error message + const displayOverride = uploadAssetsOptionsNonTrack[i].displayOverride uploadAssetsNonTrack = uploadAssetsNonTrack.concat({ name: uploadAssetsOptionsNonTrack[i].id, - translate: !!uploadAssetsOptionsNonTrack[i].displayOverride - ? t(uploadAssetsOptionsNonTrack[i].displayOverride) + translate: !!displayOverride + ? t(displayOverride) : t(uploadAssetsOptionsNonTrack[i].title), type: uploadAssetsOptionsNonTrack[i].type, flavorType: uploadAssetsOptionsNonTrack[i].flavorType, diff --git a/app/src/components/events/partials/wizards/NewEventWizard.tsx b/app/src/components/events/partials/wizards/NewEventWizard.tsx index ddedea7e88..5ab59c0276 100644 --- a/app/src/components/events/partials/wizards/NewEventWizard.tsx +++ b/app/src/components/events/partials/wizards/NewEventWizard.tsx @@ -18,7 +18,7 @@ import { getExtendedEventMetadata, } from "../../../../selectors/eventSelectors"; import { useAppDispatch, useAppSelector } from "../../../../store"; -import { MetadataCatalog, postNewEvent } from "../../../../slices/eventSlice"; +import { MetadataCatalog, UploadAssetOption, postNewEvent } from "../../../../slices/eventSlice"; import { getUserInformation } from "../../../../selectors/userInfoSelectors"; import { UserInfoState } from "../../../../slices/userInfoSlice"; @@ -43,7 +43,6 @@ const NewEventWizard: React.FC<{ uploadAssetOptions, user ); - let workflowPanelRef = React.useRef(); const [page, setPage] = useState(0); const [snapshot, setSnapshot] = useState(initialValues); @@ -93,8 +92,7 @@ const NewEventWizard: React.FC<{ // Validation schema of current page const currentValidationSchema = NewEventSchema[page]; -// @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type. - const nextPage = (values) => { + const nextPage = (values: typeof initialValues) => { setSnapshot(values); // set page as completely filled out @@ -109,8 +107,7 @@ const NewEventWizard: React.FC<{ } }; -// @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type. - const previousPage = (values, twoPagesBack) => { + const previousPage = (values: typeof initialValues, twoPagesBack?: boolean) => { setSnapshot(values); // if previous page is hidden or not always shown, than go back two pages if (steps[page - 1].hidden || twoPagesBack) { @@ -120,10 +117,7 @@ const NewEventWizard: React.FC<{ } }; -// @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type. - const handleSubmit = (values) => { -// @ts-expect-error TS(2339): Property 'submitForm' does not exist on type 'neve... Remove this comment to see the full error message - workflowPanelRef.current?.submitForm(); + const handleSubmit = (values: typeof initialValues) => { const response = dispatch(postNewEvent({values, metadataInfo: metadataFields, extendedMetadata})); console.info(response); close(); @@ -222,8 +216,7 @@ const NewEventWizard: React.FC<{ const getInitialValues = ( metadataFields: MetadataCatalog, extendedMetadata: MetadataCatalog[], -// @ts-expect-error TS(7006): Parameter 'uploadAssetOptions' implicitly has an '... Remove this comment to see the full error message - uploadAssetOptions, + uploadAssetOptions: UploadAssetOption[], user: UserInfoState ) => { let initialValues = initialFormValuesNewEvents; @@ -255,25 +248,18 @@ const getInitialValues = ( if (!!uploadAssetOptions) { initialValues.uploadAssetsTrack = []; // initial value of upload asset needs to be null, because object (file) is saved there -// @ts-expect-error TS(7006): Parameter 'option' implicitly has an 'any' type. - uploadAssetOptions.forEach((option) => { + for (const option of uploadAssetOptions) { if (option.type === "track") { -// @ts-expect-error TS(2339): Property 'uploadAssetsTrack' does not exist on typ... Remove this comment to see the full error message initialValues.uploadAssetsTrack.push({ ...option, - file: null, + file: undefined, }); } else { initialValues[option.id] = null; } - }); + }; } - // // Add all initial form values known upfront listed in newEventsConfig - // for (const [key, value] of Object.entries(initialFormValuesNewEvents)) { - // initialValues[key] = value; - // } - const defaultDate = new Date(); // fill times with some default values diff --git a/app/src/configs/modalConfig.ts b/app/src/configs/modalConfig.ts index db15604308..1511a405df 100644 --- a/app/src/configs/modalConfig.ts +++ b/app/src/configs/modalConfig.ts @@ -1,7 +1,7 @@ // All fields for new event form that are fix and not depending on response of backend // InitialValues of Formik form (others computed dynamically depending on responses from backend) import { initArray } from "../utils/utils"; -import { EditedEvents, Event } from "../slices/eventSlice"; +import { EditedEvents, Event, UploadAssetsTrack } from "../slices/eventSlice"; import { TransformedAcl } from "../slices/aclDetailsSlice"; // Context for notifications shown in modals @@ -26,7 +26,7 @@ export const initialFormValuesNewEvents: { configuration: { [key: string]: string }, aclTemplate: string, acls: TransformedAcl[], - uploadAssetsTrack?: any[] + uploadAssetsTrack?: UploadAssetsTrack[] [key: string]: unknown, // Metadata fields that are getting added later } = { sourceMode: "UPLOAD", diff --git a/app/src/slices/eventDetailsSlice.ts b/app/src/slices/eventDetailsSlice.ts index 3f2db72120..f79f1c1a77 100644 --- a/app/src/slices/eventDetailsSlice.ts +++ b/app/src/slices/eventDetailsSlice.ts @@ -1559,7 +1559,10 @@ export const updateAssets = createAsyncThunk('eventDetails/updateAssets', async let formData = new FormData(); - let assets = { + let assets: { + workflow: string, + options: UploadAssetOption[], + } = { workflow: uploadAssetWorkflow, options: [], }; diff --git a/app/src/slices/eventSlice.ts b/app/src/slices/eventSlice.ts index f46723dc51..53cb87bc72 100644 --- a/app/src/slices/eventSlice.ts +++ b/app/src/slices/eventSlice.ts @@ -130,6 +130,25 @@ export type EditedEvents = { weekday: string, } +export type UploadAssetOption = { + accept: string, + "displayFallback.DETAIL": string, + "displayFallback.SHORT": string, + displayOrder: number, + flavorSubType: string, + flavorType: string, + id: string, + multiple: boolean, + showAs: string, + title: string, + type: string, + displayOverride?: string, +} + +export type UploadAssetsTrack = UploadAssetOption & { + file?: FileList +} + type EventState = { status: 'uninitialized' | 'loading' | 'succeeded' | 'failed', error: SerializedError | null, @@ -149,7 +168,7 @@ type EventState = { metadata: MetadataCatalog, extendedMetadata: MetadataCatalog[], isFetchingAssetUploadOptions: boolean, - uploadAssetOptions: any[], + uploadAssetOptions: UploadAssetOption[], uploadAssetWorkflow: any, // TODO: proper typing schedulingInfo: { editedEvents: EditedEvents[], @@ -400,18 +419,7 @@ export const postNewEvent = createAsyncThunk('events/postNewEvent', async (param scheduleStartHour: string, scheduleStartMinute: string, sourceMode: string, - uploadAssetsTrack: { - accept: string, - displayOrder: number, - file: FileList, - flavorSubtType: string, - flavorType: string, - id: string, - multiple: boolean, - showAs: string, - title: string, - type: string, - }[], + uploadAssetsTrack?: UploadAssetsTrack[], [key: string]: unknown, }, metadataInfo: MetadataCatalog, @@ -424,7 +432,7 @@ export const postNewEvent = createAsyncThunk('events/postNewEvent', async (param const uploadAssetOptions = getAssetUploadOptions(state as RootState); let formData = new FormData(); - let metadataFields, extendedMetadataFields, metadata, source, access, assets; + let metadataFields, extendedMetadataFields, metadata, source, access; // prepare metadata provided by user metadataFields = prepareMetadataFieldsForPost(metadataInfo.fields, values); @@ -528,7 +536,10 @@ export const postNewEvent = createAsyncThunk('events/postNewEvent', async (param // information about upload assets options // need to provide all possible upload asset options independent of source mode/type - assets = { + let assets: { + workflow: string, + options: UploadAssetOption[], + }= { workflow: WORKFLOW_UPLOAD_ASSETS_NON_TRACK, options: [], }; @@ -540,7 +551,7 @@ export const postNewEvent = createAsyncThunk('events/postNewEvent', async (param uploadAssetOptions[i].type === "track" && values.sourceMode === "UPLOAD" ) { - let asset = values.uploadAssetsTrack.find( + let asset = values.uploadAssetsTrack?.find( (asset) => asset.id === uploadAssetOptions[i].id ); if (!!asset && !!asset.file) { From 405f79c586231fa3ee79414286286114357ddbdd Mon Sep 17 00:00:00 2001 From: Arnei Date: Fri, 12 Apr 2024 12:51:21 +0200 Subject: [PATCH 04/30] Type FileUpload.tsx Add typing to the file upload used when creating new Themes. --- .../partials/wizard/BumperPage.tsx | 1 + .../partials/wizard/TitleSlidePage.tsx | 1 + .../partials/wizard/WatermarkPage.tsx | 1 + .../components/shared/wizard/FileUpload.tsx | 57 ++++++++++--------- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/app/src/components/configuration/partials/wizard/BumperPage.tsx b/app/src/components/configuration/partials/wizard/BumperPage.tsx index 01fb9a149b..af9b88a43f 100644 --- a/app/src/components/configuration/partials/wizard/BumperPage.tsx +++ b/app/src/components/configuration/partials/wizard/BumperPage.tsx @@ -12,6 +12,7 @@ import Notifications from "../../../shared/Notifications"; interface RequiredFormProps { bumperActive: boolean, trailerActive: boolean, + [key: string]: unknown, } const BumperPage = ({ diff --git a/app/src/components/configuration/partials/wizard/TitleSlidePage.tsx b/app/src/components/configuration/partials/wizard/TitleSlidePage.tsx index 3db480d974..48aaaacab6 100644 --- a/app/src/components/configuration/partials/wizard/TitleSlidePage.tsx +++ b/app/src/components/configuration/partials/wizard/TitleSlidePage.tsx @@ -10,6 +10,7 @@ import FileUpload from "../../../shared/wizard/FileUpload"; interface RequiredFormProps { titleSlideActive: boolean, titleSlideMode: string, + [key: string]: unknown, } const TitleSlidePage = ({ diff --git a/app/src/components/configuration/partials/wizard/WatermarkPage.tsx b/app/src/components/configuration/partials/wizard/WatermarkPage.tsx index dc3940f962..6af725d396 100644 --- a/app/src/components/configuration/partials/wizard/WatermarkPage.tsx +++ b/app/src/components/configuration/partials/wizard/WatermarkPage.tsx @@ -14,6 +14,7 @@ interface RequiredFormProps { watermarkActive: boolean, watermarkFile: string, watermarkPosition: string, + [key: string]: unknown, } const WatermarkPage = ({ diff --git a/app/src/components/shared/wizard/FileUpload.tsx b/app/src/components/shared/wizard/FileUpload.tsx index a5e135035a..fb823b4a8d 100644 --- a/app/src/components/shared/wizard/FileUpload.tsx +++ b/app/src/components/shared/wizard/FileUpload.tsx @@ -4,20 +4,16 @@ import axios from "axios"; import { NOTIFICATION_CONTEXT } from "../../../configs/modalConfig"; import { useAppDispatch } from "../../../store"; import { addNotification } from "../../../slices/notificationSlice"; +import { FormikProps } from "formik"; /** * This component renders a custom file upload button in wizards. */ -const FileUpload : React.FC<{ - descriptionKey?: any, - labelKey: any, - buttonKey: any, - acceptableTypes: any, - fileId: any, - fileName: any, - formik: any, - isEdit: any, -}> = ({ +interface RequiredFormProps { + [key: string]: unknown, +} + +const FileUpload = ({ descriptionKey, labelKey, buttonKey, @@ -26,28 +22,36 @@ const FileUpload : React.FC<{ fileName, formik, isEdit, +}: { + descriptionKey?: string, + labelKey: string, + buttonKey: string, + acceptableTypes: string, + fileId: string, + fileName: string, + formik: FormikProps, + isEdit?: boolean, }) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); // Temporary storage for uploaded file - const [file, setFile] = useState({}); + const [file, setFile] = useState(); // how much is uploaded; used for progress bar const [loaded, setLoaded] = useState(0); // reference used for activating file input when button is clicked - const hiddenFileInput = useRef(null); + const hiddenFileInput = useRef(null); const handleDelete = () => { - setFile({}); + setFile(undefined); setLoaded(0); formik.setFieldValue(fileId, ""); formik.setFieldValue(fileName, ""); }; // upload file to backend -// @ts-expect-error TS(7006): Parameter 'file' implicitly has an 'any' type. - const upload = (file) => { + const upload = (file: File) => { const data = new FormData(); data.append("BODY", file, file.name); axios @@ -57,8 +61,7 @@ const FileUpload : React.FC<{ }, onUploadProgress: (ProgressEvent) => { // update loaded with current progress -// @ts-expect-error TS(2532): Object is possibly 'undefined'. - setLoaded((ProgressEvent.loaded / ProgressEvent.total) * 100); + setLoaded(ProgressEvent.total ? (ProgressEvent.loaded / ProgressEvent.total) * 100 : 0); }, }) .then((res) => { @@ -80,14 +83,14 @@ const FileUpload : React.FC<{ }; const handleClick = () => { -// @ts-expect-error TS(2531): Object is possibly 'null'. - hiddenFileInput.current.click(); + hiddenFileInput.current?.click(); }; -// @ts-expect-error TS(7006): Parameter 'e' implicitly has an 'any' type. - const handleChange = (e) => { - setFile(e.target.files[0]); - upload(e.target.files[0]); + const handleChange = (e: React.ChangeEvent) => { + if (e.target.files) { + setFile(e.target.files[0]); + upload(e.target.files[0]); + } }; return ( @@ -102,15 +105,14 @@ const FileUpload : React.FC<{
{/* If user already uploaded a file, its name and a delete button is rendered */} {/* else render button for upload */} - {!!formik.values[fileId] ? ( + {!!formik.values[fileId] && file ? (

-{/* @ts-expect-error TS(2339): */} - + {formik.values[fileName]}

@@ -145,8 +147,7 @@ const FileUpload : React.FC<{
{/* render progress bar while loaded is under 100 and a file is in the upload */} -{/* @ts-expect-error TS(2339): Property 'name' does not exist on type '{}'.*/} - {!!file.name && loaded < 100 && ( + {!! file && !!file.name && loaded < 100 && (
From 826f0a4cef8f39e651c0d1580037a104f3927654 Mon Sep 17 00:00:00 2001 From: Arnei Date: Fri, 12 Apr 2024 14:48:29 +0200 Subject: [PATCH 05/30] Type RenderWorkflowConfig With this, all parameters called "formik" should be typed. --- .../ModalTabsAndPages/NewProcessingPage.tsx | 1 + .../StartTaskWorkflowPage.tsx | 1 + .../partials/wizards/RenderWorkflowConfig.tsx | 63 +++++++++++-------- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/app/src/components/events/partials/ModalTabsAndPages/NewProcessingPage.tsx b/app/src/components/events/partials/ModalTabsAndPages/NewProcessingPage.tsx index a8ef7c0b21..f63b407e33 100644 --- a/app/src/components/events/partials/ModalTabsAndPages/NewProcessingPage.tsx +++ b/app/src/components/events/partials/ModalTabsAndPages/NewProcessingPage.tsx @@ -108,6 +108,7 @@ const NewProcessingPage = ({ ) : null} diff --git a/app/src/components/events/partials/ModalTabsAndPages/StartTaskWorkflowPage.tsx b/app/src/components/events/partials/ModalTabsAndPages/StartTaskWorkflowPage.tsx index 7f60f9b4e8..473eaf6086 100644 --- a/app/src/components/events/partials/ModalTabsAndPages/StartTaskWorkflowPage.tsx +++ b/app/src/components/events/partials/ModalTabsAndPages/StartTaskWorkflowPage.tsx @@ -94,6 +94,7 @@ const StartTaskWorkflowPage = ({
diff --git a/app/src/components/events/partials/wizards/RenderWorkflowConfig.tsx b/app/src/components/events/partials/wizards/RenderWorkflowConfig.tsx index 98595f7bbf..7431cf6941 100644 --- a/app/src/components/events/partials/wizards/RenderWorkflowConfig.tsx +++ b/app/src/components/events/partials/wizards/RenderWorkflowConfig.tsx @@ -1,6 +1,6 @@ import React from "react"; import { v4 as uuidv4 } from "uuid"; -import { Field } from "formik"; +import { Field, FormikProps } from "formik"; import { getWorkflowDefById, } from "../../../../selectors/workflowSelectors"; @@ -10,14 +10,18 @@ import { useAppSelector } from "../../../../store"; * This component renders the configuration panel for the selected workflow in the processing step of the new event * wizard chosen via dropdown. */ -const RenderWorkflowConfig: React.FC<{ - workflowId: string - formik: any //TODO: Add type - displayDescription?: any -}> = ({ +interface RequiredFormProps { + configuration?: { [key: string]: any } +} + +const RenderWorkflowConfig = ({ workflowId, formik, displayDescription +}: { + workflowId: string + formik: FormikProps + displayDescription?: boolean }) => { const workflowDef = useAppSelector(state => getWorkflowDefById(state, workflowId)); @@ -82,8 +86,11 @@ const RenderWorkflowConfig: React.FC<{ }; // render input depending on field type -// @ts-expect-error TS(7006): Parameter 'field' implicitly has an 'any' type. -const renderInputByType = (field, key, formik) => { +const renderInputByType = ( + field: any, + key: React.Key | null | undefined, + formik: FormikProps, +) => { switch (field.type) { case "checkbox": return ; @@ -100,26 +107,25 @@ const renderInputByType = (field, key, formik) => { } }; -// @ts-expect-error TS(7031): Binding element 'field' implicitly has an 'any' ty... Remove this comment to see the full error message -const RenderDatetimeLocal = ({ field, formik }) => { +const RenderDatetimeLocal = ( + { field, formik } : { field: any, formik: FormikProps }) => { return ; }; -// @ts-expect-error TS(7031): Binding element 'field' implicitly has an 'any' ty... Remove this comment to see the full error message -const RenderCheckbox = ({ field, formik }) => { +const RenderCheckbox = ( + { field, formik } : { field: any, formik: FormikProps }) => { return ; }; -// @ts-expect-error TS(7031): Binding element 'field' implicitly has an 'any' ty... Remove this comment to see the full error message -const RenderRadio = ({ field, formik }) => { +const RenderRadio = ( + { field, formik } : { field: any, formik: FormikProps }) => { return ; }; -// @ts-expect-error TS(7031): Binding element 'field' implicitly has an 'any' ty... Remove this comment to see the full error message -const RenderNumber = ({ field, formik }) => { +const RenderNumber = ( + { field, formik } : { field: any, formik: FormikProps }) => { // validate that value of number is between max and min -// @ts-expect-error TS(7006): Parameter 'value' implicitly has an 'any' type. - const validate = (value) => { + const validate = (value: string) => { let error; if (parseInt(value) > field.max || parseInt(value) < field.min) { error = "out of range"; @@ -130,19 +136,24 @@ const RenderNumber = ({ field, formik }) => { return ; }; -// @ts-expect-error TS(7031): Binding element 'field' implicitly has an 'any' ty... Remove this comment to see the full error message -const RenderText = ({ field, formik }) => { +const RenderText = ({ + field, + formik +}: { + field: any, + formik: FormikProps, +}) => { return ; }; -const RenderField : React.FC<{ - field: any, - formik: any, - validate?: (value: any) => string | undefined, -}> = ({ +const RenderField = ({ field, formik, validate = undefined +}: { + field: any, + formik: FormikProps, + validate?: (value: any) => string | undefined, }) => { // id used for Field and label const uuid = uuidv4(); @@ -170,7 +181,7 @@ const RenderField : React.FC<{ {/* if input has an additional fieldset or further configuration inputs then render again by input type*/} - {!!field.fieldset && !!formik.values.configuration[field.name] && ( + {!!field.fieldset && !!formik.values.configuration && !!formik.values.configuration[field.name] && (
    {/* @ts-expect-error TS(7006): Parameter 'f' implicitly has an 'any' type. */} {field.fieldset?.map((f, keys) => renderInputByType(f, keys, formik))} From 3fc7fe74f6135d9a746e5d3c7a400bba50a4e605 Mon Sep 17 00:00:00 2001 From: Arnei Date: Fri, 12 Apr 2024 15:41:39 +0200 Subject: [PATCH 06/30] Fix issue with event table not updating By typing stuff in eventSlice --- app/src/slices/eventDetailsSlice.ts | 2 +- app/src/slices/eventSlice.ts | 27 ++++----------------------- app/src/thunks/assetsThunks.ts | 7 ++++--- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/app/src/slices/eventDetailsSlice.ts b/app/src/slices/eventDetailsSlice.ts index f79f1c1a77..f4d9a89f97 100644 --- a/app/src/slices/eventDetailsSlice.ts +++ b/app/src/slices/eventDetailsSlice.ts @@ -1560,7 +1560,7 @@ export const updateAssets = createAsyncThunk('eventDetails/updateAssets', async let formData = new FormData(); let assets: { - workflow: string, + workflow: string | undefined, options: UploadAssetOption[], } = { workflow: uploadAssetWorkflow, diff --git a/app/src/slices/eventSlice.ts b/app/src/slices/eventSlice.ts index 53cb87bc72..16d76d374c 100644 --- a/app/src/slices/eventSlice.ts +++ b/app/src/slices/eventSlice.ts @@ -25,6 +25,7 @@ import { fetchSeriesOptions } from "../slices/seriesSlice"; import { AppDispatch, RootState } from '../store'; import { fetchAssetUploadOptions } from '../thunks/assetsThunks'; import { TransformedAcls } from './aclDetailsSlice'; +import { TableConfig } from '../configs/tableConfigs/aclsTableConfig'; /** * This file contains redux reducer for actions affecting the state of events @@ -159,7 +160,7 @@ type EventState = { statusAssetUploadOptions: 'uninitialized' | 'loading' | 'succeeded' | 'failed', errorAssetUploadOptions: SerializedError | null, results: Event[], - columns: any, // TODO: proper typing, derive from `initialColumns` + columns: TableConfig["columns"], // TODO: proper typing, derive from `initialColumns` total: number, count: number, offset: number, @@ -169,7 +170,7 @@ type EventState = { extendedMetadata: MetadataCatalog[], isFetchingAssetUploadOptions: boolean, uploadAssetOptions: UploadAssetOption[], - uploadAssetWorkflow: any, // TODO: proper typing + uploadAssetWorkflow: string | undefined, // TODO: proper typing schedulingInfo: { editedEvents: EditedEvents[], seriesOptions: { @@ -1059,31 +1060,13 @@ const eventSlice = createSlice({ setEventColumns(state, action: PayloadAction< EventState["columns"] >) { - state.columns = action.payload.updatedColumns; + state.columns = action.payload; }, setShowActions(state, action: PayloadAction< EventState["showActions"] >) { state.showActions = action.payload; }, - setEventSelected(state, action: PayloadAction< - any - >) { - // state.rows: state.rows.map((row) => { - // if (row.id === id) { - // return { - // ...row, - // selected: !row.selected, - // }; - // } - // return row; - // }), - }, - setAssetUploadWorkflow(state, action: PayloadAction<{ - workflow: EventState["columns"], - }>) { - state.uploadAssetWorkflow = action.payload.workflow; - }, }, // These are used for thunks extraReducers: builder => { @@ -1170,8 +1153,6 @@ const eventSlice = createSlice({ export const { setEventColumns, setShowActions, - setEventSelected, - setAssetUploadWorkflow, } = eventSlice.actions; // Export the slice reducer as the default export diff --git a/app/src/thunks/assetsThunks.ts b/app/src/thunks/assetsThunks.ts index 27d970d091..fcbe20793c 100644 --- a/app/src/thunks/assetsThunks.ts +++ b/app/src/thunks/assetsThunks.ts @@ -2,6 +2,7 @@ import axios from "axios"; import { getAssetUploadOptions } from "../selectors/eventSelectors"; import { createAsyncThunk } from "@reduxjs/toolkit"; import { RootState } from "../store"; +import { UploadAssetOption } from "../slices/eventSlice"; // thunks for assets, especially for getting asset options @@ -17,7 +18,7 @@ export const fetchAssetUploadOptions = createAsyncThunk('assets/fetchAssetUpload // only fetch asset upload options, if they haven't been fetched yet if (!(assetUploadOptions.length !== 0 && assetUploadOptions.length !== 0)) { let workflow; - let newAssetUploadOptions: any[] = []; + let newAssetUploadOptions: UploadAssetOption[] = []; // request asset upload options from API await axios @@ -34,7 +35,7 @@ export const fetchAssetUploadOptions = createAsyncThunk('assets/fetchAssetUpload // if the line is a source upload option or additional asset upload option, // format it and add to upload options list if (isSourceOption || isAssetOption) { - let option = JSON.parse(optionJson as any); + let option = JSON.parse(optionJson as string); option = { ...option, @@ -45,7 +46,7 @@ export const fetchAssetUploadOptions = createAsyncThunk('assets/fetchAssetUpload newAssetUploadOptions.push(option); } else if (optionKey.indexOf(workflowPrefix) >= 0) { // if the line is the upload asset workflow id, set the asset upload workflow - workflow = optionJson; + workflow = optionJson as string; } } } From b2b6909b964c1e5b3a94276239f57ce3768fd8f5 Mon Sep 17 00:00:00 2001 From: Lars Kiesow Date: Mon, 6 May 2024 21:04:47 +0200 Subject: [PATCH 07/30] Add simple CI test This patch adds a simple CI workflow to test if the dependencies can be downloaded and the project actually builds. --- .github/workflows/test.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..6400998b88 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,30 @@ +name: Test build + +on: + push: + branches-ignore: + - 'dependabot/**' + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: checkout code + uses: actions/checkout@v3 + + - name: get node.js + uses: actions/setup-node@v3 + with: + node-version: 20 + + - name: download tooling dependencies + run: npm ci + + - name: download dependencies + working-directory: ./app + run: npm ci + + - name: build project + working-directory: ./app + run: npm run build From fb02783a4a1534acdaefbbe9c8bd13776bd1256d Mon Sep 17 00:00:00 2001 From: Daniel Ziegenberg Date: Wed, 8 May 2024 00:39:59 +0200 Subject: [PATCH 08/30] Fix links in README.md Signed-off-by: Daniel Ziegenberg --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eb640e357d..5a7f339b58 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ not work when using this mode. How to cut a release for Opencast --------------------------------- -1. [NOT YET FUNCTIONAL] (Optional) Run the [Update translations](https://github.com/opencast/opencast-editor/actions/workflows/update-translations.yml) workflow, to make sure all changes from crowdin are included in the next release. +1. [NOT YET FUNCTIONAL] (Optional) Run the [Update translations](https://github.com/opencast/opencast-admin-interface/actions/workflows/update-translations.yml/actions/workflows/update-translations.yml) workflow, to make sure all changes from crowdin are included in the next release. 1. Switch to the commit you want to turn into the release 1. Create and push a new tag ```bash @@ -75,9 +75,9 @@ How to cut a release for Opencast git tag -m Release -s "$DATE" git push upstream "$DATE":"$DATE" ``` -1. Wait for the [Create release draft](https://github.com/opencast/opencast-editor/actions/workflows/create-release.yml) +1. Wait for the [Create release draft](https://github.com/opencast/opencast-admin-interface/actions/workflows/create-release.yml) workflow to finish - - It will create a new [GitHub release draft](https://github.com/opencast/opencast-editor/releases) + - It will create a new [GitHub release draft](https://github.com/opencast/opencast-admin-interface/releases) - Review and publish the draft 1. Submit a pull request against Opencast - [Update the release](https://github.com/opencast/opencast/blob/b2bea8822b95b8692bb5bbbdf75c9931c2b7298a/modules/admin-ui-interface/pom.xml#L16-L17) From e39019e929b4a114d8e5ac4166aec43d31d4439c Mon Sep 17 00:00:00 2001 From: Daniel Ziegenberg Date: Wed, 8 May 2024 00:41:18 +0200 Subject: [PATCH 09/30] Remove last reference to the editor from README.md Signed-off-by: Daniel Ziegenberg --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a7f339b58..976c6c367c 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ repository yourself. Translating the Admin UI ------------- -You can help translating the editor to your language on [crowdin.com/project/opencast-admin-interface](https://crowdin.com/project/opencast-admin-interface). Simply request to join the project on Crowdin and start translating. If you are interested in translating a language which is not a target language right now, please create [a GitHub issue](https://github.com/opencast/opencast-admin-interface/issues) and we will add the language. +You can help translating the Opencast Admin UI to your language on [crowdin.com/project/opencast-admin-interface](https://crowdin.com/project/opencast-admin-interface). Simply request to join the project on Crowdin and start translating. If you are interested in translating a language which is not a target language right now, please create [a GitHub issue](https://github.com/opencast/opencast-admin-interface/issues) and we will add the language. This project follows the general form of [Opencast's Localization Process](https://docs.opencast.org/develop/developer/#participate/localization/), especially regarding what happens when you need to [change an existing translation key](https://docs.opencast.org/develop/developer/#participate/localization/#i-need-to-update-the-wording-of-the-source-translation-what-happens). Any questions not answered there should be referred to the mailing lists! From 5742ca74a63fd314444db4ae9fc84d41bbabeb8a Mon Sep 17 00:00:00 2001 From: Daniel Ziegenberg Date: Wed, 8 May 2024 00:42:19 +0200 Subject: [PATCH 10/30] markdownlint README.md and improve some grammar and style Signed-off-by: Daniel Ziegenberg --- README.md | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 976c6c367c..92eeee4476 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Opencast Admin UI The Opencast Admin UI is a graphical interface included by Opencast to give admins an easy way of managing their Opencast instance. - Quickstart ---------- + Commands to hack into your console to get to testing pull requests ASAP: ```console @@ -21,13 +21,13 @@ npm run proxy-server http://stable.opencast.org opencast_system_account CHANGE_M Open a second tab: -``` +```bash npm run client ``` Open a third tab to checkout the pull request you want to test. You need to know the pull request number!: -``` +```bash git fetch origin pull/{PULL REQUEST NUMBER HERE}/head:some-branch-name-of-your-choosing git checkout some-branch-name-of-your-choosing ``` @@ -39,25 +39,30 @@ Before starting, get the project dependencies by running `npm ci` beforehand bo To test with real data run: - npm run proxy-server http://stable.opencast.org *opencast_digest_username* *opencast_digest_password* +```shell +npm run proxy-server http://stable.opencast.org *opencast_digest_username* *opencast_digest_password* +``` This will start a proxy server at localhost:5000. It will automatically proxy -requests to a Opencast instance at http://stable.opencast.org. You can change -the url to at a different Opencast if you wish (e.g. http://localhost.8080 for +requests to an Opencast instance at http://stable.opencast.org. You can change the URL to a different Opencast if you wish (e.g., http://localhost.8080 for a local Opencast installation). Note that `http` is required. You can then start the client in a different tab by running: - npm run client +```shell +npm run client +``` This will start a client server in the development mode. Open [http://localhost:3000](localhost:3000) to view it in the browser. -------- -Alternatively you can spin up a mock instance of the admin ui with: +Alternatively, you can spin up a mock instance of the admin ui with: - npm start +```shell +npm start +``` This uses mock data instead of a real Opencast. This means certain features will not work when using this mode. @@ -67,14 +72,16 @@ not work when using this mode. How to cut a release for Opencast --------------------------------- -1. [NOT YET FUNCTIONAL] (Optional) Run the [Update translations](https://github.com/opencast/opencast-admin-interface/actions/workflows/update-translations.yml/actions/workflows/update-translations.yml) workflow, to make sure all changes from crowdin are included in the next release. +1. [NOT YET FUNCTIONAL] (Optional) Run the [Update translations](https://github.com/opencast/opencast-admin-interface/actions/workflows/update-translations.yml/actions/workflows/update-translations.yml) workflow to ensure all changes from crowdin are included in the next release. 1. Switch to the commit you want to turn into the release 1. Create and push a new tag + ```bash DATE=$(date +%Y-%m-%d) git tag -m Release -s "$DATE" git push upstream "$DATE":"$DATE" ``` + 1. Wait for the [Create release draft](https://github.com/opencast/opencast-admin-interface/actions/workflows/create-release.yml) workflow to finish - It will create a new [GitHub release draft](https://github.com/opencast/opencast-admin-interface/releases) @@ -85,30 +92,27 @@ How to cut a release for Opencast if necessary - Verify that the new release runs in Opencast, then create the pull request. - - Opencast API used by the Admin UI ------------- + The Admin UI accesses all endpoints in Opencast located under * `/admin-ng/*` -If you want to use current version of the frontend with an earlier Opencast -version, you will have to cherry pick the relevant commits from the Opencast +If you want to use the current version of the frontend with an earlier Opencast +version, you will have to cherry-pick the relevant commits from the Opencast repository yourself. - - Translating the Admin UI ------------- -You can help translating the Opencast Admin UI to your language on [crowdin.com/project/opencast-admin-interface](https://crowdin.com/project/opencast-admin-interface). Simply request to join the project on Crowdin and start translating. If you are interested in translating a language which is not a target language right now, please create [a GitHub issue](https://github.com/opencast/opencast-admin-interface/issues) and we will add the language. - -This project follows the general form of [Opencast's Localization Process](https://docs.opencast.org/develop/developer/#participate/localization/), especially regarding what happens when you need to [change an existing translation key](https://docs.opencast.org/develop/developer/#participate/localization/#i-need-to-update-the-wording-of-the-source-translation-what-happens). Any questions not answered there should be referred to the mailing lists! +You can help translate the Opencast Admin UI to your language on [crowdin.com/project/opencast-admin-interface](https://crowdin.com/project/opencast-admin-interface). Simply request to join the project on Crowdin and start translating. If you are interested in translating a language that is not a target language right now, please create [a GitHub issue](https://github.com/opencast/opencast-admin-interface/issues) and we will add the language. +This project follows the general form of [Opencast's Localization Process](https://docs.opencast.org/develop/developer/#participate/localization/), especially regarding what happens when you need to [change an existing translation key](https://docs.opencast.org/develop/developer/#participate/localization/#i-need-to-update-the-wording-of-the-source-translation-what-happens). Any questions not answered there should be referred to the mailing lists! Configuration ------------- + The Admin UI frontend cannot be directly configured. Rather, it adapts to the various configurations in the Opencast backend. TODO: Throw in some links to the docs, which ones? \ No newline at end of file From 9596335177c75ca21fb3ffb8c67eefde3bccef4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Sch=C3=BCttl=C3=B6ffel?= Date: Wed, 8 May 2024 09:39:32 +0200 Subject: [PATCH 11/30] added i18n for subtitles, added subtitles to test file --- .../org/opencastproject/adminui/languages/lang-en_US.json | 4 ++++ test/app/GET/admin-ng/resources/eventUploadAssetOptions.json | 1 + 2 files changed, 5 insertions(+) diff --git a/app/src/i18n/org/opencastproject/adminui/languages/lang-en_US.json b/app/src/i18n/org/opencastproject/adminui/languages/lang-en_US.json index f6ce6aa1a9..ab2db1d7b7 100644 --- a/app/src/i18n/org/opencastproject/adminui/languages/lang-en_US.json +++ b/app/src/i18n/org/opencastproject/adminui/languages/lang-en_US.json @@ -489,6 +489,10 @@ "MULTIPLE_PARTS": { "SHORT": "Multiple parts", "DETAIL": "A set of files containing different parts of the event." + }, + "SUBTITLES": { + "SHORT": "Subtitles", + "DETAIL": "A subtitle file in vtt format" } }, "DATE_TIME": { diff --git a/test/app/GET/admin-ng/resources/eventUploadAssetOptions.json b/test/app/GET/admin-ng/resources/eventUploadAssetOptions.json index 462b8d4681..ca02f4cada 100644 --- a/test/app/GET/admin-ng/resources/eventUploadAssetOptions.json +++ b/test/app/GET/admin-ng/resources/eventUploadAssetOptions.json @@ -9,6 +9,7 @@ "EVENTS.EVENTS.NEW.SOURCE.UPLOAD.SEGMENTABLE": "{\"id\": \"track_presentation\",\"type\":\"track\", \"flavorType\": \"presentation\",\"flavorSubType\": \"source\", \"multiple\":false,\"displayOrder\":11}", "EVENTS.EVENTS.NEW.SOURCE.UPLOAD.UNKNOWN": "{\"id\": \"track_unknown\",\"type\":\"track\", \"flavorType\": \"presentation\",\"flavorSubType\": \"unknown\", \"multiple\":false,\"displayOrder\":11, \"displayOverride.SHORT\":\"Override Text\", \"displayFallback.DETAIL\":\"Fallback detail text\"}", "EVENTS.EVENTS.NEW.SOURCE.UPLOAD.AUDIO_ONLY": "{\"id\": \"track_audio\",\"type\":\"track\", \"flavorType\": \"audio\",\"flavorSubType\": \"source\", \"multiple\":false,\"displayOrder\":6, \"displayOverride.SHORT\":\"Hi I'm the translate text short override!\", \"displayOverride.DETAIL\":\"Hi I'm the translate text detail override!\"}", + "EVENTS.EVENTS.NEW.SOURCE.UPLOAD.SUBTITLES": "{\"id\": \"track_subtitles\", \"type\":\"track\", \"flavorType\": \"captions\", \"flavorSubType\": \"source\", \"tags\": \"generator:unknown\", \"displayOrder\":3, \"accept\": \".vtt\"}", "EVENTS.EVENTS.NEW.UPLOAD_ASSET.WORKFLOWDEFID": "publish-uploaded-assets" } From bf607c7494d7d6b1621cb28e92a5dbb93c9d3679 Mon Sep 17 00:00:00 2001 From: Daniel Ziegenberg Date: Thu, 9 May 2024 03:44:04 +0200 Subject: [PATCH 12/30] Typing WizardStepperEvent Signed-off-by: Daniel Ziegenberg --- .../shared/wizard/WizardStepperEvent.tsx | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/app/src/components/shared/wizard/WizardStepperEvent.tsx b/app/src/components/shared/wizard/WizardStepperEvent.tsx index 1f66a80d31..1fc51d67e3 100644 --- a/app/src/components/shared/wizard/WizardStepperEvent.tsx +++ b/app/src/components/shared/wizard/WizardStepperEvent.tsx @@ -7,27 +7,32 @@ import { useStepperStyle, } from "../../../utils/wizardUtils"; import CustomStepIcon from "./CustomStepIcon"; +import { FormikProps } from "formik/dist/types"; const WizardStepperEvent = ({ -// @ts-expect-error TS(7031): Binding element 'steps' implicitly has an 'any' ty... Remove this comment to see the full error message steps, -// @ts-expect-error TS(7031): Binding element 'page' implicitly has an 'any' typ... Remove this comment to see the full error message page, -// @ts-expect-error TS(7031): Binding element 'setPage' implicitly has an 'any' ... Remove this comment to see the full error message setPage, -// @ts-expect-error TS(7031): Binding element 'formik' implicitly has an 'any' t... Remove this comment to see the full error message formik, -// @ts-expect-error TS(7031): Binding element 'completed' implicitly has an 'any... Remove this comment to see the full error message completed, -// @ts-expect-error TS(7031): Binding element 'setCompleted' implicitly has an '... Remove this comment to see the full error message setCompleted, +} : { + steps: { + name: string, + translation: string, + hidden?: boolean, + }[], + page: number, + setPage: (num: number) => void, + formik: FormikProps, + completed: Record, + setCompleted: (rec: Record) => void, }) => { const { t } = useTranslation(); const classes = useStepperStyle(); -// @ts-expect-error TS(7006): Parameter 'key' implicitly has an 'any' type. - const handleOnClick = async (key) => { + const handleOnClick = async (key: number) => { if (isSummaryReachable(key, steps, completed)) { if (completed[key]) { @@ -57,7 +62,6 @@ const WizardStepperEvent = ({ connector={false} className={cn("step-by-step", classes.root)} > -{/* @ts-expect-error TS(7006): Parameter 'label' implicitly has an 'any' type. */} {steps.map((label, key) => !label.hidden ? ( From 417f680a914df4aae81a326e4ac198c7e2fabb19 Mon Sep 17 00:00:00 2001 From: Daniel Ziegenberg Date: Thu, 9 May 2024 04:17:42 +0200 Subject: [PATCH 13/30] Remove the ServiceWorker implementation The serviceWorker was never used, and having the Admin UI work offline doesn't make sense, so we can safely remove the whole ordeal. Fixes #45 --- app/src/index.tsx | 5 -- app/src/serviceWorker.ts | 145 --------------------------------------- 2 files changed, 150 deletions(-) delete mode 100644 app/src/serviceWorker.ts diff --git a/app/src/index.tsx b/app/src/index.tsx index 994257080c..edec7a52ac 100644 --- a/app/src/index.tsx +++ b/app/src/index.tsx @@ -2,7 +2,6 @@ import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; -import * as serviceWorker from "./serviceWorker"; // redux imports import { persistStore } from "redux-persist"; @@ -46,7 +45,3 @@ ReactDOM.render( document.getElementById("root") ); -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: https://bit.ly/CRA-PWA -serviceWorker.unregister(); diff --git a/app/src/serviceWorker.ts b/app/src/serviceWorker.ts deleted file mode 100644 index 065fd90360..0000000000 --- a/app/src/serviceWorker.ts +++ /dev/null @@ -1,145 +0,0 @@ -// This optional code is used to register a service worker. -// register() is not called by default. - -// This lets the app load faster on subsequent visits in production, and gives -// it offline capabilities. However, it also means that developers (and users) -// will only see deployed updates on subsequent visits to a page, after all the -// existing tabs open on the page have been closed, since previously cached -// resources are updated in the background. - -// To learn more about the benefits of this model and instructions on how to -// opt-in, read https://bit.ly/CRA-PWA - -const isLocalhost = Boolean( - window.location.hostname === "localhost" || - // [::1] is the IPv6 localhost address. - window.location.hostname === "[::1]" || - // 127.0.0.0/8 are considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) -); - -// @ts-expect-error TS(7006): Parameter 'config' implicitly has an 'any' type. -export function register(config) { - if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { - // The URL constructor is available in all browsers that support SW. -// @ts-expect-error TS(2580): Cannot find name 'process'. Do you need to install... Remove this comment to see the full error message - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; - } - - window.addEventListener("load", () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); - - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - "This web app is being served cache-first by a service " + - "worker. To learn more, visit https://bit.ly/CRA-PWA" - ); - }); - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config); - } - }); - } -} - -// @ts-expect-error TS(7006): Parameter 'swUrl' implicitly has an 'any' type. -function registerValidSW(swUrl, config) { - navigator.serviceWorker - .register(swUrl) - .then((registration) => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === "installed") { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - "New content is available and will be used when all " + - "tabs for this page are closed. See https://bit.ly/CRA-PWA." - ); - - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log("Content is cached for offline use."); - - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } - } - } - }; - }; - }) - .catch((error) => { - console.error("Error during service worker registration:", error); - }); -} - -// @ts-expect-error TS(7006): Parameter 'swUrl' implicitly has an 'any' type. -function checkValidServiceWorker(swUrl, config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl, { - headers: { "Service-Worker": "script" }, - }) - .then((response) => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get("content-type"); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf("javascript") === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then((registration) => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } - }) - .catch(() => { - console.log( - "No internet connection found. App is running in offline mode." - ); - }); -} - -export function unregister() { - if ("serviceWorker" in navigator) { - navigator.serviceWorker.ready - .then((registration) => { - registration.unregister(); - }) - .catch((error) => { - console.error(error.message); - }); - } -} From 8f794c346c77e014725f18e5c3c86e3f96c83fbf Mon Sep 17 00:00:00 2001 From: Julian Kniephoff Date: Mon, 13 May 2024 16:45:15 +0200 Subject: [PATCH 14/30] Harmonize date rendering between read-only and read/write fields Metadata fields, that is. This expands on f62e5dac7b5bd080b067b0b7cdc9e4bb74950d22 (itself a late addition to #277) and fixes a time drift between the read-only "Created" field and the non-edit-mode variant of the Start field (the values of which are synchronized) caused by the two methods of formatting interpreting the source date in different timezones. --- .../partials/ModalTabsAndPages/DetailsMetadataTab.tsx | 4 ++-- app/src/components/shared/RenderDate.tsx | 8 ++++++++ app/src/components/shared/wizard/RenderField.tsx | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 app/src/components/shared/RenderDate.tsx diff --git a/app/src/components/events/partials/ModalTabsAndPages/DetailsMetadataTab.tsx b/app/src/components/events/partials/ModalTabsAndPages/DetailsMetadataTab.tsx index 547042aa0c..17ac05e8da 100644 --- a/app/src/components/events/partials/ModalTabsAndPages/DetailsMetadataTab.tsx +++ b/app/src/components/events/partials/ModalTabsAndPages/DetailsMetadataTab.tsx @@ -4,13 +4,13 @@ import { Field, Formik } from "formik"; import cn from "classnames"; import _ from "lodash"; import Notifications from "../../../shared/Notifications"; +import RenderDate from "../../../shared/RenderDate"; import RenderMultiField from "../../../shared/wizard/RenderMultiField"; import RenderField from "../../../shared/wizard/RenderField"; import { getUserInformation } from "../../../../selectors/userInfoSelectors"; import { getCurrentLanguageInformation, hasAccess, isJson } from "../../../../utils/utils"; import { getMetadataCollectionFieldName } from "../../../../utils/resourceUtils"; import { useAppSelector } from "../../../../store"; -import { parseISO } from "date-fns"; /** * This component renders metadata details of a certain event or series @@ -128,7 +128,7 @@ const DetailsMetadataTab: React.FC<{ ) : (
{ field.type === "time" || field.type === "date" - ? parseISO(field.value).toLocaleString(currentLanguage?.dateLocale.code, { timeZone: 'UTC' }) + ? : field.value }