Skip to content

Commit b94add6

Browse files
committed
Display LifeCyclePolicies in UI (read-only)
Adds a new tab to recordings called "LifeCycle Policies". Much like other tabs this tab displays information on its subject in a table format. Unlike i.e. the events tab, the LifeCycle Policies cannot be changed in any way, just be viewed. Editing is supposed to be added at a later date. Depends on PR opencast/opencast#6139 being merged to make any sense. Similarly, if you would like to test this, your admin interface should point to an Opencast with the PR installed.
1 parent a3129b9 commit b94add6

21 files changed

+975
-4
lines changed

src/App.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import Acls from "./components/users/Acls";
1515
import About from "./components/About";
1616
import { useAppDispatch } from "./store";
1717
import { fetchOcVersion, fetchUserInfo } from "./slices/userInfoSlice";
18+
import LifeCyclePolicies from "./components/events/LifeCyclePolicies";
1819

1920
function App() {
2021
const dispatch = useAppDispatch();
@@ -41,6 +42,8 @@ function App() {
4142

4243
<Route path={"/events/series"} element={<Series />} />
4344

45+
<Route path={"/events/lifeCyclePolicies"} element={<LifeCyclePolicies />} />
46+
4447
<Route path={"/recordings/recordings"} element={<Recordings />} />
4548

4649
<Route path={"/systems/jobs"} element={<Jobs />} />

src/components/events/Events.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import EditMetadataEventsModal from "./partials/modals/EditMetadataEventsModal";
1515
import { eventsTemplateMap } from "../../configs/tableConfigs/eventsTableMap";
1616
import {
1717
loadEventsIntoTable,
18+
loadLifeCyclePoliciesIntoTable,
1819
loadSeriesIntoTable,
1920
} from "../../thunks/tableThunks";
2021
import { fetchFilters, fetchStats, editTextFilter } from "../../slices/tableFilterSlice";
@@ -43,6 +44,7 @@ import {
4344
import { fetchSeries } from "../../slices/seriesSlice";
4445
import EventDetailsModal from "./partials/modals/EventDetailsModal";
4546
import { showModal } from "../../selectors/eventDetailsSelectors";
47+
import { fetchLifeCyclePolicies } from "../../slices/lifeCycleSlice";
4648

4749
// References for detecting a click outside of the container of the dropdown menu
4850
const containerAction = React.createRef<HTMLDivElement>();
@@ -99,6 +101,14 @@ const Events = () => {
99101
dispatch(loadSeriesIntoTable());
100102
};
101103

104+
const loadLifeCyclePolicies = async () => {
105+
// Fetching policies from server
106+
await dispatch(fetchLifeCyclePolicies());
107+
108+
// Load policies into table
109+
dispatch(loadLifeCyclePoliciesIntoTable());
110+
};
111+
102112
useEffect(() => {
103113
if ("events" !== currentFilterType) {
104114
dispatch(fetchFilters("events"))
@@ -230,6 +240,15 @@ const Events = () => {
230240
{t("EVENTS.EVENTS.NAVIGATION.SERIES")}
231241
</Link>
232242
)}
243+
{hasAccess("ROLE_UI_LIFECYCLEPOLICIES_VIEW", user) && (
244+
<Link
245+
to="/events/lifeCyclePolicies"
246+
className={cn({ active: false })}
247+
onClick={() => loadLifeCyclePolicies()}
248+
>
249+
{t("LIFECYCLE.NAVIGATION.POLICIES")}
250+
</Link>
251+
)}
233252
</nav>
234253

235254
{/* Include status bar component*/}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import React, { useEffect, useState } from "react";
2+
import MainNav from "../shared/MainNav";
3+
import { useTranslation } from "react-i18next";
4+
import { Link } from "react-router-dom";
5+
import cn from "classnames";
6+
import TableFilters from "../shared/TableFilters";
7+
import Table from "../shared/Table";
8+
import Notifications from "../shared/Notifications";
9+
import { loadEventsIntoTable, loadLifeCyclePoliciesIntoTable, loadSeriesIntoTable } from "../../thunks/tableThunks";
10+
import { fetchFilters, editTextFilter, fetchStats } from "../../slices/tableFilterSlice";
11+
import Header from "../Header";
12+
import NavBar from "../NavBar";
13+
import MainView from "../MainView";
14+
import Footer from "../Footer";
15+
import { getUserInformation } from "../../selectors/userInfoSelectors";
16+
import { hasAccess } from "../../utils/utils";
17+
import { getCurrentFilterResource } from "../../selectors/tableFilterSelectors";
18+
import { useAppDispatch, useAppSelector } from "../../store";
19+
import { AsyncThunk } from "@reduxjs/toolkit";
20+
import { AsyncThunkConfig } from "@reduxjs/toolkit/dist/createAsyncThunk";
21+
import { getTotalLifeCyclePolicies } from "../../selectors/lifeCycleSelectors";
22+
import { fetchLifeCyclePolicies } from "../../slices/lifeCycleSlice";
23+
import { lifeCyclePoliciesTemplateMap } from "../../configs/tableConfigs/lifeCyclePoliciesTableMap";
24+
import { fetchEvents } from "../../slices/eventSlice";
25+
import { setOffset } from "../../slices/tableSlice";
26+
import { fetchSeries } from "../../slices/seriesSlice";
27+
28+
/**
29+
* This component renders the table view of policies
30+
*/
31+
const LifeCyclePolicies = () => {
32+
const { t } = useTranslation();
33+
const dispatch = useAppDispatch();
34+
const [displayNavigation, setNavigation] = useState(false);
35+
36+
const user = useAppSelector(state => getUserInformation(state));
37+
const policiesTotal = useAppSelector(state => getTotalLifeCyclePolicies(state));
38+
const currentFilterType = useAppSelector(state => getCurrentFilterResource(state));
39+
40+
const loadEvents = async () => {
41+
// Fetching stats from server
42+
dispatch(fetchStats());
43+
44+
// Fetching events from server
45+
await dispatch(fetchEvents());
46+
47+
// Load events into table
48+
dispatch(loadEventsIntoTable());
49+
};
50+
51+
const loadSeries = () => {
52+
// Reset the current page to first page
53+
dispatch(setOffset(0));
54+
55+
//fetching series from server
56+
dispatch(fetchSeries());
57+
58+
//load series into table
59+
dispatch(loadSeriesIntoTable());
60+
};
61+
62+
const loadLifeCyclePolicies = async () => {
63+
// Fetching policies from server
64+
await dispatch(fetchLifeCyclePolicies());
65+
66+
// Load policies into table
67+
dispatch(loadLifeCyclePoliciesIntoTable());
68+
};
69+
70+
useEffect(() => {
71+
if ("lifeCyclePolicies" !== currentFilterType) {
72+
dispatch(fetchFilters("lifeCyclePolicies"));
73+
}
74+
75+
// Reset text filter
76+
dispatch(editTextFilter(""));
77+
78+
// Load policies on mount
79+
loadLifeCyclePolicies().then((r) => console.info(r));
80+
// eslint-disable-next-line react-hooks/exhaustive-deps
81+
}, []);
82+
83+
const toggleNavigation = () => {
84+
setNavigation(!displayNavigation);
85+
};
86+
87+
return (
88+
<>
89+
<Header />
90+
<NavBar>
91+
{/* Include Burger-button menu*/}
92+
<MainNav isOpen={displayNavigation} toggleMenu={toggleNavigation} />
93+
94+
<nav>
95+
{hasAccess("ROLE_UI_EVENTS_VIEW", user) && (
96+
<Link
97+
to="/events/events"
98+
className={cn({ active: false })}
99+
onClick={() => loadEvents()}
100+
>
101+
{t("EVENTS.EVENTS.NAVIGATION.EVENTS")}
102+
</Link>
103+
)}
104+
{hasAccess("ROLE_UI_SERIES_VIEW", user) && (
105+
<Link
106+
to="/events/series"
107+
className={cn({ active: false })}
108+
onClick={() => loadSeries()}
109+
>
110+
{t("EVENTS.EVENTS.NAVIGATION.SERIES")}
111+
</Link>
112+
)}
113+
{hasAccess("ROLE_UI_LIFECYCLEPOLICIES_VIEW", user) && (
114+
<Link
115+
to="/events/lifeCyclePolicies"
116+
className={cn({ active: true })}
117+
onClick={() => loadLifeCyclePolicies()}
118+
>
119+
{t("LIFECYCLE.NAVIGATION.POLICIES")}
120+
</Link>
121+
)}
122+
</nav>
123+
</NavBar>
124+
125+
<MainView open={displayNavigation}>
126+
{/* Include notifications component */}
127+
<Notifications />
128+
129+
<div className="controls-container">
130+
{/* Include filters component */}
131+
{/* LifeCycle policies are not indexed, can't search or filter them */}
132+
{/* But if we don't include this component, the policies won't load on page load, because the first
133+
fetch request we send to the backend contains invalid params >.> */}
134+
<TableFilters
135+
loadResource={fetchLifeCyclePolicies as AsyncThunk<any, void, AsyncThunkConfig>}
136+
loadResourceIntoTable={loadLifeCyclePoliciesIntoTable}
137+
resource={"lifeCyclePolicies"}
138+
/>
139+
140+
<h1>{t("LIFECYCLE.POLICIES.TABLE.CAPTION")}</h1>
141+
<h4>{t("TABLE_SUMMARY", { numberOfRows: policiesTotal })}</h4>
142+
</div>
143+
{/* Include table component */}
144+
<Table templateMap={lifeCyclePoliciesTemplateMap} />
145+
</MainView>
146+
<Footer />
147+
</>
148+
);
149+
};
150+
151+
export default LifeCyclePolicies;

src/components/events/Series.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import DeleteSeriesModal from "./partials/modals/DeleteSeriesModal";
1111
import { seriesTemplateMap } from "../../configs/tableConfigs/seriesTableMap";
1212
import {
1313
loadEventsIntoTable,
14+
loadLifeCyclePoliciesIntoTable,
1415
loadSeriesIntoTable,
1516
} from "../../thunks/tableThunks";
1617
import { fetchFilters, fetchStats, editTextFilter } from "../../slices/tableFilterSlice";
@@ -34,6 +35,7 @@ import {
3435
showActionsSeries,
3536
} from "../../slices/seriesSlice";
3637
import { fetchSeriesDetailsTobiraNew } from "../../slices/seriesSlice";
38+
import { fetchLifeCyclePolicies } from "../../slices/lifeCycleSlice";
3739

3840
// References for detecting a click outside of the container of the dropdown menu
3941
const containerAction = React.createRef<HTMLDivElement>();
@@ -79,6 +81,14 @@ const Series = () => {
7981
dispatch(loadSeriesIntoTable());
8082
};
8183

84+
const loadLifeCyclePolicies = async () => {
85+
// Fetching policies from server
86+
await dispatch(fetchLifeCyclePolicies());
87+
88+
// Load policies into table
89+
dispatch(loadLifeCyclePoliciesIntoTable());
90+
};
91+
8292
useEffect(() => {
8393
if ("series" !== currentFilterType) {
8494
dispatch(fetchFilters("series"))
@@ -186,8 +196,17 @@ const Series = () => {
186196
{t("EVENTS.EVENTS.NAVIGATION.SERIES")}
187197
</Link>
188198
)}
199+
{hasAccess("ROLE_UI_LIFECYCLEPOLICIES_VIEW", user) && (
200+
<Link
201+
to="/events/lifeCyclePolicies"
202+
className={cn({ active: false })}
203+
onClick={() => loadLifeCyclePolicies()}
204+
>
205+
{t("LIFECYCLE.NAVIGATION.POLICIES")}
206+
</Link>
207+
)}
189208
</nav>
190-
209+
191210
<div className="btn-group">
192211
{hasAccess("ROLE_UI_SERIES_CREATE", user) && (
193212
<button className="add" onClick={() => showNewSeriesModal()}>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React, { useState } from "react";
2+
import { useTranslation } from "react-i18next";
3+
import { useAppDispatch, useAppSelector } from "../../../store";
4+
import { Tooltip } from "../../shared/Tooltip";
5+
import { LifeCyclePolicy } from "../../../slices/lifeCycleSlice";
6+
import DetailsModal from "../../shared/modals/DetailsModal";
7+
import LifeCyclePolicyDetails from "./modals/LifeCyclePolicyDetails";
8+
import { hasAccess } from "../../../utils/utils";
9+
import { getUserInformation } from "../../../selectors/userInfoSelectors";
10+
import { fetchLifeCyclePolicyDetails } from "../../../slices/lifeCycleDetailsSlice";
11+
12+
/**
13+
* This component renders the title cells of series in the table view
14+
*/
15+
const LifeCyclePolicyActionCell = ({
16+
row,
17+
}: {
18+
row: LifeCyclePolicy
19+
}) => {
20+
const { t } = useTranslation();
21+
const dispatch = useAppDispatch();
22+
23+
const [displayLifeCyclePolicyDetails, setLifeCyclePolicyDetails] = useState(false);
24+
25+
const user = useAppSelector(state => getUserInformation(state));
26+
27+
const showLifeCyclePolicyDetails = async () => {
28+
await dispatch(fetchLifeCyclePolicyDetails(row.id));
29+
30+
setLifeCyclePolicyDetails(true);
31+
};
32+
33+
const hideLifeCyclePolicyDetails = () => {
34+
setLifeCyclePolicyDetails(false);
35+
};
36+
37+
return (
38+
<>
39+
{/* view details location/recording */}
40+
{hasAccess("ROLE_UI_LIFECYCLEPOLICY_DETAILS_VIEW", user) && (
41+
<Tooltip title={t("LIFECYCLE.POLICIES.TABLE.TOOLTIP.DETAILS")}>
42+
<button
43+
className="button-like-anchor more"
44+
onClick={() => showLifeCyclePolicyDetails()}
45+
/>
46+
</Tooltip>
47+
)}
48+
49+
{displayLifeCyclePolicyDetails && (
50+
<DetailsModal
51+
handleClose={hideLifeCyclePolicyDetails}
52+
title={row.title}
53+
prefix={"LIFECYCLE.POLICIES.DETAILS.HEADER"}
54+
>
55+
<LifeCyclePolicyDetails />
56+
</DetailsModal>
57+
)}
58+
</>
59+
);
60+
};
61+
62+
export default LifeCyclePolicyActionCell;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from "react";
2+
import { LifeCyclePolicy } from "../../../slices/lifeCycleSlice";
3+
4+
/**
5+
* This component renders the maintenance cells of servers in the table view
6+
*/
7+
const LifeCyclePolicyIsActiveCell = ({
8+
row,
9+
}: {
10+
row: LifeCyclePolicy
11+
}) => {
12+
13+
return (
14+
<input
15+
type="checkbox"
16+
checked={row.isActive}
17+
disabled={true}
18+
/>
19+
);
20+
};
21+
22+
export default LifeCyclePolicyIsActiveCell;

0 commit comments

Comments
 (0)