diff --git a/package.json b/package.json
index e04c54b36..4aedaa4ed 100644
--- a/package.json
+++ b/package.json
@@ -92,7 +92,7 @@
"moment": "^2.29.1",
"moment-duration-format": "^2.3.2",
"moment-timezone": "^0.5.33",
- "openstack-uicore-foundation": "5.0.35",
+ "openstack-uicore-foundation": "5.0.36-beta.2",
"p-limit": "^6.1.0",
"path-browserify": "^1.0.1",
"postcss-loader": "^6.2.1",
@@ -114,7 +114,7 @@
"react-dropzone": "^4.2.13",
"react-final-form": "^6.5.9",
"react-google-maps": "^9.4.5",
- "react-redux": "^5.0.7",
+ "react-redux": "^7.1.0",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"react-rte": "^0.16.3",
@@ -124,7 +124,7 @@
"react-switch": "^6.0.0",
"react-tooltip": "^5.28.0",
"react-window": "^1.8.10",
- "redux": "^3.7.2",
+ "redux": "^4.2.1",
"redux-persist": "^5.10.0",
"redux-thunk": "^2.3.0",
"segmented-control": "0.1.12",
diff --git a/src/actions/__tests__/event-actions.test.js b/src/actions/__tests__/event-actions.test.js
index 0dce052d8..ba2e72dec 100644
--- a/src/actions/__tests__/event-actions.test.js
+++ b/src/actions/__tests__/event-actions.test.js
@@ -52,7 +52,7 @@ describe("Event Actions", () => {
capturedParams = null;
});
- test("builds speakers_count between filter using [] syntax", async () => {
+ test("passes pre-built [] range filter strings through to the request", async () => {
const store = mockStore({
currentSummitState: {
currentSummit: {
@@ -63,7 +63,7 @@ describe("Event Actions", () => {
});
store.dispatch(
- getEvents(null, 1, 10, "id", 1, { speakers_count_filter: [1, 3] }, [])
+ getEvents(null, 1, 10, "id", 1, ["speakers_count[]1&&3"], [])
);
await flushPromises();
@@ -71,7 +71,6 @@ describe("Event Actions", () => {
expect(getRequest).toHaveBeenCalledTimes(1);
expect(capturedParams).toBeTruthy();
expect(capturedParams["filter[]"]).toContain("speakers_count[]1&&3");
- expect(capturedParams["filter[]"]).not.toContain("speakers_count[]]1&&3");
});
test("requests type.use_speakers in fields for event list", async () => {
@@ -93,7 +92,7 @@ describe("Event Actions", () => {
expect(capturedParams.fields).toContain("type.use_speakers");
});
- test("builds speakers_count operator filter when value is not an array", async () => {
+ test("passes pre-built operator filter strings through to the request", async () => {
const store = mockStore({
currentSummitState: {
currentSummit: {
@@ -103,9 +102,7 @@ describe("Event Actions", () => {
}
});
- store.dispatch(
- getEvents(null, 1, 10, "id", 1, { speakers_count_filter: ">=2" }, [])
- );
+ store.dispatch(getEvents(null, 1, 10, "id", 1, ["speakers_count>=2"], []));
await flushPromises();
diff --git a/src/actions/event-actions.js b/src/actions/event-actions.js
index 795915f49..2c933993b 100644
--- a/src/actions/event-actions.js
+++ b/src/actions/event-actions.js
@@ -36,11 +36,9 @@ import { epochToMomentTimeZone } from "openstack-uicore-foundation/lib/utils/met
import URI from "urijs";
import history from "../history";
import {
- checkOrFilter,
getAccessTokenSafely,
isNumericString,
- joinCVSChunksAndNormalizeHeaders,
- parseDateRangeFilter
+ joinCVSChunksAndNormalizeHeaders
} from "../utils/methods";
import { getQAUsersBySummitEvent } from "./user-chat-roles-actions";
import { getAuditLog } from "./audit-log-actions";
@@ -51,11 +49,8 @@ import {
DEFAULT_PER_PAGE,
EXPORT_PAGE_SIZE_200,
FIVE_PER_PAGE,
- HOUR_AND_HALF,
- SECONDS_TO_MINUTES,
- TWO
+ HOUR_AND_HALF
} from "../utils/constants";
-import { getIdValue } from "../utils/summitUtils";
URI.escapeQuerySpace = false;
@@ -99,323 +94,6 @@ const fieldsBoundToQuestions = [
SOCIAL_DESCRIPTION
];
-const parseFilters = (filters, term = null) => {
- const filter = [];
-
- if (
- filters.hasOwnProperty("event_type_capacity_filter") &&
- Array.isArray(filters.event_type_capacity_filter) &&
- filters.event_type_capacity_filter.length > 0
- ) {
- if (
- filters.event_type_capacity_filter.includes("allows_attendee_vote_filter")
- ) {
- filter.push("type_allows_attendee_vote==1");
- }
- if (filters.event_type_capacity_filter.includes("allows_location_filter")) {
- filter.push("type_allows_location==1");
- }
- if (
- filters.event_type_capacity_filter.includes(
- "allows_publishing_dates_filter"
- )
- ) {
- filter.push("type_allows_publishing_dates==1");
- }
- }
-
- if (
- filters.hasOwnProperty("selection_plan_id_filter") &&
- Array.isArray(filters.selection_plan_id_filter) &&
- filters.selection_plan_id_filter.length > 0
- ) {
- filter.push(
- `selection_plan_id==${filters.selection_plan_id_filter.join("||")}`
- );
- }
-
- if (
- filters.hasOwnProperty("location_id_filter") &&
- Array.isArray(filters.location_id_filter) &&
- filters.location_id_filter.length > 0
- ) {
- filter.push(`location_id==${filters.location_id_filter.join("||")}`);
- }
-
- if (
- filters.hasOwnProperty("selection_status_filter") &&
- Array.isArray(filters.selection_status_filter) &&
- filters.selection_status_filter.length > 0
- ) {
- filter.push(
- `selection_status==${filters.selection_status_filter.join("||")}`
- );
- }
-
- if (
- filters.hasOwnProperty("review_status_filter") &&
- Array.isArray(filters.review_status_filter) &&
- filters.review_status_filter.length > 0
- ) {
- filter.push(`review_status==${filters.review_status_filter.join("||")}`);
- }
-
- if (filters?.progress_flag?.length > 0) {
- filter.push(
- filters.progress_flag
- .map((pf) => `actions==type_id==${pf}&&is_completed==1`)
- .join(",")
- );
- }
-
- if (
- filters.hasOwnProperty("track_id_filter") &&
- Array.isArray(filters.track_id_filter) &&
- filters.track_id_filter.length > 0
- ) {
- filter.push(`track_id==${filters.track_id_filter.join("||")}`);
- }
-
- if (
- filters.hasOwnProperty("event_type_id_filter") &&
- Array.isArray(filters.event_type_id_filter) &&
- filters.event_type_id_filter.length > 0
- ) {
- filter.push(`event_type_id==${filters.event_type_id_filter.join("||")}`);
- }
-
- if (
- filters.hasOwnProperty("speaker_id_filter") &&
- Array.isArray(filters.speaker_id_filter) &&
- filters.speaker_id_filter.length > 0
- ) {
- filter.push(
- `speaker_id==${filters.speaker_id_filter
- .map((speaker) => speaker.id)
- .join("||")}`
- );
- }
-
- if (
- filters.hasOwnProperty("level_filter") &&
- Array.isArray(filters.level_filter) &&
- filters.level_filter.length > 0
- ) {
- filter.push(`level==${filters.level_filter.join("||")}`);
- }
-
- if (
- filters.hasOwnProperty("tags_filter") &&
- Array.isArray(filters.tags_filter) &&
- filters.tags_filter.length > 0
- ) {
- filter.push(`tags==${filters.tags_filter.map((t) => t.tag).join("||")}`);
- }
-
- if (filters.published_filter) {
- filter.push(
- `published==${filters.published_filter === "published" ? "1" : "0"}`
- );
- }
-
- if (filters.has_rsvp_filter) {
- filter.push(
- `rsvp_type${filters.has_rsvp_filter === "yes" ? "<>" : "=="}None`
- );
- }
-
- if (filters.start_date_filter) {
- parseDateRangeFilter(filter, filters.start_date_filter, "start_date");
- }
-
- if (filters.end_date_filter) {
- parseDateRangeFilter(filter, filters.end_date_filter, "end_date");
- }
-
- if (filters.created_filter) {
- parseDateRangeFilter(filter, filters.created_filter, "created");
- }
-
- if (filters.modified_filter) {
- parseDateRangeFilter(filter, filters.modified_filter, "last_edited");
- }
-
- if (filters.duration_filter) {
- // multiply values to send the minutes in seconds
- if (Array.isArray(filters.duration_filter)) {
- // between
- filter.push(
- `duration[]${filters.duration_filter[0] * SECONDS_TO_MINUTES}&&${
- filters.duration_filter[1] * SECONDS_TO_MINUTES
- }`
- );
- } else {
- filter.push(
- `duration${filters.duration_filter.replace(/\d/g, "")}${
- filters.duration_filter.replace(/\D/g, "") * SECONDS_TO_MINUTES
- }`
- );
- }
- }
-
- if (filters.speakers_count_filter) {
- if (
- Array.isArray(filters.speakers_count_filter) &&
- filters.speakers_count_filter.length === TWO
- ) {
- // between
- filter.push(
- `speakers_count[]${filters.speakers_count_filter[0]}&&${filters.speakers_count_filter[1]}`
- );
- } else {
- filter.push(`speakers_count${filters.speakers_count_filter}`);
- }
- }
-
- if (
- filters.hasOwnProperty("submitters") &&
- Array.isArray(filters.submitters) &&
- filters.submitters.length > 0
- ) {
- // created by fullname | created_by_email
- filter.push(
- filters.submitters.map((tt) => {
- const escapedFullName = escapeFilterValue(
- `${tt.first_name} ${tt.last_name}`
- );
- const escapedEmail = escapeFilterValue(tt.email);
- const fullNameFilter = `created_by_fullname==${escapedFullName}`;
- const emailFilter = `created_by_email==${escapedEmail}`;
- return [fullNameFilter, emailFilter];
- }, "")
- );
- }
-
- if (filters.hasOwnProperty("streaming_url") && filters.streaming_url) {
- const searchString = escapeFilterValue(filters.streaming_url);
- filter.push(`streaming_url@@${searchString}`);
- }
- if (filters.hasOwnProperty("meeting_url") && filters.meeting_url) {
- const searchString = escapeFilterValue(filters.meeting_url);
- filter.push(`meeting_url@@${searchString}`);
- }
- if (filters.hasOwnProperty("etherpad_link") && filters.etherpad_link) {
- const searchString = escapeFilterValue(filters.etherpad_link);
- filter.push(`etherpad_link@@${searchString}`);
- }
-
- if (filters.hasOwnProperty("streaming_type") && filters.streaming_type) {
- filter.push(`streaming_type==${filters.streaming_type}`);
- }
-
- if (
- filters.hasOwnProperty("submission_source_filter") &&
- filters.submission_source_filter
- ) {
- filter.push(`submission_source==${filters.submission_source_filter}`);
- }
-
- if (
- filters.hasOwnProperty("speaker_company") &&
- Array.isArray(filters.speaker_company) &&
- filters.speaker_company.length > 0
- ) {
- filter.push(
- `speaker_company==${filters.speaker_company
- .map((c) => escapeFilterValue(c.name))
- .join("||")}`
- );
- }
-
- if (
- filters.hasOwnProperty("submitter_company") &&
- Array.isArray(filters.submitter_company) &&
- filters.submitter_company.length > 0
- ) {
- filter.push(
- `created_by_company==${filters.submitter_company
- .map((c) => escapeFilterValue(c.name))
- .join("||")}`
- );
- }
-
- if (
- filters.hasOwnProperty("sponsor") &&
- Array.isArray(filters.sponsor) &&
- filters.sponsor.length > 0
- ) {
- filter.push(
- `sponsor==${filters.sponsor.map((sponsor) => sponsor.name).join("||")}`
- );
- }
-
- if (
- filters.hasOwnProperty("all_companies") &&
- Array.isArray(filters.all_companies) &&
- filters.all_companies.length > 0
- ) {
- const companies = filters.all_companies
- .map((c) => escapeFilterValue(c.name))
- .join("||");
- filter.push(
- `speaker_company==${companies},created_by_company==${companies},sponsor==${companies}`
- );
- }
-
- if (filters.is_public) {
- filter.push("is_public==1");
- }
-
- if (filters.is_activity) {
- filter.push("is_activity==1");
- }
-
- if (
- filters.hasOwnProperty("submission_status_filter") &&
- Array.isArray(filters.submission_status_filter) &&
- filters.submission_status_filter.length > 0
- ) {
- filter.push(
- `submission_status==${filters.submission_status_filter.join("||")}`
- );
- }
-
- if (
- filters.hasOwnProperty("media_upload_with_type") &&
- filters.media_upload_with_type.operator !== null &&
- Array.isArray(filters.media_upload_with_type.value) &&
- filters.media_upload_with_type.value.length > 0
- ) {
- const concatOperator =
- filters.media_upload_with_type.operator === "has_media_upload_with_type=="
- ? "||"
- : "&&";
- filter.push(
- `${
- filters.media_upload_with_type.operator
- }${filters.media_upload_with_type.value
- .map((v) => v.id)
- .join(concatOperator)}`
- );
- }
-
- if (term) {
- const escapedTerm = escapeFilterValue(term);
- let searchString =
- `title=@${escapedTerm},` +
- `abstract=@${escapedTerm},` +
- `speaker_title=@${escapedTerm}`;
-
- if (isNumericString(term)) {
- searchString += `,id==${term}`;
- }
-
- filter.push(searchString);
- }
-
- return checkOrFilter(filters, filter);
-};
-
export const normalizeEvent = (entity, eventTypeConfig, summit) => {
const normalizedEntity = { ...entity };
if (!normalizedEntity.start_date) delete normalizedEntity.start_date;
@@ -516,12 +194,19 @@ export const normalizeBulkEvents = (entity) => {
const normalizedEvent = {
id: e.id,
title: e.title,
- selection_plan_id: getIdValue(e.selection_plan) || e.selection_plan_id,
- location_id: e.location?.id || e.location_id,
+ selection_plan_id:
+ e.selection_plan?.id ||
+ (typeof e.selection_plan === "number"
+ ? e.selection_plan
+ : e.selection_plan_id),
+ location_id:
+ e.location?.id ||
+ (typeof e.location === "number" ? e.location : e.location_id),
start_date: e.start_date,
end_date: e.end_date,
- type_id: getIdValue(e.type) || e.type_id,
- track_id: getIdValue(e.track) || e.track_id,
+ type_id: e.type?.id || (typeof e.type === "number" ? e.type : e.type_id),
+ track_id:
+ e.track?.id || (typeof e.track === "number" ? e.track : e.track_id),
duration: e.duration,
streaming_url: e.streaming_url,
streaming_type: e.streaming_type,
@@ -545,12 +230,12 @@ export const normalizeBulkEvents = (entity) => {
export const getEvents =
(
- term = null,
+ term = "",
page = DEFAULT_CURRENT_PAGE,
perPage = DEFAULT_PER_PAGE,
order = "id",
orderDir = DEFAULT_ORDER_DIR,
- filters = {},
+ filters = [],
extraColumns = []
) =>
async (dispatch, getState) => {
@@ -558,10 +243,23 @@ export const getEvents =
const accessToken = await getAccessTokenSafely();
const { currentSummit } = currentSummitState;
const summitTZ = currentSummit.time_zone.name;
+ const filter = Array.isArray(filters) ? [...filters] : [];
dispatch(startLoading());
- const filter = parseFilters(filters, term);
+ if (term) {
+ const escapedTerm = escapeFilterValue(term);
+ let searchString =
+ `title=@${escapedTerm},` +
+ `abstract=@${escapedTerm},` +
+ `speaker_title=@${escapedTerm}`;
+
+ if (isNumericString(term)) {
+ searchString += `,id==${term}`;
+ }
+
+ filter.push(searchString);
+ }
const params = {
expand:
@@ -607,66 +305,65 @@ export const getEvents =
});
};
-export const bulkUpdateEvents =
- (summitId, events) => async (dispatch, getState) => {
- const { currentSummitState } = getState();
- const accessToken = await getAccessTokenSafely();
- const { currentSummit } = currentSummitState;
- dispatch(startLoading());
+export const bulkUpdateEvents = (events) => async (dispatch, getState) => {
+ const { currentSummitState } = getState();
+ const accessToken = await getAccessTokenSafely();
+ const { currentSummit } = currentSummitState;
+ dispatch(startLoading());
- const normalizedEvents = normalizeBulkEvents(
- events.map((event) =>
- normalizeEvent(
- event,
- currentSummit.event_types.find((et) => et.id === event.type_id)
- )
+ const normalizedEvents = normalizeBulkEvents(
+ events.map((event) =>
+ normalizeEvent(
+ event,
+ currentSummit.event_types.find((et) => et.id === event.type_id)
)
- );
+ )
+ );
- return putRequest(
- null,
- createAction(UPDATED_REMOTE_EVENTS)({}),
- `${window.API_BASE_URL}/api/v1/summits/${summitId}/events/?access_token=${accessToken}`,
- {
- events: normalizedEvents
- },
- authErrorHandler
- )({})(dispatch)
- .then(() => {
- dispatch(stopLoading());
- dispatch(
- showSuccessMessage(
- T.translate("bulk_actions_page.messages.update_success"),
- () => history.push(`/app/summits/${currentSummit.id}/events/`)
- )
- );
- const {
- currentEventListState: {
- term,
- currentPage,
- perPage,
- order,
- orderDir,
- filters,
- extraColumns
- }
- } = getState();
- dispatch(
- getEvents(
- term,
- currentPage,
- perPage,
- order,
- orderDir,
- filters,
- extraColumns
- )
- );
- })
- .catch(() => {
- console.log("ERROR");
- });
- };
+ return putRequest(
+ null,
+ createAction(UPDATED_REMOTE_EVENTS)({}),
+ `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/events/?access_token=${accessToken}`,
+ {
+ events: normalizedEvents
+ },
+ authErrorHandler
+ )({})(dispatch)
+ .then(() => {
+ dispatch(stopLoading());
+ dispatch(
+ showSuccessMessage(
+ T.translate("bulk_actions_page.messages.update_success"),
+ () => history.push(`/app/summits/${currentSummit.id}/events/`)
+ )
+ );
+ const {
+ currentEventListState: {
+ term,
+ currentPage,
+ perPage,
+ order,
+ orderDir,
+ filters,
+ extraColumns
+ }
+ } = getState();
+ dispatch(
+ getEvents(
+ term,
+ currentPage,
+ perPage,
+ order,
+ orderDir,
+ filters,
+ extraColumns
+ )
+ );
+ })
+ .catch(() => {
+ dispatch(stopLoading());
+ });
+};
export const getActionTypes =
(selectionPlanId) => async (dispatch, getState) => {
@@ -1236,26 +933,34 @@ export const deleteEvent = (eventId) => async (dispatch, getState) => {
};
export const exportEvents =
- (
- term = null,
- order = "id",
- orderDir = DEFAULT_ORDER_DIR,
- extraFilters = {}
- ) =>
+ (term = null, order = "id", orderDir = DEFAULT_ORDER_DIR, filters = []) =>
async (dispatch, getState) => {
- dispatch(startLoading());
const { currentSummitState, currentEventListState } = getState();
const accessToken = await getAccessTokenSafely();
const { currentSummit } = currentSummitState;
const { totalEvents } = currentEventListState;
-
const csvMIME = "text/csv;charset=utf-8";
const filename = `${currentSummit.name}-Activities.csv`;
const totalPages = Math.ceil(totalEvents / EXPORT_PAGE_SIZE_200);
-
const endpoint = `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/events/csv`;
- const filter = parseFilters(extraFilters, term);
+ const filter = Array.isArray(filters) ? [...filters] : [];
+
+ dispatch(startLoading());
+
+ if (term) {
+ const escapedTerm = escapeFilterValue(term);
+ let searchString =
+ `title=@${escapedTerm},` +
+ `abstract=@${escapedTerm},` +
+ `speaker_title=@${escapedTerm}`;
+
+ if (isNumericString(term)) {
+ searchString += `,id==${term}`;
+ }
+
+ filter.push(searchString);
+ }
const params = Array.from({ length: totalPages }, (_, i) => {
const res = {
@@ -1489,10 +1194,17 @@ export const getEventComments =
const { currentSummitState } = getState();
const { currentSummit } = currentSummitState;
const summitTZ = currentSummit.time_zone_id;
+ const filter = [];
dispatch(startLoading());
- const filter = parseFilters(filters);
+ if (filters.is_public) {
+ filter.push("is_public==1");
+ }
+
+ if (filters.is_activity) {
+ filter.push("is_activity==1");
+ }
if (term) {
const escapedTerm = escapeFilterValue(term);
@@ -1500,9 +1212,6 @@ export const getEventComments =
filter.push(searchString);
}
- // `is_public==${escapedTerm},`;
- // `is_activity==${escapedTerm},`;
-
const params = {
page,
per_page: perPage,
diff --git a/src/components/filters/select-filter-criteria/index.js b/src/components/filters/select-filter-criteria/index.js
index 06c7c4a04..fa2c0b18d 100644
--- a/src/components/filters/select-filter-criteria/index.js
+++ b/src/components/filters/select-filter-criteria/index.js
@@ -9,15 +9,13 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- **/
+ * */
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import T from "i18n-react/dist/i18n-react";
import Swal from "sweetalert2";
import AsyncSelect from "react-select/lib/Async";
-
-import styles from "./index.module.less";
import { queryFilterCriterias } from "../../../actions/filter-criteria-actions";
const SelectFilterCriteria = ({
@@ -30,7 +28,6 @@ const SelectFilterCriteria = ({
}) => {
const [selectedFilter, setSelectedFilter] = useState(null);
const [defaultOptions, setDefaultOptions] = useState([]);
- const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (!selectedFilterCriteria) setSelectedFilter(null);
@@ -44,9 +41,9 @@ const SelectFilterCriteria = ({
const handleFilterDelete = () => {
Swal.fire({
title: T.translate("general.are_you_sure"),
- text:
- T.translate("select_filter_criteria.remove_filter_criteria_warning") +
- `"${selectedFilter.label}"`,
+ text: `${T.translate(
+ "select_filter_criteria.remove_filter_criteria_warning"
+ )}"${selectedFilter.label}"`,
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
@@ -59,7 +56,6 @@ const SelectFilterCriteria = ({
};
const getCriterias = (input, callback) => {
- setIsLoading(true);
// we need to map into value/label because of a bug in react-select 2
// https://github.com/JedWatson/react-select/issues/2998
@@ -69,7 +65,6 @@ const SelectFilterCriteria = ({
label: c.name,
...c
}));
- setIsLoading(false);
callback(newOptions);
};
@@ -88,18 +83,17 @@ const SelectFilterCriteria = ({
};
return (
-
+
diff --git a/src/components/filters/select-filter-criteria/index.module.less b/src/components/filters/select-filter-criteria/index.module.less
deleted file mode 100644
index c9a629fe6..000000000
--- a/src/components/filters/select-filter-criteria/index.module.less
+++ /dev/null
@@ -1,3 +0,0 @@
-.selectFilterWrapper {
- margin-bottom: 20px;
-}
diff --git a/src/components/tables/editable-table/EditableTable.js b/src/components/tables/editable-table/EditableTable.js
deleted file mode 100644
index a8f959d07..000000000
--- a/src/components/tables/editable-table/EditableTable.js
+++ /dev/null
@@ -1,269 +0,0 @@
-import React, { useState, useEffect } from "react";
-import { Tooltip } from "react-tooltip";
-import T from "i18n-react/dist/i18n-react";
-import EditableTableHeading from "./EditableTableHeading";
-import EditableTableRow from "./EditableTableRow";
-import {
- INDEX_NOT_FOUND,
- SORT_ASCENDING,
- SORT_DESCENDING
-} from "../../../utils/constants";
-
-import styles from "./index.module.less";
-
-const defaults = {
- sortFunc: (a, b) => {
- if (a < b) {
- return SORT_DESCENDING;
- }
- if (a > b) {
- return SORT_ASCENDING;
- }
- return 0;
- },
- sortable: false,
- sortCol: 0,
- sortDir: 1,
- colWidth: ""
-};
-
-function EditableTable(props) {
- const {
- options,
- columns,
- currentSummit,
- page,
- data,
- handleSort,
- updateData,
- handleDeleteRow,
- formattingFunction,
- afterUpdate = []
- } = props;
- let tableClass = options.hasOwnProperty("className") ? options.className : "";
- const [editButton, setEditButton] = useState(false);
- const [editEnabled, setEditEnabled] = useState(false);
- const [selected, setSelected] = useState([]);
- const [selectAll, setSelectAll] = useState(false);
- tableClass += options.actions?.edit ? " table-hover" : "";
-
- const getSortDir = (columnKey, columnIndex, sortCol, sortDir) => {
- if (columnKey && columnKey === sortCol) {
- return sortDir;
- }
- if (sortCol === columnIndex) {
- return sortDir;
- }
- return null;
- };
-
- const resetState = () => {
- setSelectAll(false);
- setEditButton(false);
- setEditEnabled(false);
- setSelected([]);
- };
-
- // reseting states on data changes/pagination
- useEffect(() => {
- resetState();
- }, [page]);
-
- useEffect(() => {
- if (selected.length > 0) {
- setEditButton(true);
- } else {
- setEditButton(false);
- setEditEnabled(false);
- }
- }, [selected]);
-
- useEffect(() => {
- if (selectAll) {
- setSelected(data);
- setSelectAll(true);
- } else {
- setSelectAll(false);
- setSelected([]);
- }
- }, [selectAll]);
-
- const updateSelected = (row, checked) => {
- const selectedRow = row;
- const rowIndex = selected.findIndex((s) => s.id === selectedRow.id);
- const exists = rowIndex !== INDEX_NOT_FOUND;
-
- if (checked) {
- if (exists) {
- // if already on selected list, replace with new data
- const updatedSelected = { ...selectedRow };
- const newSelected = selected.slice();
- newSelected[rowIndex] = updatedSelected;
- setSelected(newSelected);
- } else {
- // append to list
- setSelected((currSelected) => [...currSelected, selectedRow]);
- }
- } else {
- setSelected(selected.filter((se) => se.id !== selectedRow.id));
- }
- };
-
- const handledAfterUpdateData = () => {
- const actionsAfterUpdate = [];
- if (afterUpdate.length > 0) {
- afterUpdate.forEach(({ action, propertyName }) => {
- selected.forEach((row) => {
- if (Array.isArray(row[propertyName])) {
- row[propertyName].forEach((e) => {
- actionsAfterUpdate.push(action(e));
- });
- } else {
- actionsAfterUpdate.push(action(row[propertyName]));
- }
- });
- });
- }
- return Promise.all(actionsAfterUpdate);
- };
-
- const onUpdateEvents = (evt) => {
- evt.stopPropagation();
- evt.preventDefault();
- updateData(currentSummit.id, selected)
- .then(() => handledAfterUpdateData())
- .then(() => resetState())
- .catch((error) => {
- console.error("Error updating events:", error);
- });
- };
-
- return (
-
-
-
-
- {editEnabled ? (
- <>
-
- {T.translate("bulk_actions_page.btn_apply_changes")}
-
-
- {T.translate("general.cancel")}
-
- >
- ) : (
- setEditEnabled(!editEnabled)}
- type="button"
- >
- {T.translate("event_list.edit_selected")}
-
- )}
-
-
-
-
-
- );
-}
-
-export default EditableTable;
diff --git a/src/components/tables/editable-table/EditableTableHeading.js b/src/components/tables/editable-table/EditableTableHeading.js
deleted file mode 100644
index 17578effb..000000000
--- a/src/components/tables/editable-table/EditableTableHeading.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React from "react";
-import PropTypes from "prop-types";
-import { SORT_ASCENDING, SORT_DESCENDING } from "../../../utils/constants";
-
-function EditableTableHeading(props) {
- const {
- editEnabled,
- sortable,
- sortDir,
- onSort,
- columnIndex,
- columnKey,
- sortFunc,
- width,
- children
- } = props;
- const getSortClass = () => {
- // disable sorting if on edit mode
- if (!sortable || editEnabled) return null;
-
- switch (sortDir) {
- case SORT_ASCENDING:
- return "sorting_asc";
- case SORT_DESCENDING:
- return "sorting_desc";
- default:
- return sortable ? "sorting" : null;
- }
- };
-
- const handleSort = (e) => {
- e.preventDefault();
-
- if (!onSort || !sortable) return;
-
- onSort(
- columnIndex,
- columnKey,
- sortDir ? sortDir * SORT_DESCENDING : SORT_ASCENDING,
- sortFunc
- );
- };
-
- return (
-
- {children}
-
- );
-}
-
-EditableTableHeading.propTypes = {
- onSort: PropTypes.func,
- sortDir: PropTypes.number,
- columnIndex: PropTypes.number,
- columnKey: PropTypes.any,
- sortable: PropTypes.bool,
- sortFunc: PropTypes.func
-};
-
-export default EditableTableHeading;
diff --git a/src/components/tables/editable-table/EditableTableRow.js b/src/components/tables/editable-table/EditableTableRow.js
deleted file mode 100644
index bcd49f6f7..000000000
--- a/src/components/tables/editable-table/EditableTableRow.js
+++ /dev/null
@@ -1,190 +0,0 @@
-import React, { useEffect, useState } from "react";
-import TextArea from "openstack-uicore-foundation/lib/components/inputs/textarea-input";
-import T from "i18n-react/dist/i18n-react";
-import history from "../../../history";
-
-import styles from "./index.module.less";
-
-function EditableTableRow(props) {
- const {
- row,
- columns,
- editEnabled,
- selected,
- updateSelected,
- deleteRow,
- selectAll,
- currentSummit,
- actions,
- formattingFunction
- } = props;
- const [checked, setChecked] = useState(false);
- const [editData, setEditData] = useState(row);
-
- const formattedData = formattingFunction(row, currentSummit);
-
- useEffect(() => {
- setEditData(row);
- }, [row]);
- useEffect(() => {
- updateSelected(editData, checked);
- }, [checked, row]);
- useEffect(() => {
- setChecked(selectAll);
- }, [selectAll]);
- useEffect(() => {
- if (selected.length === 0) {
- setChecked(false);
- }
- }, [selected]);
- useEffect(() => {
- updateSelected(editData, checked);
- }, [editData]);
- useEffect(() => {
- if (!editEnabled) {
- setEditData(row);
- }
- }, [editEnabled]);
-
- const onRowChange = (ev) => {
- const { value, id } = ev.target;
- if (id.includes("___")) {
- const parts = id.split("___"); // ['array property', '
', 'element property']
- const arrayProp = parts[0];
- const elementId = parseInt(parts[1], 10);
- const prop = parts[2];
-
- const arrayToChange = editData[arrayProp].map((elem) => {
- if (elem.id === elementId) {
- return { ...elem, [prop]: value };
- }
- return elem;
- });
-
- const newEventData = { ...editData, [arrayProp]: arrayToChange };
- setEditData(newEventData);
- } else {
- const newEventData = { ...editData, [id]: value };
- setEditData(newEventData);
- }
- };
-
- const onRemoveOption = (rowId, id) => {
- const currentRow = selected.find((r) => r.id === row.id);
- const newOptions = currentRow[id].filter((s) => s.id !== rowId);
- const newEventData = { ...editData, [id]: newOptions };
- setEditData(newEventData);
- };
-
- return (
- <>
-
- setChecked(!checked)}
- checked={checked}
- />
-
- {row.id}
- {selected.find((s) => s.id === row.id) && editEnabled && checked ? (
- <>
- {columns.map((col) => {
- if (col.columnKey === "id") {
- return null;
- }
- if (col.editableField === true) {
- // Default field as text
- return (
-
-
-
- );
- }
- if (col.editableField) {
- return (
-
- {col.editableField({
- value:
- editData[col.columnKey]?.id ||
- editData[col.columnKey]?.value ||
- editData[col.columnKey],
- onChange: onRowChange,
- row: editData,
- rowData: editData[col.columnKey],
- onRemoveOption
- })}
-
- );
- }
- return (
-
- {col.render
- ? col.render(row[col.columnKey], row)
- : formattedData[col.columnKey]}
-
- );
- })}
- >
- ) : (
- columns.map(
- (col) =>
- col.columnKey !== "id" && (
-
- {col.render
- ? col.render(row[col.columnKey], row)
- : formattedData[col.columnKey]}
-
- )
- )
- )}
- {(actions.edit || actions.delete) && (
-
-
- {actions.edit && (
-
- history.push(
- `/app/summits/${currentSummit.id}/events/${row.id}`
- )
- }
- aria-label={`Edit event ${row.id}`} // Provide an appropriate aria-label
- >
-
-
- )}
- {actions.delete && (
- deleteRow(row.id)}
- aria-label={`Delete event ${row.id}`} // Provide an appropriate aria-label
- >
-
-
- )}
-
-
- )}
- >
- );
-}
-
-export default EditableTableRow;
diff --git a/src/components/tables/editable-table/__tests__/EditableTable.test.js b/src/components/tables/editable-table/__tests__/EditableTable.test.js
deleted file mode 100644
index 189b36a09..000000000
--- a/src/components/tables/editable-table/__tests__/EditableTable.test.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import React from "react";
-import userEvent from "@testing-library/user-event";
-import { act, render, screen, waitFor } from "@testing-library/react";
-import EditableTable from "../EditableTable";
-
-describe("EditableTable", () => {
- const baseProps = {
- options: {
- className: "test-table",
- actions: {}
- },
- columns: [
- { columnKey: "id", value: "id", sortable: true },
- { columnKey: "title", value: "title", sortable: true }
- ],
- currentSummit: { id: 99 },
- page: 1,
- handleSort: jest.fn(),
- handleDeleteRow: jest.fn(),
- formattingFunction: (row) => row,
- data: [
- {
- id: 1,
- title: "Event 1",
- media_uploads: [{ id: 11, event_id: 1 }]
- },
- {
- id: 2,
- title: "Event 2",
- media_uploads: [{ id: 22, event_id: 2 }]
- }
- ]
- };
-
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- test("applies bulk updates without executing afterUpdate by default", async () => {
- const user = userEvent.setup();
- const updateData = jest.fn(() => Promise.resolve());
-
- render( );
-
- const checkboxes = screen.getAllByRole("checkbox");
-
- await user.click(checkboxes[1]);
- await user.click(screen.getByText("event_list.edit_selected"));
- await act(async () => {
- await user.click(screen.getByText("bulk_actions_page.btn_apply_changes"));
- });
-
- await waitFor(() => {
- expect(updateData).toHaveBeenCalledTimes(1);
- expect(updateData).toHaveBeenCalledWith(
- 99,
- expect.arrayContaining([expect.objectContaining({ id: 1 })])
- );
- });
- });
-
- test("executes afterUpdate actions only when explicitly configured", async () => {
- const user = userEvent.setup();
- const updateData = jest.fn(() => Promise.resolve());
- const afterUpdateAction = jest.fn(() => Promise.resolve());
-
- render(
-
- );
-
- const checkboxes = screen.getAllByRole("checkbox");
-
- await user.click(checkboxes[1]);
- await user.click(screen.getByText("event_list.edit_selected"));
- await act(async () => {
- await user.click(screen.getByText("bulk_actions_page.btn_apply_changes"));
- });
-
- await waitFor(() => {
- expect(updateData).toHaveBeenCalledTimes(1);
- expect(afterUpdateAction).toHaveBeenCalledTimes(1);
- expect(afterUpdateAction).toHaveBeenCalledWith(
- expect.objectContaining({ id: 11, event_id: 1 })
- );
- });
- });
-});
diff --git a/src/components/tables/editable-table/index.module.less b/src/components/tables/editable-table/index.module.less
deleted file mode 100644
index 0af7345a4..000000000
--- a/src/components/tables/editable-table/index.module.less
+++ /dev/null
@@ -1,70 +0,0 @@
-.editableTableWrapper {
- width: 100%;
-
- tbody > tr:nth-of-type(even) {
- background-color: #ffffff;
- }
-
- .tableWrapper {
- width: 100%;
- overflow-x: auto;
- position: relative;
-
- td {
- max-width: 150px;
- text-overflow: ellipsis;
- vertical-align: middle;
-
- &.checkColumn {
- display: table-cell;
- text-align: center;
- position: sticky;
- z-index: 5;
- left: 0;
- background-color: inherit;
- min-width: auto;
- }
- &.idColumn {
- min-width: auto;
- }
-
- &:not([class]) {
- min-width: 150px;
- align-self: flex-start;
- }
- }
-
- .bulkEditRow {
- display: flex;
- flex-wrap: nowrap;
- gap: 0 30px;
- align-items: baseline;
- padding-bottom: 10px;
- }
-
- .bulkEditCol {
- min-width: 250px;
- align-self: flex-start;
- }
-
- .actionColumn {
- display: table-cell;
- padding: 0 20px;
- text-align: right;
- position: sticky;
- z-index: 5;
- right: 0;
- background-color: inherit;
-
- .editButtonWrapper {
- display: flex;
- }
-
- i {
- padding: 0 10px;
- display: inline;
- cursor: pointer;
- }
- }
- }
-}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 17ec2744c..3fc30caad 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -1164,14 +1164,13 @@
"event_type_capacity": "Activity Type Capacity",
"selection_plan": "Selection Plan",
"location": "Location",
- "level": "Level",
+ "level": "Activity Level",
"tags": "Tags",
"progress_flags": "Progress Flags",
"streaming_url": "Streaming URL",
"meeting_url": "Meeting URL",
"etherpad_link": "Etherpad URL",
"streaming_type": "Streaming Type",
- "status": "Status",
"selection_status": "Selection Status",
"submission_status": "Submission Status",
"published": "Published",
@@ -1188,9 +1187,21 @@
"media_uploads_display": "Display on Site",
"allow_feedback": "Allow feedback ?",
"to_record": "To record ?",
+ "created_by": "Submitter",
+ "review_status": "Review Status",
"delete_event_warning": "Are you sure you want to delete activity",
"import": "Import",
"import_events": "Import Activity",
+ "import_events_format_header": "Format must be the following:",
+ "import_events_format_minimal": "(Minimal data required)",
+ "import_events_format_title": "* title ( text )",
+ "import_events_format_description": "* description (text )",
+ "import_events_format_type_id": "* type_id (int) or type (string type name)",
+ "import_events_format_track_id": "* track_id (int) or track ( string track name)",
+ "import_events_format_speaker_emails": "* speaker_emails ( list of email | delimited) [optional]",
+ "import_events_format_speaker_fullnames": "* speaker_fullnames ( list of full names | delimited) [optional]",
+ "import_events_format_speaker_companies": "* speaker_companies ( list of companies | delimited) [optional]",
+ "import_events_format_speaker_titles": "* speaker_titles ( list of titles | delimited) [optional]",
"ingest": "Ingest",
"send_speaker_email": "Send Speaker Email (Email Flow Event ACTIVITES_SUBMISSIONS_IMPORT_ACTIVITY_SPEAKER)",
"mux_import": "Import MP4 From MUX",
@@ -1200,15 +1211,14 @@
"mux_token_secret_info": "MUX Token Secret",
"mux_email_to": "Email to",
"mux_email_to_info": "Recipient to send process excerpt.",
- "export": "Export",
- "created_by": "Submitter",
"mux_import_done": "MUX MP4 Import Process Requested Successfuly.",
+ "missing_token_error": "Missing Mux Token ID or Mux Token Secret",
+ "mux_import_error": "Error while importing Mux MP4: {error}",
"allows_attendee_vote_filter": "Allows attendee vote",
"allows_location_filter": "Allows location",
"allows_publishing_dates_filter": "Allows publishing dates",
"apply_filters": "Apply Filters",
"select_fields": "Show Columns",
- "review_status": "Review Status",
"created": "Created",
"modified": "Modified",
"submission_source": "Submission Source",
@@ -1221,50 +1231,12 @@
"submission_status_accepted": "Accepted",
"submission_status_received": "Received",
"submission_status_not_submitted": "Not Submitted",
- "media_upload_type": "Media Upload Type",
"media_uploads": "Media Uploads",
- "media_upload_with_type": "Media Upload Types",
"edit_selected": "Edit Selected",
- "delete_selected": "Delete Selected",
"placeholders": {
"search_events": "Search by Id, Title, Abstract, Speaker Title",
- "event_type_capacity": "Filter by Activity Type Capacity",
- "selection_plan": "Filter by Selection Plan",
- "location": "Filter by Location",
- "selection_status": "Filter by Selection Status",
- "progress_flag": "Filter by Progress Flag",
- "track": "Filter by Activity Category",
"event_type": "Filter by Activity Type",
- "speaker": "Filter by Speaker",
- "speaker_company": "Filter by Speaker Company",
- "level": "Filter by Level",
- "tags": "Filter by Tags",
- "start_date": "Filter from Date",
- "end_date": "Filter to Date",
- "start_date_from": "Filter Start Date from",
- "start_date_to": "Filter Start Date to",
- "end_date_from": "Filter End Date from",
- "end_date_to": "Filter End Date to",
- "created_from": "Filter Created Date from",
- "created_to": "Filter Created Date to",
- "modified_from": "Filter Modified Date from",
- "modified_to": "Filter Modified Date to",
- "select_fields": "Select data to display",
- "has_rsvp": "Has RSVP?",
- "submitters": "Filter by Submitter",
- "submitter_company": "Filter by Submitter Company",
- "streaming_url": "Filter by Stream URL",
- "meeting_url": "Filter by Meeting URL",
- "etherpad_link": "Filter by Etherpad URL",
- "streaming_type": "Filter by Streaming Type",
- "sponsor": "Filter by Sponsor",
- "all_companies": "Filter by All Companies",
- "submission_status": "Filter by Submission Status",
- "review_status": "Filter by Review Status",
- "media_upload_type": "Filter by Media Upload Type",
- "submission_source": "Filter by Submission Source",
- "media_upload_type_id_to_include": "Select the Media Upload Types to include",
- "media_upload_type_id_to_exclude": "Select the Media Upload Types to exclude"
+ "select_fields": "Select data to display"
}
},
"event_type_list": {
diff --git a/src/pages/events/__tests__/format-event-data.test.js b/src/pages/events/__tests__/format-event-data.test.js
new file mode 100644
index 000000000..077ecd13c
--- /dev/null
+++ b/src/pages/events/__tests__/format-event-data.test.js
@@ -0,0 +1,113 @@
+import { formatEventData } from "../summit-event-list-page/helpers";
+
+describe("summit-event-list-page.formatEventData", () => {
+ const summit = {
+ time_zone: { name: "UTC" },
+ time_zone_id: "UTC"
+ };
+
+ test("returns speakers_count based on speakers length when type.use_speakers is true", () => {
+ const event = {
+ id: 10,
+ summit_id: 1,
+ title: "Test Event",
+ is_published: false,
+ type: {
+ use_speakers: true,
+ allows_location: false,
+ allows_attendee_vote: false,
+ allows_publishing_dates: false
+ },
+ speakers: [
+ { first_name: "Jane", last_name: "Doe", company: "ACME" },
+ { first_name: "John", last_name: "Smith", company: "Globex" }
+ ],
+ tags: [],
+ sponsors: []
+ };
+
+ const result = formatEventData(event, summit);
+
+ expect(result.speakers_count).toBe(2);
+ });
+
+ test("returns 0 when type.use_speakers is true and speakers list is empty", () => {
+ const event = {
+ id: 12,
+ summit_id: 1,
+ title: "No Speakers Yet",
+ is_published: false,
+ type: {
+ use_speakers: true,
+ allows_location: false,
+ allows_attendee_vote: false,
+ allows_publishing_dates: false
+ },
+ speakers: [],
+ tags: [],
+ sponsors: []
+ };
+
+ const result = formatEventData(event, summit);
+
+ expect(result.speakers_count).toBe(0);
+ });
+
+ test("returns N/A and does not throw when event type is missing", () => {
+ const event = {
+ id: 11,
+ summit_id: 1,
+ title: "Type-less Event",
+ is_published: false,
+ speakers: [],
+ tags: [],
+ sponsors: []
+ };
+
+ let result;
+ expect(() => {
+ result = formatEventData(event, summit);
+ }).not.toThrow();
+ expect(result.speakers_count).toBe("N/A");
+ });
+
+ test("keeps real event fields raw so the row can be sent back to bulkUpdateEvents unchanged", () => {
+ const speakers = [{ id: 1, first_name: "Jane", last_name: "Doe" }];
+ const track = { id: 5, name: "Track A" };
+ const tags = [{ tag: "ai" }, { tag: "cloud" }];
+ const event = {
+ id: 13,
+ summit_id: 1,
+ title: "Raw Fields Event",
+ is_published: false,
+ type: { use_speakers: true },
+ speakers,
+ track,
+ tags,
+ sponsors: [],
+ location: { id: 7, name: "Hall A" },
+ start_date: 1700000000,
+ end_date: 1700003600,
+ duration: 3600,
+ level: "beginner",
+ selection_status: "unaccepted"
+ };
+
+ const result = formatEventData(event, summit);
+
+ expect(result.speakers).toBe(speakers);
+ expect(result.track).toBe(track);
+ expect(result.tags).toBe(tags);
+ expect(result.location).toEqual({ id: 7, name: "Hall A" });
+ expect(result.start_date).toBe(1700000000);
+ expect(result.end_date).toBe(1700003600);
+ expect(result.duration).toBe(3600);
+ expect(result.level).toBe("beginner");
+ expect(result.selection_status).toBe("unaccepted");
+
+ // display-only companions added alongside the raw fields
+ expect(result.speaker_names).toBe("Jane Doe");
+ expect(result.track_name).toBe("Track A");
+ expect(typeof result.start_date_display).toBe("string");
+ });
+});
diff --git a/src/pages/events/__tests__/summit-event-list-page.test.js b/src/pages/events/__tests__/summit-event-list-page.test.js
index 00ab2f5c5..1896a847c 100644
--- a/src/pages/events/__tests__/summit-event-list-page.test.js
+++ b/src/pages/events/__tests__/summit-event-list-page.test.js
@@ -4,26 +4,14 @@ import { renderWithRedux } from "../../../utils/test-utils";
const mockEditableTableSpy = jest.fn(() => null);
-jest.mock("openstack-uicore-foundation/lib/components", () => ({
- CompanyInput: () => null,
- DateTimePicker: () => null,
- Dropdown: () => null,
- FreeTextSearch: () => null,
- Input: () => null,
- MemberInput: () => null,
- OperatorInput: () => null,
- SpeakerInput: () => null,
- TagInput: () => null,
- UploadInput: () => null
-}));
-
jest.mock(
- "../../../components/tables/editable-table/EditableTable",
+ "openstack-uicore-foundation/lib/components/mui/bulk-edit-table",
() =>
- function EditableTableMock(props) {
+ function BulkEditTableMock(props) {
mockEditableTableSpy(props);
return null;
- }
+ },
+ { virtual: true }
);
jest.mock("i18n-react/dist/i18n-react", () => ({
@@ -51,14 +39,38 @@ jest.mock("react-bootstrap", () => {
};
});
-jest.mock("../../../components/filters/media-type-filter", () => () => null);
-jest.mock("../../../components/filters/or-and-filter", () => () => null);
+jest.mock(
+ "openstack-uicore-foundation/lib/components/mui/snackbar-notification",
+ () => ({
+ ...jest.requireActual(
+ "openstack-uicore-foundation/lib/components/mui/snackbar-notification"
+ ),
+ useSnackbarMessage: () => ({
+ errorMessage: jest.fn(),
+ successMessage: jest.fn()
+ })
+ })
+);
+
jest.mock("../../../components/filters/save-filter-criteria", () => () => null);
jest.mock(
"../../../components/filters/select-filter-criteria",
() => () => null
);
+jest.mock("openstack-uicore-foundation/lib/components/mui/grid-filter", () => ({
+ ...jest.requireActual(
+ "openstack-uicore-foundation/lib/components/mui/grid-filter"
+ ),
+ GridFilter: () => null,
+ useGridFilter: () => ({
+ parsedFilter: [],
+ resetFilters: jest.fn(),
+ filterValues: [],
+ setFilters: jest.fn()
+ })
+}));
+
describe("SummitEventListPage", () => {
let windowOpenSpy;
@@ -71,54 +83,6 @@ describe("SummitEventListPage", () => {
windowOpenSpy.mockRestore();
});
- test("does not pass afterUpdate prop to EditableTable in bulk mode", () => {
- renderWithRedux( , {
- initialState: {
- currentSummitState: {
- currentSummit: {
- id: 12,
- time_zone: { name: "UTC" },
- time_zone_id: "UTC",
- selection_plans: [],
- tracks: [],
- event_types: [],
- locations: [],
- presentation_action_types: []
- }
- },
- currentEventListState: {
- events: [
- {
- id: 101,
- type: { id: 1, name: "Presentation", use_speakers: true },
- title: "Sample event",
- selection_status: "pending",
- media_uploads: []
- }
- ],
- lastPage: 1,
- currentPage: 1,
- order: "id",
- orderDir: 1,
- totalEvents: 1,
- term: "",
- filters: {},
- extraColumns: ["media_uploads"],
- perPage: 10,
- enabledFilters: []
- }
- }
- });
-
- expect(mockEditableTableSpy).toHaveBeenCalled();
-
- const editableTableProps =
- mockEditableTableSpy.mock.calls[
- mockEditableTableSpy.mock.calls.length - 1
- ][0];
- expect(editableTableProps.afterUpdate).toBeUndefined();
- });
-
test("opens media upload material link using row event id", async () => {
renderWithRedux( , {
initialState: {
@@ -134,6 +98,9 @@ describe("SummitEventListPage", () => {
presentation_action_types: []
}
},
+ mediaUploadListState: {
+ media_uploads: []
+ },
currentEventListState: {
events: [
{
@@ -161,8 +128,7 @@ describe("SummitEventListPage", () => {
term: "",
filters: {},
extraColumns: ["media_uploads"],
- perPage: 10,
- enabledFilters: []
+ perPage: 10
}
}
});
@@ -196,7 +162,8 @@ describe("SummitEventListPage", () => {
expect(windowOpenSpy).toHaveBeenCalledWith(
"/app/summits/12/events/101/materials/999",
- "_blank"
+ "_blank",
+ "noopener,noreferrer"
);
});
@@ -215,6 +182,9 @@ describe("SummitEventListPage", () => {
presentation_action_types: []
}
},
+ mediaUploadListState: {
+ media_uploads: []
+ },
currentEventListState: {
events: [
{
@@ -239,8 +209,7 @@ describe("SummitEventListPage", () => {
term: "",
filters: {},
extraColumns: ["media_uploads"],
- perPage: 10,
- enabledFilters: []
+ perPage: 10
}
}
});
@@ -271,7 +240,8 @@ describe("SummitEventListPage", () => {
expect(windowOpenSpy).toHaveBeenCalledWith(
"/app/summits/12/events/101/materials/999",
- "_blank"
+ "_blank",
+ "noopener,noreferrer"
);
});
@@ -290,6 +260,9 @@ describe("SummitEventListPage", () => {
presentation_action_types: []
}
},
+ mediaUploadListState: {
+ media_uploads: []
+ },
currentEventListState: {
events: [
{
@@ -315,8 +288,7 @@ describe("SummitEventListPage", () => {
term: "",
filters: {},
extraColumns: ["media_uploads"],
- perPage: 10,
- enabledFilters: []
+ perPage: 10
}
}
});
diff --git a/src/pages/events/summit-event-list-page.js b/src/pages/events/summit-event-list-page.js
deleted file mode 100644
index 058754b88..000000000
--- a/src/pages/events/summit-event-list-page.js
+++ /dev/null
@@ -1,2150 +0,0 @@
-/**
- * Copyright 2026 OpenStack Foundation
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * */
-
-import React from "react";
-import { connect } from "react-redux";
-import T from "i18n-react/dist/i18n-react";
-import Swal from "sweetalert2";
-import { Modal, Pagination } from "react-bootstrap";
-import CompanyInput from "openstack-uicore-foundation/lib/components/inputs/company-input"
-import DateTimePicker from "openstack-uicore-foundation/lib/components/inputs/datetimepicker"
-import Dropdown from "openstack-uicore-foundation/lib/components/inputs/dropdown"
-import FreeTextSearch from "openstack-uicore-foundation/lib/components/free-text-search"
-import Input from "openstack-uicore-foundation/lib/components/inputs/text-input"
-import MemberInput from "openstack-uicore-foundation/lib/components/inputs/member-input"
-import OperatorInput from "openstack-uicore-foundation/lib/components/inputs/operator-input"
-import SpeakerInput from "openstack-uicore-foundation/lib/components/inputs/speaker-input"
-import TagInput from "openstack-uicore-foundation/lib/components/inputs/tag-input"
-import UploadInput from "openstack-uicore-foundation/lib/components/inputs/upload-input";
-import { SegmentedControl } from "segmented-control";
-import { epochToMomentTimeZone } from "openstack-uicore-foundation/lib/utils/methods";
-import {
- bulkUpdateEvents,
- changeEventListSearchTerm,
- deleteEvent,
- exportEvents,
- getEvents,
- importEventsCSV,
- importMP4AssetsFromMUX,
- queryAllCompanies,
- querySpeakerCompany,
- querySubmitterCompany
-} from "../../actions/event-actions";
-import { handleDDLSortByLabel, hasErrors } from "../../utils/methods";
-import "../../styles/summit-event-list-page.less";
-import OrAndFilter from "../../components/filters/or-and-filter";
-import MediaTypeFilter from "../../components/filters/media-type-filter";
-import {
- ALL_FILTER,
- DATE_FILTER_ARRAY_SIZE,
- DEFAULT_CURRENT_PAGE,
- DEFAULT_PER_PAGE,
- DEFAULT_Z_INDEX,
- HIGH_Z_INDEX
-} from "../../utils/constants";
-import {
- defaultColumns,
- editableColumns,
- formatEventData
-} from "../../utils/summitUtils";
-import SaveFilterCriteria from "../../components/filters/save-filter-criteria";
-import SelectFilterCriteria from "../../components/filters/select-filter-criteria";
-import {
- deleteFilterCriteria,
- saveFilterCriteria
-} from "../../actions/filter-criteria-actions";
-import { CONTEXT_ACTIVITIES } from "../../utils/filter-criteria-constants";
-import EditableTable from "../../components/tables/editable-table/EditableTable";
-import { buildNameIdDDL } from "../../utils/events/summit-event-list-page.utils";
-
-const fieldNames = (
- allSelectionPlans,
- allTracks,
- event_types,
- currentSummitId
-) => [
- {
- columnKey: "speakers",
- value: "speakers",
- customStyle: { minWidth: "350px" },
- editableField: (extraProps) => {
- const useSpeakers = extraProps.row.type?.use_speakers;
- return useSpeakers ? (
- ({ ...base, zIndex: 9999 }),
- control: (base, state) => ({
- ...base,
- zIndex: state.menuIsOpen ? HIGH_Z_INDEX : DEFAULT_Z_INDEX
- })
- }}
- getOptionLabel={(speaker) =>
- `${speaker.first_name} ${speaker.last_name} (${speaker.email})`
- }
- // eslint-disable-next-line react/jsx-props-no-spreading
- {...extraProps}
- />
- ) : (
- false
- );
- }
- },
- { columnKey: "created_by_fullname", value: "created_by", sortable: true },
- { columnKey: "published_date", value: "published", sortable: true },
- { columnKey: "duration", value: "duration", sortable: true },
- { columnKey: "speakers_count", value: "speakers_count", sortable: true },
- { columnKey: "speaker_company", value: "speaker_company", sortable: true },
- {
- columnKey: "track",
- value: "track",
- sortable: true,
- editableField: (extraProps) => {
- const track_ddl = buildNameIdDDL(allTracks);
-
- return (
- ({ ...base, zIndex: 9999 }),
- control: (base, state) => ({
- ...base,
- zIndex: state.menuIsOpen ? HIGH_Z_INDEX : DEFAULT_Z_INDEX
- })
- }}
- // eslint-disable-next-line react/jsx-props-no-spreading
- {...extraProps}
- />
- );
- }
- },
- { columnKey: "start_date", value: "start_date", sortable: true },
- { columnKey: "end_date", value: "end_date", sortable: true },
- { columnKey: "submitters", value: "submitters" },
- {
- columnKey: "submitter_company",
- value: "submitter_company",
- sortable: true
- },
- { columnKey: "sponsor", value: "sponsor", sortable: true },
- { columnKey: "event_type_capacity", value: "event_type_capacity" },
- {
- columnKey: "selection_plan",
- value: "selection_plan",
- sortable: true,
- editableField: (extraProps) => {
- if (!extraProps.row?.type?.id) return false;
- const event_type = Array.isArray(event_types)
- ? event_types.find(
- (t) => t?.id !== undefined && t.id === extraProps.row.type?.id
- )
- : null;
- if (!event_type) return false;
-
- const allowSelectionPlanEdit =
- ["PresentationType"].includes(event_type.class_name) ||
- ["PresentationType"].includes(event_type.name);
- if (!allowSelectionPlanEdit) return false;
-
- const trackId = extraProps.row?.track?.id;
- const track =
- trackId !== undefined && trackId !== null
- ? allTracks.find((t) => t?.id !== undefined && t.id === trackId)
- : null;
-
- const selection_plans_per_track = buildNameIdDDL(
- (Array.isArray(allSelectionPlans) ? allSelectionPlans : []).filter(
- (sp) =>
- !track ||
- (Array.isArray(sp.track_groups) &&
- Array.isArray(track.track_groups) &&
- sp.track_groups.some((gr) => track.track_groups.includes(gr)))
- )
- );
-
- return (
- ({ ...base, zIndex: 9999 }),
- control: (base, state) => ({
- ...base,
- width: 220,
- zIndex: state.menuIsOpen ? HIGH_Z_INDEX : DEFAULT_Z_INDEX
- })
- }}
- // eslint-disable-next-line react/jsx-props-no-spreading
- {...extraProps}
- />
- );
- },
- render: (e) => (e?.name ? e.name : "N/A")
- },
- { columnKey: "location", value: "location", sortable: true },
- { columnKey: "level", value: "level", sortable: true },
- { columnKey: "tags", value: "tags", sortable: true },
- {
- columnKey: "streaming_url",
- value: "streaming_url",
- sortable: true,
- title: true,
- editableField: true
- },
- {
- columnKey: "meeting_url",
- value: "meeting_url",
- sortable: true,
- title: true,
- editableField: true
- },
- {
- columnKey: "etherpad_link",
- value: "etherpad_link",
- sortable: true,
- title: true,
- editableField: true
- },
- { columnKey: "streaming_type", value: "streaming_type", sortable: true },
- {
- columnKey: "review_status",
- value: "review_status",
- sortable: true,
- title: true
- },
- {
- columnKey: "status",
- value: "submission_status",
- sortable: true,
- title: true
- },
- {
- columnKey: "progress_flags",
- value: "progress_flags",
- sortable: true,
- title: true
- },
- { columnKey: "created", value: "created", sortable: true },
- { columnKey: "modified", value: "modified", sortable: true },
- {
- columnKey: "submission_source",
- value: "submission_source",
- sortable: true
- },
- {
- columnKey: "media_uploads",
- value: "media_uploads",
- sortable: false,
- render: (e, row) => {
- if (!e?.length) return "N/A";
- return (
- <>
- {e.map((m) => (
-
- {
- ev.preventDefault();
- if (!row?.id || !currentSummitId) return false;
- window.open(
- `/app/summits/${currentSummitId}/events/${row.id}/materials/${m.id}`,
- "_blank"
- );
- return false;
- }}
- >
- {m.media_upload_type.name} - {m.created}
-
-
-
- ))}
- >
- );
- }
- },
- {
- columnKey: "media_uploads_display",
- value: "media_uploads_display",
- sortable: false,
- render: (e, row) => {
- const media_uploads = row?.media_uploads || [];
- if (!media_uploads.length) return "N/A";
- return (
- <>
- {media_uploads.map((m) => (
-
- {`"${m.media_upload_type.name}" : `}
- {`${m.display_on_site ? "Yes" : "No"}`}
-
-
- ))}
- >
- );
- }
- },
- {
- columnKey: "allow_feedback",
- value: "allow_feedback",
- sortable: false,
- render: (field) =>
- field === undefined ? "N/A" : field === true ? "Yes" : "No",
- editableField: (extraProps) => (
- ({ ...base, zIndex: 9999 }),
- control: (base, state) => ({
- ...base,
- zIndex: state.menuIsOpen ? HIGH_Z_INDEX : DEFAULT_Z_INDEX
- })
- }}
- // eslint-disable-next-line react/jsx-props-no-spreading
- {...extraProps}
- />
- )
- },
- {
- columnKey: "to_record",
- value: "to_record",
- sortable: false,
- render: (field) =>
- field === undefined ? "N/A" : field === true ? "Yes" : "No",
- editableField: (extraProps) => (
- ({ ...base, zIndex: 9999 }),
- control: (base, state) => ({
- ...base,
- zIndex: state.menuIsOpen ? HIGH_Z_INDEX : DEFAULT_Z_INDEX
- })
- }}
- // eslint-disable-next-line react/jsx-props-no-spreading
- {...extraProps}
- />
- )
- }
-];
-
-const defaultFilters = {
- event_type_capacity_filter: [],
- selection_plan_id_filter: [],
- location_id_filter: [],
- selection_status_filter: [],
- track_id_filter: [],
- event_type_id_filter: [],
- speaker_id_filter: [],
- speaker_company: [],
- level_filter: [],
- tags_filter: [],
- published_filter: null,
- has_rsvp_filter: null,
- progress_flag: [],
- created_filter: Array(DATE_FILTER_ARRAY_SIZE).fill(null),
- modified_filter: Array(DATE_FILTER_ARRAY_SIZE).fill(null),
- start_date_filter: Array(DATE_FILTER_ARRAY_SIZE).fill(null),
- end_date_filter: Array(DATE_FILTER_ARRAY_SIZE).fill(null),
- duration_filter: "",
- speakers_count_filter: "",
- submitters: [],
- submitter_company: [],
- streaming_url: "",
- meeting_url: "",
- etherpad_link: "",
- streaming_type: "",
- sponsor: [],
- all_companies: [],
- submission_status_filter: [],
- media_upload_with_type: { operator: null, value: [] },
- review_status_filter: [],
- submission_source_filter: ""
-};
-
-class SummitEventListPage extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleEdit = this.handleEdit.bind(this);
- this.handlePageChange = this.handlePageChange.bind(this);
- this.handleSort = this.handleSort.bind(this);
- this.handleSearch = this.handleSearch.bind(this);
- this.handleNewEvent = this.handleNewEvent.bind(this);
- this.handleDeleteEvent = this.handleDeleteEvent.bind(this);
- this.handleExport = this.handleExport.bind(this);
- this.handleChangeSendSpeakerEmail =
- this.handleChangeSendSpeakerEmail.bind(this);
- this.handleImportEvents = this.handleImportEvents.bind(this);
- this.handleMUXImport = this.handleMUXImport.bind(this);
- this.handleChangeMUXModal = this.handleChangeMUXModal.bind(this);
- this.handleImportAssetsFromMUX = this.handleImportAssetsFromMUX.bind(this);
- this.handleExtraFilterChange = this.handleExtraFilterChange.bind(this);
- this.handleTagOrSpeakerFilterChange =
- this.handleTagOrSpeakerFilterChange.bind(this);
- this.handleSetPublishedFilter = this.handleSetPublishedFilter.bind(this);
- this.handleSetRSVPFilter = this.handleSetRSVPFilter.bind(this);
- this.handleChangeDateFilter = this.handleChangeDateFilter.bind(this);
- this.handleApplyEventFilters = this.handleApplyEventFilters.bind(this);
- this.handleFiltersChange = this.handleFiltersChange.bind(this);
- this.handleColumnsChange = this.handleColumnsChange.bind(this);
- this.handleTermChange = this.handleTermChange.bind(this);
- this.handleOrAndFilter = this.handleOrAndFilter.bind(this);
- this.handleFilterCriteriaSave = this.handleFilterCriteriaSave.bind(this);
- this.handleFilterCriteriaChange =
- this.handleFilterCriteriaChange.bind(this);
- this.handleFilterCriteriaDelete =
- this.handleFilterCriteriaDelete.bind(this);
-
- this.state = {
- showImportModal: false,
- send_speaker_email: false,
- showImportFromMUXModal: false,
- importFile: null,
- muxModalState: {
- mux_token_id: "",
- mux_token_secret: "",
- mux_email_to: ""
- },
- enabledFilters: [],
- errors: {},
- eventFilters: {
- ...defaultFilters,
- orAndFilter: ALL_FILTER
- },
- selectedColumns: [],
- selectedFilterCriteria: null
- };
-
- this.extraFilters = {
- allows_attendee_vote_filter: false,
- allows_location_filter: false,
- allows_publishing_dates_filter: false
- };
- }
-
- componentDidMount() {
- const {
- getEvents,
- currentSummit,
- filters,
- extraColumns,
- term,
- currentPage,
- perPage,
- order,
- orderDir
- } = this.props;
- const { eventFilters } = this.state;
- const enabledFilters = Object.keys(filters).filter((e) =>
- Array.isArray(filters[e])
- ? filters[e]?.some((e) => e !== null)
- : filters[e]?.length > 0
- );
-
- this.setState((prevState) => ({
- ...prevState,
- selectedColumns: extraColumns,
- enabledFilters,
- eventFilters: { ...eventFilters, ...filters }
- }));
-
- if (currentSummit) {
- getEvents(
- term,
- currentPage,
- perPage,
- order,
- orderDir,
- filters,
- extraColumns
- );
- }
- }
-
- handleChangeSendSpeakerEmail(ev) {
- this.setState((prevState) => ({
- ...prevState,
- send_speaker_email: ev.target.checked
- }));
- }
-
- handleChangeMUXModal(ev) {
- const { errors, muxModalState } = this.state;
- const newErrors = { ...errors };
- const newMuxModalState = { ...muxModalState };
- const { value, id } = ev.target;
- newErrors[id] = "";
- newMuxModalState[id] = value;
- this.setState((prevState) => ({
- ...prevState,
- muxModalState,
- errors: newErrors
- }));
- }
-
- handleMUXImport(ev) {
- ev.preventDefault();
- this.setState((prevState) => ({
- ...prevState,
- showImportFromMUXModal: true
- }));
- }
-
- handleImportAssetsFromMUX(ev) {
- const { importMP4AssetsFromMUX } = this.props;
- const {
- muxModalState: { mux_token_id, mux_token_secret, mux_email_to }
- } = this.state;
- ev.preventDefault();
- importMP4AssetsFromMUX(mux_token_id, mux_token_secret, mux_email_to).then(
- () =>
- this.setState((prevState) => ({
- ...prevState,
- muxModalState: {
- mux_token_id: "",
- mux_token_secret: "",
- mux_email_to: ""
- }
- }))
- );
- }
-
- handleImportEvents() {
- const { importEventsCSV } = this.props;
- const { importFile, send_speaker_email } = this.state;
- if (importFile) {
- importEventsCSV(importFile, send_speaker_email);
- }
- this.setState((prevState) => ({
- ...prevState,
- showImportModal: false,
- send_speaker_email: false,
- importFile: null
- }));
- }
-
- handleEdit(event_id) {
- const { currentSummit, history } = this.props;
- history.push(`/app/summits/${currentSummit.id}/events/${event_id}`);
- }
-
- handleExport(ev) {
- const { order, orderDir, term, exportEvents } = this.props;
- const { eventFilters, selectedColumns } = this.state;
- ev.preventDefault();
- exportEvents(term, order, orderDir, eventFilters, selectedColumns);
- }
-
- handlePageChange(page) {
- const { order, orderDir, perPage, term, getEvents } = this.props;
- const { eventFilters, selectedColumns } = this.state;
- getEvents(
- term,
- page,
- perPage,
- order,
- orderDir,
- eventFilters,
- selectedColumns
- );
- }
-
- handleSort(index, key, dir) {
- const { term, getEvents } = this.props;
- const { eventFilters, selectedColumns } = this.state;
-
- switch (key) {
- case "name":
- key = "last_name";
- break;
- case "submitter_company":
- key = "created_by_company";
- break;
- case "progress_flags":
- key = "actions";
- break;
- default:
- break;
- }
-
- getEvents(
- term,
- DEFAULT_CURRENT_PAGE,
- DEFAULT_PER_PAGE,
- key,
- dir,
- eventFilters,
- selectedColumns
- );
- }
-
- handleSearch(term) {
- const { order, orderDir, getEvents } = this.props;
- const { eventFilters, selectedColumns } = this.state;
- getEvents(
- term,
- DEFAULT_CURRENT_PAGE,
- DEFAULT_PER_PAGE,
- order,
- orderDir,
- eventFilters,
- selectedColumns
- );
- }
-
- handleNewEvent() {
- const { currentSummit, history } = this.props;
- history.push(`/app/summits/${currentSummit.id}/events/new`);
- }
-
- handleDeleteEvent(eventId) {
- const { deleteEvent, events } = this.props;
- const event = events.find((e) => e.id === eventId);
-
- Swal.fire({
- title: T.translate("general.are_you_sure"),
- text: `${T.translate("event_list.delete_event_warning")} ${event.title}`,
- type: "warning",
- showCancelButton: true,
- confirmButtonColor: "#DD6B55",
- confirmButtonText: T.translate("general.yes_delete")
- }).then((result) => {
- if (result.value) {
- deleteEvent(eventId);
- }
- });
- }
-
- handleTermChange(term) {
- const { changeEventListSearchTerm } = this.props;
- changeEventListSearchTerm(term);
- }
-
- handleApplyEventFilters() {
- const { order, orderDir, term, getEvents } = this.props;
- const { eventFilters, selectedColumns } = this.state;
- getEvents(
- term,
- DEFAULT_CURRENT_PAGE,
- DEFAULT_PER_PAGE,
- order,
- orderDir,
- eventFilters,
- selectedColumns
- );
- this.setState((prevState) => ({
- ...prevState,
- selectedFilterCriteria: null
- }));
- }
-
- handleExtraFilterChange(ev) {
- const { eventFilters } = this.state;
- const { type, id } = ev.target;
- let { value } = ev.target;
- if (type === "operatorinput") {
- value = Array.isArray(value)
- ? value
- : `${ev.target.operator}${ev.target.value}`;
- if (id === "duration_filter") {
- value = Array.isArray(value)
- ? value
- : `${ev.target.operator}${ev.target.value}`;
- }
- }
- if (type === "mediatypeinput") {
- value = {
- operator: ev.target.operator,
- value: ev.target.value
- };
- }
- this.setState((prevState) => ({
- ...prevState,
- eventFilters: { ...eventFilters, [id]: value },
- selectedFilterCriteria: null
- }));
- }
-
- handleOrAndFilter(ev) {
- const { eventFilters } = this.state;
- this.setState((prevState) => ({
- ...prevState,
- eventFilters: { ...eventFilters, orAndFilter: ev }
- }));
- }
-
- handleTagOrSpeakerFilterChange(ev) {
- const { value, id } = ev.target;
- const { eventFilters } = this.state;
- this.setState((prevState) => ({
- ...prevState,
- eventFilters: { ...eventFilters, [id]: value }
- }));
- }
-
- handleSetPublishedFilter(ev) {
- const { eventFilters } = this.state;
- this.extraFilters.published_filter = ev;
- this.setState((prevState) => ({
- ...prevState,
- eventFilters: { ...eventFilters, published_filter: ev }
- }));
- }
-
- handleSetRSVPFilter(ev) {
- const { eventFilters } = this.state;
- this.extraFilters.has_rsvp_filter = ev;
- this.setState((prevState) => ({
- ...prevState,
- eventFilters: { ...eventFilters, has_rsvp_filter: ev }
- }));
- }
-
- handleFiltersChange(ev) {
- const { value } = ev.target;
- const { enabledFilters, eventFilters } = this.state;
- if (value.length < enabledFilters.length) {
- if (value.length === 0) {
- this.setState((prevState) => ({
- ...prevState,
- enabledFilters: value,
- eventFilters: defaultFilters,
- selectedFilterCriteria: null
- }));
- } else {
- const removedFilter = enabledFilters.filter(
- (e) => !value.includes(e)
- )[0];
- let defaultValue;
- if (
- removedFilter === "published_filter" ||
- removedFilter === "has_rsvp_filter"
- ) {
- defaultValue = null;
- } else if (Array.isArray(eventFilters[removedFilter])) {
- defaultValue = [];
- } else {
- defaultValue = "";
- }
- const newEventFilters = {
- ...eventFilters,
- [removedFilter]: defaultValue
- };
- this.setState((prevState) => ({
- ...prevState,
- enabledFilters: value,
- eventFilters: newEventFilters,
- selectedFilterCriteria: null
- }));
- }
- } else {
- this.setState((prevState) => ({
- ...prevState,
- enabledFilters: value,
- selectedFilterCriteria: null
- }));
- }
- }
-
- handleChangeDateFilter(ev, lastDate) {
- const { value, id } = ev.target;
- const { eventFilters } = this.state;
- const newDateFilter = eventFilters[id];
-
- this.setState((prevState) => ({
- ...prevState,
- eventFilters: {
- ...eventFilters,
- [id]: lastDate
- ? [newDateFilter[0], value.unix()]
- : [value.unix(), newDateFilter[1]]
- }
- }));
- }
-
- handleFilterCriteriaSave(filterData) {
- const { enabledFilters, eventFilters } = this.state;
- const { currentSummit, saveFilterCriteria } = this.props;
- const filterToSave = {
- id: filterData.id,
- show_id: currentSummit.id,
- name: filterData.name,
- enabled_filters: enabledFilters,
- // only save criteria for enabled filters
- criteria: Object.fromEntries(
- Object.entries(eventFilters).filter(([key]) =>
- enabledFilters.includes(key)
- )
- ),
- context: CONTEXT_ACTIVITIES,
- visibility: filterData.visibility
- };
- saveFilterCriteria(filterToSave);
- }
-
- handleColumnsChange(ev) {
- const { value } = ev.target;
- const { selectedColumns } = this.state;
- let newColumns = value;
- const all_companies = ["submitter_company", "speaker_company", "sponsor"];
-
- const mediaUploadsSelected = newColumns.includes("media_uploads");
-
- // Ensure 'media_uploads_display' is included if 'media_uploads' is selected
- if (mediaUploadsSelected && !newColumns.includes("media_uploads_display")) {
- newColumns = [...newColumns, "media_uploads_display"];
- }
-
- // Remove 'media_uploads_display' if 'media_uploads' is deselected
- if (!mediaUploadsSelected && newColumns.includes("media_uploads_display")) {
- newColumns = newColumns.filter((col) => col !== "media_uploads_display");
- }
-
- if (
- selectedColumns.includes("all_companies") &&
- !newColumns.includes("all_companies")
- ) {
- newColumns = [...newColumns.filter((e) => !all_companies.includes(e))];
- }
- const selectedCompanies = selectedColumns.filter((c) =>
- all_companies.includes(c)
- ).length;
- const newCompanies = newColumns.filter((c) =>
- all_companies.includes(c)
- ).length;
- if (newColumns.includes("all_companies")) {
- if (newColumns.filter((c) => all_companies.includes(c)).length === 0) {
- newColumns = [...selectedColumns, ...all_companies, "all_companies"];
- } else if (selectedCompanies === newCompanies) {
- newColumns = [
- ...new Set([...newColumns, ...all_companies, "all_companies"])
- ];
- } else if (newCompanies < selectedCompanies) {
- newColumns = [...newColumns.filter((c) => c !== "all_companies")];
- }
- }
- this.setState((prevState) => ({
- ...prevState,
- selectedColumns: newColumns
- }));
- }
-
- handleFilterCriteriaChange(filterCriteria) {
- const { extraColumns, term, order, orderDir, getEvents } = this.props;
- const { eventFilters } = this.state;
- let newEventFilters = {};
- if (filterCriteria) {
- Object.entries(filterCriteria.criteria).forEach(([key, values]) => {
- newEventFilters = { ...newEventFilters, [key]: values };
- });
- }
-
- this.setState(
- (prevState) => ({
- ...prevState,
- eventFilters: { ...defaultFilters, ...newEventFilters },
- enabledFilters: filterCriteria ? filterCriteria.enabled_filters : [],
- selectedFilterCriteria: filterCriteria || null
- }),
- () =>
- getEvents(
- term,
- DEFAULT_CURRENT_PAGE,
- DEFAULT_PER_PAGE,
- order,
- orderDir,
- eventFilters,
- extraColumns
- )
- );
- }
-
- handleFilterCriteriaDelete(filterCriteriaId) {
- const {
- extraColumns,
- term,
- order,
- orderDir,
- getEvents,
- deleteFilterCriteria
- } = this.props;
- const { eventFilters } = this.state;
- deleteFilterCriteria(filterCriteriaId).then(() =>
- this.setState(
- (prevState) => ({
- ...prevState,
- eventFilters: { ...defaultFilters, orAndFilter: ALL_FILTER },
- enabledFilters: [],
- selectedFilterCriteria: null
- }),
- () =>
- getEvents(
- term,
- DEFAULT_CURRENT_PAGE,
- DEFAULT_PER_PAGE,
- order,
- orderDir,
- eventFilters,
- extraColumns
- )
- )
- );
- }
-
- translateSortKey = (key) => {
- switch (key) {
- case "last_name":
- return "name";
- case "created_by_company":
- return "submitter_company";
- case "actions":
- return "progress_flags";
- default:
- break;
- }
-
- return key;
- };
-
- render() {
- const {
- currentSummit,
- events,
- lastPage,
- currentPage,
- order,
- orderDir,
- totalEvents,
- term,
- bulkUpdateEvents
- } = this.props;
- const {
- enabledFilters,
- eventFilters,
- selectedFilterCriteria,
- selectedColumns,
- showImportModal,
- send_speaker_email,
- importFile,
- showImportFromMUXModal,
- errors,
- muxModalState: { mux_token_id, mux_token_secret, mux_email_to }
- } = this.state;
-
- let columns = [
- { columnKey: "id", value: T.translate("general.id"), sortable: true },
- {
- columnKey: "type",
- value: T.translate("event_list.type"),
- sortable: true,
- // eslint-disable-next-line react/no-unstable-nested-components
- editableField: (extraProps) => (
- ({ ...base, zIndex: 9999 }),
- control: (base, state) => ({
- ...base,
- zIndex: state.menuIsOpen ? HIGH_Z_INDEX : DEFAULT_Z_INDEX
- })
- }}
- // eslint-disable-next-line react/jsx-props-no-spreading
- {...extraProps}
- />
- ),
- render: (e) => e.name
- },
- {
- columnKey: "title",
- value: T.translate("event_list.title"),
- sortable: true,
- editableField: true
- },
- {
- columnKey: "selection_status",
- value: T.translate("event_list.selection_status"),
- sortable: true
- }
- ];
-
- const table_options = {
- sortCol: this.translateSortKey(order),
- sortDir: orderDir,
- className: "summit-event-list-table",
- actions: {
- edit: { onClick: this.handleEdit },
- delete: { onClick: this.handleDeleteEvent }
- }
- };
-
- const selection_plans_ddl = buildNameIdDDL(currentSummit.selection_plans);
-
- const location_ddl = buildNameIdDDL(currentSummit.locations);
-
- const selection_status_ddl = [
- { label: "Pending", value: "pending" },
- { label: "Accepted", value: "accepted" },
- { label: "Rejected", value: "rejected" },
- { label: "Alternate", value: "alternate" }
- ];
-
- const track_ddl = buildNameIdDDL(currentSummit.tracks);
-
- const event_type_ddl = buildNameIdDDL(currentSummit.event_types);
-
- const level_ddl = [
- { label: "Beginner", value: "beginner" },
- { label: "Intermediate", value: "intermediate" },
- { label: "Advanced", value: "advanced" },
- { label: "N/A", value: "na" }
- ];
-
- const streaming_type_ddl = [
- { label: "LIVE", value: "LIVE" },
- { label: "VOD", value: "VOD" }
- ];
-
- const submission_source_ddl = [
- { label: "Admin", value: "Admin" },
- { label: "Submission", value: "Submission" }
- ];
-
- const filters_ddl = [
- { label: "Activity Type Capacity", value: "event_type_capacity_filter" },
- { label: "Selection Plan", value: "selection_plan_id_filter" },
- { label: "Activity Type", value: "event_type_id_filter" },
- { label: "Activity Category", value: "track_id_filter" },
- { label: "Level", value: "level_filter" },
- { label: "Etherpad URL", value: "etherpad_link" },
- { label: "Location", value: "location_id_filter" },
- { label: "Meeting URL", value: "meeting_url" },
- { label: "Progress Flag", value: "progress_flag" },
- { label: "Published Status", value: "published_filter" },
- { label: "Speakers", value: "speaker_id_filter" },
- { label: "Speakers Companies", value: "speaker_company" },
- { label: "Tags", value: "tags_filter" },
- { label: "Start Date", value: "start_date_filter" },
- { label: "End Date", value: "end_date_filter" },
- { label: "Duration", value: "duration_filter" },
- { label: "Speakers Count", value: "speakers_count_filter" },
- { label: "Submitter", value: "submitters" },
- { label: "Submitter Company", value: "submitter_company" },
- { label: "Selection Status", value: "selection_status_filter" },
- { label: "Stream URL", value: "streaming_url" },
- { label: "Streaming Type", value: "streaming_type" },
- { label: "Sponsors", value: "sponsor" },
- { label: "All Companies", value: "all_companies" },
- {
- label: T.translate("event_list.submission_status"),
- value: "submission_status_filter"
- },
- {
- label: T.translate("event_list.media_upload_with_type"),
- value: "media_upload_with_type"
- },
- { label: "Review Status", value: "review_status_filter" },
- { label: "Created", value: "created_filter" },
- { label: "Modified", value: "modified_filter" },
- { label: "Submission Source", value: "submission_source_filter" },
- { label: "Has RSVP?", value: "has_rsvp_filter" }
- ];
-
- const ddl_columns = [
- {
- value: "event_type_capacity",
- label: T.translate("event_list.event_type_capacity")
- },
- { value: "speakers", label: T.translate("event_list.speakers") },
- {
- value: "all_companies",
- label: T.translate("event_list.all_companies")
- },
- {
- value: "created_by_fullname",
- label: T.translate("event_list.created_by")
- },
- { value: "duration", label: T.translate("event_list.duration") },
- { value: "end_date", label: T.translate("event_list.end_date") },
- { value: "published_date", label: T.translate("event_list.published") },
- {
- value: "speaker_company",
- label: T.translate("event_list.speaker_company")
- },
- {
- value: "speakers_count",
- label: T.translate("event_list.speakers_count")
- },
- { value: "sponsor", label: T.translate("event_list.sponsor") },
- {
- value: "selection_plan",
- label: T.translate("event_list.selection_plan")
- },
- { value: "location", label: T.translate("event_list.location") },
- { value: "level", label: T.translate("event_list.level") },
- { value: "tags", label: T.translate("event_list.tags") },
- {
- value: "streaming_url",
- label: T.translate("event_list.streaming_url")
- },
- { value: "meeting_url", label: T.translate("event_list.meeting_url") },
- {
- value: "etherpad_link",
- label: T.translate("event_list.etherpad_link")
- },
- {
- value: "streaming_type",
- label: T.translate("event_list.streaming_type")
- },
- { value: "start_date", label: T.translate("event_list.start_date") },
- {
- value: "submitter_company",
- label: T.translate("event_list.submitter_company")
- },
- { value: "track", label: T.translate("event_list.track") },
- { value: "status", label: T.translate("event_list.submission_status") },
- {
- value: "submission_source",
- label: T.translate("event_list.submission_source")
- },
- {
- value: "progress_flags",
- label: T.translate("event_list.progress_flags")
- },
- {
- value: "media_uploads",
- label: T.translate("event_list.media_uploads")
- },
- {
- value: "review_status",
- label: T.translate("event_list.review_status")
- },
- { value: "created", label: T.translate("event_list.created") },
- { value: "modified", label: T.translate("event_list.modified") },
- {
- value: "allow_feedback",
- label: T.translate("event_list.allow_feedback")
- },
- {
- value: "to_record",
- label: T.translate("event_list.to_record")
- }
- ];
-
- const ddl_filterByEventTypeCapacity = [
- {
- value: "allows_attendee_vote_filter",
- label: T.translate("event_list.allows_attendee_vote_filter")
- },
- {
- value: "allows_location_filter",
- label: T.translate("event_list.allows_location_filter")
- },
- {
- value: "allows_publishing_dates_filter",
- label: T.translate("event_list.allows_publishing_dates_filter")
- }
- ];
-
- const submission_status_ddl = [
- {
- label: T.translate("event_list.submission_status_accepted"),
- value: "Accepted"
- },
- {
- label: T.translate("event_list.submission_status_received"),
- value: "Received"
- },
- {
- label: T.translate("event_list.submission_status_not_submitted"),
- value: "NonReceived"
- }
- ];
-
- const review_status_ddl = [
- {
- label: T.translate("event_list.review_status_accepted"),
- value: "Accepted"
- },
- {
- label: T.translate("event_list.review_status_in_review"),
- value: "InReview"
- },
- {
- label: T.translate("event_list.review_status_no_submitted"),
- value: "NotSubmitted"
- },
- {
- label: T.translate("event_list.review_status_published"),
- value: "Published"
- },
- {
- label: T.translate("event_list.review_status_received"),
- value: "Received"
- },
- {
- label: T.translate("event_list.review_status_rejected"),
- value: "Rejected"
- }
- ];
-
- const progress_flag_ddl = currentSummit.presentation_action_types.map(
- (pf) => ({ value: pf.id, label: pf.label })
- );
-
- const showColumns = fieldNames(
- currentSummit.selection_plans,
- currentSummit.tracks,
- currentSummit.event_types,
- currentSummit.id
- )
- .filter(
- (f) =>
- selectedColumns.includes(f.columnKey) &&
- !defaultColumns.includes(f.columnKey)
- )
- .map((f2) => {
- let c = {
- columnKey: f2.columnKey,
- value: T.translate(`event_list.${f2.value}`),
- sortable: f2.sortable,
- editable: !!editableColumns.includes(f2.editable),
- customStyle: f2.customStyle
- };
- // optional fields
- if (f2.hasOwnProperty("title")) c = { ...c, title: f2.title };
-
- if (f2.hasOwnProperty("render")) c = { ...c, render: f2.render };
-
- if (f2.hasOwnProperty("editableField"))
- c = { ...c, editableField: f2.editableField };
-
- return c;
- });
-
- columns = [...columns, ...showColumns];
-
- if (!currentSummit.id) return
;
-
- return (
-
-
- {" "}
- {T.translate("event_list.event_list")} ({totalEvents})
-
-
-
-
-
-
-
- {T.translate("event_list.add_event")}
-
-
- {T.translate("general.export")}
-
-
- {T.translate("event_list.mux_import")}
-
- this.setState({ showImportModal: true })}
- type="button"
- >
- {T.translate("event_list.import")}
-
-
-
-
-
-
- this.handleOrAndFilter(filter)}
- />
-
-
-
-
-
-
-
-
-
-
-
- {T.translate("event_list.apply_filters")}
-
-
-
-
-
- {enabledFilters.includes("event_type_capacity_filter") && (
-
-
-
- )}
- {enabledFilters.includes("selection_plan_id_filter") && (
-
-
-
- )}
- {enabledFilters.includes("location_id_filter") && (
-
-
-
- )}
- {enabledFilters.includes("selection_status_filter") && (
-
-
-
- )}
- {enabledFilters.includes("published_filter") && (
-
- this.handleSetPublishedFilter(newValue)}
- style={{
- width: "100%",
- height: 40,
- color: "#337ab7",
- fontSize: "10px"
- }}
- />
-
- )}
- {enabledFilters.includes("progress_flag") && (
-
-
-
- )}
- {enabledFilters.includes("track_id_filter") && (
-
-
-
- )}
- {enabledFilters.includes("event_type_id_filter") && (
-
-
-
- )}
- {enabledFilters.includes("speaker_id_filter") && (
-
-
-
- )}
- {enabledFilters.includes("speaker_company") && (
-
-
-
- )}
- {enabledFilters.includes("level_filter") && (
-
-
-
- )}
- {enabledFilters.includes("tags_filter") && (
-
-
-
- )}
- {enabledFilters.includes("sponsor") && (
-
-
-
- )}
- {enabledFilters.includes("all_companies") && (
-
- ({ ...base, zIndex: 9999 }),
- control: (base, state) => ({
- ...base,
- zIndex: state.menuIsOpen ? HIGH_Z_INDEX : DEFAULT_Z_INDEX
- })
- }}
- />
-
- )}
- {enabledFilters.includes("start_date_filter") && (
- <>
-
- this.handleChangeDateFilter(ev, false)}
- value={epochToMomentTimeZone(
- eventFilters.start_date_filter[0],
- currentSummit.time_zone_id
- )}
- className="event-list-date-picker"
- />
-
-
- this.handleChangeDateFilter(ev, true)}
- value={epochToMomentTimeZone(
- eventFilters.start_date_filter[1],
- currentSummit.time_zone_id
- )}
- className="event-list-date-picker"
- />
-
- >
- )}
- {enabledFilters.includes("end_date_filter") && (
- <>
-
- this.handleChangeDateFilter(ev, false)}
- value={epochToMomentTimeZone(
- eventFilters.end_date_filter[0],
- currentSummit.time_zone_id
- )}
- className="event-list-date-picker"
- />
-
-
- this.handleChangeDateFilter(ev, true)}
- value={epochToMomentTimeZone(
- eventFilters.end_date_filter[1],
- currentSummit.time_zone_id
- )}
- className="event-list-date-picker"
- />
-
- >
- )}
- {enabledFilters.includes("created_filter") && (
- <>
-
- this.handleChangeDateFilter(ev, false)}
- value={epochToMomentTimeZone(
- eventFilters.created_filter[0],
- currentSummit.time_zone_id
- )}
- className="event-list-date-picker"
- />
-
-
- this.handleChangeDateFilter(ev, true)}
- value={epochToMomentTimeZone(
- eventFilters.created_filter[1],
- currentSummit.time_zone_id
- )}
- className="event-list-date-picker"
- />
-
- >
- )}
- {enabledFilters.includes("modified_filter") && (
- <>
-
- this.handleChangeDateFilter(ev, false)}
- value={epochToMomentTimeZone(
- eventFilters.modified_filter[0],
- currentSummit.time_zone_id
- )}
- className="event-list-date-picker"
- />
-
-
- this.handleChangeDateFilter(ev, true)}
- value={epochToMomentTimeZone(
- eventFilters.modified_filter[1],
- currentSummit.time_zone_id
- )}
- className="event-list-date-picker"
- />
-
- >
- )}
- {enabledFilters.includes("submitters") && (
-
-
- member.hasOwnProperty("email")
- ? `${member.first_name} ${member.last_name} (${member.email})`
- : `${member.first_name} ${member.last_name} (${member.id})`
- }
- />
-
- )}
- {enabledFilters.includes("submitter_company") && (
-
-
-
- )}
- {enabledFilters.includes("streaming_url") && (
-
-
-
- )}
- {enabledFilters.includes("meeting_url") && (
-
-
-
- )}
- {enabledFilters.includes("etherpad_link") && (
-
-
-
- )}
- {enabledFilters.includes("streaming_type") && (
-
-
-
- )}
- {enabledFilters.includes("submission_source_filter") && (
-
-
-
- )}
- {enabledFilters.includes("has_rsvp_filter") && (
-
- this.handleSetRSVPFilter(newValue)}
- style={{
- width: "100%",
- height: 40,
- color: "#337ab7",
- fontSize: "10px"
- }}
- />
-
- )}
- {enabledFilters.includes("duration_filter") && (
-
-
-
- )}
- {enabledFilters.includes("speakers_count_filter") && (
-
-
-
- )}
- {enabledFilters.includes("submission_status_filter") && (
-
-
-
- )}
- {enabledFilters.includes("review_status_filter") && (
-
-
-
- )}
- {enabledFilters.includes("media_upload_with_type") && (
-
-
-
- )}
-
-
-
-
-
-
-
- {T.translate("event_list.select_fields")}
-
-
-
-
-
- {events.length === 0 && (
-
{T.translate("event_list.no_events")}
- )}
-
- {events.length > 0 && (
-
- )}
-
-
this.setState({ showImportModal: false })}
- >
-
- {T.translate("event_list.import_events")}
-
-
-
-
- Format must be the following:
-
- (Minimal data required)
-
- * title ( text )
- * description (text )
- * type_id (int) or type (string type name)
-
- * track_id (int) or track ( string track name)
-
- * speaker_emails ( list of email | delimited) [optional]
-
- * speaker_fullnames ( list of full names | delimited) [optional]
-
- * speaker_companies ( list of companies | delimited) [optional]
-
- * speaker_titles ( list of titles | delimited) [optional]
-
-
-
-
- this.setState({ importFile: file })}
- handleRemove={() => this.setState({ importFile: null })}
- className="dropzone col-md-6"
- multiple={false}
- accept=".csv"
- />
-
-
-
-
-
- {T.translate("event_list.send_speaker_email")}
-
-
-
-
-
-
-
- {T.translate("event_list.ingest")}
-
-
-
-
-
this.setState({ showImportFromMUXModal: false })}
- >
-
- {T.translate("event_list.mux_import")}
-
-
-
-
-
-
- {T.translate("event_list.import")}
-
-
-
-
- );
- }
-}
-
-const mapStateToProps = ({ currentSummitState, currentEventListState }) => ({
- currentSummit: currentSummitState.currentSummit,
- ...currentEventListState
-});
-
-export default connect(mapStateToProps, {
- getEvents,
- deleteEvent,
- exportEvents,
- importEventsCSV,
- importMP4AssetsFromMUX,
- changeEventListSearchTerm,
- saveFilterCriteria,
- deleteFilterCriteria,
- bulkUpdateEvents
-})(SummitEventListPage);
diff --git a/src/pages/events/summit-event-list-page/components/ImportMUXModal/index.jsx b/src/pages/events/summit-event-list-page/components/ImportMUXModal/index.jsx
new file mode 100644
index 000000000..2f5192093
--- /dev/null
+++ b/src/pages/events/summit-event-list-page/components/ImportMUXModal/index.jsx
@@ -0,0 +1,117 @@
+import React, { useState } from "react";
+import { Modal } from "react-bootstrap";
+import T from "i18n-react";
+import { Input } from "@mui/material";
+import { useSnackbarMessage } from "openstack-uicore-foundation/lib/components/mui/snackbar-notification";
+
+const ImportMUXModal = ({ show, onClose, onImport }) => {
+ const { errorMessage } = useSnackbarMessage();
+ const [tokenId, setTokenId] = useState("");
+ const [tokenSecret, setTokenSecret] = useState("");
+ const [emailTo, setEmailTo] = useState("");
+
+ const handleClose = () => {
+ setTokenId("");
+ setTokenSecret("");
+ setEmailTo("");
+ onClose();
+ };
+
+ const handleImport = (ev) => {
+ ev.preventDefault();
+ if (!tokenId || !tokenSecret) {
+ errorMessage(T.translate("event_list.missing_token_error"));
+ return;
+ }
+ onImport(tokenId, tokenSecret, emailTo)
+ .then(() => {
+ handleClose();
+ })
+ .catch((error) => {
+ errorMessage(
+ T.translate("event_list.mux_import_error", {
+ error: error.message || error
+ })
+ );
+ });
+ };
+
+ return (
+
+
+ {T.translate("event_list.mux_import")}
+
+
+
+
+
+
+ {T.translate("event_list.import")}
+
+
+
+ );
+};
+
+export default ImportMUXModal;
diff --git a/src/pages/events/summit-event-list-page/components/ImportModal/index.jsx b/src/pages/events/summit-event-list-page/components/ImportModal/index.jsx
new file mode 100644
index 000000000..99b099d1e
--- /dev/null
+++ b/src/pages/events/summit-event-list-page/components/ImportModal/index.jsx
@@ -0,0 +1,94 @@
+import React, { useState } from "react";
+import { Modal } from "react-bootstrap";
+import T from "i18n-react";
+import UploadInput from "openstack-uicore-foundation/lib/components/inputs/upload-input";
+
+const ImportModal = ({ show, onClose, onImport }) => {
+ const [importFile, setImportFile] = useState(null);
+ const [sendSpeakerEmail, setSendSpeakerEmail] = useState(false);
+
+ const handleClose = () => {
+ setImportFile(null);
+ setSendSpeakerEmail(false);
+ onClose();
+ };
+
+ const handleImportEvents = () => {
+ if (importFile) {
+ onImport(importFile, sendSpeakerEmail);
+ }
+
+ handleClose();
+ };
+
+ return (
+
+
+ {T.translate("event_list.import_events")}
+
+
+
+
+ {T.translate("event_list.import_events_format_header")}
+
+ {T.translate("event_list.import_events_format_minimal")}
+
+ {T.translate("event_list.import_events_format_title")}
+
+ {T.translate("event_list.import_events_format_description")}
+
+ {T.translate("event_list.import_events_format_type_id")}
+
+ {T.translate("event_list.import_events_format_track_id")}
+
+ {T.translate("event_list.import_events_format_speaker_emails")}
+
+ {T.translate("event_list.import_events_format_speaker_fullnames")}
+
+ {T.translate("event_list.import_events_format_speaker_companies")}
+
+ {T.translate("event_list.import_events_format_speaker_titles")}
+
+
+
+
+ setImportFile(file)}
+ handleRemove={() => setImportFile(null)}
+ className="dropzone col-md-6"
+ multiple={false}
+ accept=".csv"
+ />
+
+
+
+ setSendSpeakerEmail(ev.target.checked)}
+ className="form-check-input"
+ />
+
+ {T.translate("event_list.send_speaker_email")}
+
+
+
+
+
+
+
+ {T.translate("event_list.ingest")}
+
+
+
+ );
+};
+
+export default ImportModal;
diff --git a/src/pages/events/summit-event-list-page/helpers.js b/src/pages/events/summit-event-list-page/helpers.js
new file mode 100644
index 000000000..df3c6d28f
--- /dev/null
+++ b/src/pages/events/summit-event-list-page/helpers.js
@@ -0,0 +1,1052 @@
+/**
+ * Copyright 2026 OpenStack Foundation
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * */
+
+import React from "react";
+import T from "i18n-react/dist/i18n-react";
+import Dropdown from "openstack-uicore-foundation/lib/components/inputs/dropdown";
+import SpeakerInput from "openstack-uicore-foundation/lib/components/inputs/speaker-input";
+import { escapeFilterValue } from "openstack-uicore-foundation/lib/utils/actions";
+import { OPERATORS } from "openstack-uicore-foundation/lib/components/mui/grid-filter";
+import {
+ queryMembers,
+ queryTags
+} from "openstack-uicore-foundation/lib/utils/query-actions";
+import {
+ queryAllCompanies,
+ querySpeakerCompany,
+ querySubmitterCompany
+} from "../../../actions/event-actions";
+import { formatDate, formatDuration } from "../../../utils/methods";
+import { DEFAULT_Z_INDEX, HIGH_Z_INDEX } from "../../../utils/constants";
+import { buildNameIdDDL } from "../../../utils/events/summit-event-list-page.utils";
+
+// The table's column keys don't always match the API's order-by keys;
+// this is the single source of truth for that aliasing, used both when
+// sending a sort request and when highlighting the active sort column.
+const SORT_KEY_TO_API_KEY = {
+ name: "last_name",
+ submitter_company: "created_by_company",
+ progress_flags: "actions",
+ track_name: "track"
+};
+
+const API_KEY_TO_SORT_KEY = Object.fromEntries(
+ Object.entries(SORT_KEY_TO_API_KEY).map(([uiKey, apiKey]) => [apiKey, uiKey])
+);
+
+export const toApiSortKey = (key) => SORT_KEY_TO_API_KEY[key] ?? key;
+
+export const toUiSortKey = (key) => API_KEY_TO_SORT_KEY[key] ?? key;
+
+// Adds display-only fields for the table to render; never overwrites a real
+// event field, since the same row object is sent back as-is to bulkUpdateEvents.
+export const formatEventData = (e, summit) => {
+ const speakerCompanies = Array.isArray(e.speakers)
+ ? [...new Set(e.speakers.map((s) => s.company).filter(Boolean))]
+ : [];
+
+ const eventTypeCapacity = [
+ e.type?.allows_location && "Allows Location",
+ e.type?.allows_attendee_vote && "Allows Attendee Vote",
+ e.type?.allows_publishing_dates && "Allows Publishing Dates"
+ ].filter(Boolean);
+
+ const speakersCount = e.type?.use_speakers ? e.speakers?.length ?? 0 : "N/A";
+
+ return {
+ ...e,
+ created_by_fullname: e.created_by
+ ? `${e.created_by.first_name} ${e.created_by.last_name} (${e.created_by.email})`
+ : "TBD",
+ submitter_company: e.created_by ? e.created_by.company : "N/A",
+ speaker_names:
+ Array.isArray(e.speakers) && e.speakers.length > 0
+ ? e.speakers.map((s) => `${s.first_name} ${s.last_name}`).join(", ")
+ : "N/A",
+ speaker_company:
+ speakerCompanies.length > 0 ? speakerCompanies.join(", ") : "N/A",
+ speakers_count: speakersCount,
+ event_type_capacity: eventTypeCapacity.join(", "),
+ track_name: e?.track?.name ? e.track.name : "TBD",
+ sponsor:
+ Array.isArray(e.sponsors) && e.sponsors.length > 0
+ ? e.sponsors.map((s) => s.name).join(", ")
+ : "N/A",
+ progress_flags: e?.actions
+ ?.map((a) => `${a.type.label} (${a.is_completed ? "ON" : "OFF"})`)
+ .join(", "),
+ published_date_display: e.is_published
+ ? formatDate(
+ e.published_date,
+ summit.time_zone.name,
+ "MMMM Do YYYY, h:mm a"
+ )
+ : "No",
+ start_date_display: e.start_date
+ ? formatDate(e.start_date, summit.time_zone.name, "MMMM Do YYYY, h:mm a")
+ : "TBD",
+ end_date_display: e.end_date
+ ? formatDate(e.end_date, summit.time_zone.name, "MMMM Do YYYY, h:mm a")
+ : "TBD",
+ created_display: e.created
+ ? formatDate(e.created, summit.time_zone_id, "MMMM Do YYYY, h:mm a")
+ : "TBD",
+ modified: e.last_edited
+ ? formatDate(e.last_edited, summit.time_zone_id, "MMMM Do YYYY, h:mm a")
+ : "TBD"
+ };
+};
+
+export const getOptionalColumns = (
+ allSelectionPlans,
+ allTracks,
+ eventTypes,
+ currentSummitId
+) => [
+ {
+ columnKey: "speaker_names",
+ customStyle: { minWidth: "350px" },
+ label: T.translate("event_list.speakers"),
+ editableField: (extraProps) => {
+ const useSpeakers = extraProps.row.type?.use_speakers;
+ return (
+ useSpeakers && (
+ ({ ...base, zIndex: 9999 }),
+ control: (base, state) => ({
+ ...base,
+ zIndex: state.menuIsOpen ? HIGH_Z_INDEX : DEFAULT_Z_INDEX
+ })
+ }}
+ getOptionLabel={(speaker) =>
+ `${speaker.first_name} ${speaker.last_name} (${speaker.email})`
+ }
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...extraProps}
+ value={extraProps.row.speakers}
+ />
+ )
+ );
+ }
+ },
+ {
+ columnKey: "created_by_fullname",
+ sortable: true,
+ label: T.translate("event_list.created_by")
+ },
+ {
+ columnKey: "published_date",
+ sortable: true,
+ label: T.translate("event_list.published"),
+ render: (_, row) => row.published_date_display
+ },
+ {
+ columnKey: "duration",
+ sortable: true,
+ label: T.translate("event_list.duration"),
+ render: (duration, row) =>
+ row.type?.allows_publishing_dates && duration
+ ? formatDuration(duration)
+ : "N/A"
+ },
+ {
+ columnKey: "speakers_count",
+ sortable: true,
+ label: T.translate("event_list.speakers_count")
+ },
+ {
+ columnKey: "speaker_company",
+ sortable: true,
+ label: T.translate("event_list.speaker_company")
+ },
+ {
+ columnKey: "track_name",
+ sortable: true,
+ label: T.translate("event_list.track"),
+ editableField: (extraProps) => {
+ const trackOptions = buildNameIdDDL(allTracks);
+
+ return (
+ ({ ...base, zIndex: 9999 }),
+ control: (base, state) => ({
+ ...base,
+ zIndex: state.menuIsOpen ? HIGH_Z_INDEX : DEFAULT_Z_INDEX
+ })
+ }}
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...extraProps}
+ value={extraProps.row.track?.id}
+ />
+ );
+ }
+ },
+ {
+ columnKey: "start_date",
+ sortable: true,
+ label: T.translate("event_list.start_date"),
+ render: (_, row) => row.start_date_display
+ },
+ {
+ columnKey: "end_date",
+ sortable: true,
+ label: T.translate("event_list.end_date"),
+ render: (_, row) => row.end_date_display
+ },
+ {
+ columnKey: "submitter_company",
+ sortable: true,
+ label: T.translate("event_list.submitter_company")
+ },
+ {
+ columnKey: "sponsor",
+ sortable: true,
+ label: T.translate("event_list.sponsor")
+ },
+ {
+ columnKey: "event_type_capacity",
+ label: T.translate("event_list.event_type_capacity")
+ },
+ {
+ columnKey: "selection_plan",
+ sortable: true,
+ label: T.translate("event_list.selection_plan"),
+ editableField: (extraProps) => {
+ if (!extraProps.row?.type?.id) return false;
+ const eventType = Array.isArray(eventTypes)
+ ? eventTypes.find(
+ (t) => t?.id !== undefined && t.id === extraProps.row.type?.id
+ )
+ : null;
+ if (!eventType) return false;
+
+ const allowSelectionPlanEdit =
+ ["PresentationType"].includes(eventType.class_name) ||
+ ["PresentationType"].includes(eventType.name);
+ if (!allowSelectionPlanEdit) return false;
+
+ const trackId = extraProps.row?.track?.id;
+ const track =
+ trackId !== undefined && trackId !== null
+ ? allTracks.find((t) => t?.id !== undefined && t.id === trackId)
+ : null;
+
+ const selectionPlansPerTrack = buildNameIdDDL(
+ (Array.isArray(allSelectionPlans) ? allSelectionPlans : []).filter(
+ (sp) =>
+ !track ||
+ (Array.isArray(sp.track_groups) &&
+ Array.isArray(track.track_groups) &&
+ sp.track_groups.some((gr) => track.track_groups.includes(gr)))
+ )
+ );
+
+ return (
+ ({ ...base, zIndex: 9999 }),
+ control: (base, state) => ({
+ ...base,
+ width: 220,
+ zIndex: state.menuIsOpen ? HIGH_Z_INDEX : DEFAULT_Z_INDEX
+ })
+ }}
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...extraProps}
+ value={extraProps.value || ""}
+ />
+ );
+ },
+ render: (e) => (e?.name ? e.name : "N/A")
+ },
+ {
+ columnKey: "location",
+ sortable: true,
+ label: T.translate("event_list.location"),
+ render: (location) => (location?.name ? location.name : "N/A")
+ },
+ {
+ columnKey: "level",
+ sortable: true,
+ label: T.translate("event_list.level"),
+ render: (level) => level || "N/A"
+ },
+ {
+ columnKey: "tags",
+ sortable: true,
+ label: T.translate("event_list.tags"),
+ render: (tags) =>
+ Array.isArray(tags) && tags.length > 0
+ ? tags.reduce(
+ (accumulator, t) =>
+ accumulator + (accumulator !== "" ? ", " : "") + t.tag,
+ ""
+ )
+ : "N/A"
+ },
+ {
+ columnKey: "streaming_url",
+ sortable: true,
+ title: true,
+ editableField: true,
+ label: T.translate("event_list.streaming_url"),
+ placeholder: T.translate("bulk_actions_page.placeholders.streaming_url"),
+ render: (url) => url || "N/A"
+ },
+ {
+ columnKey: "meeting_url",
+ sortable: true,
+ title: true,
+ editableField: true,
+ label: T.translate("event_list.meeting_url"),
+ placeholder: T.translate("bulk_actions_page.placeholders.meeting_url"),
+ render: (url) => url || "N/A"
+ },
+ {
+ columnKey: "etherpad_link",
+ sortable: true,
+ title: true,
+ editableField: true,
+ label: T.translate("event_list.etherpad_link"),
+ placeholder: T.translate("bulk_actions_page.placeholders.etherpad_link"),
+ render: (link) => link || "N/A"
+ },
+ {
+ columnKey: "streaming_type",
+ sortable: true,
+ label: T.translate("event_list.streaming_type"),
+ render: (type) => type || "N/A"
+ },
+ {
+ columnKey: "review_status",
+ sortable: true,
+ title: true,
+ label: T.translate("event_list.review_status"),
+ render: (status) => status ?? "N/A"
+ },
+ {
+ columnKey: "status",
+ sortable: true,
+ title: true,
+ label: T.translate("event_list.submission_status"),
+ render: (status) => status ?? "Not Submitted"
+ },
+ {
+ columnKey: "progress_flags",
+ sortable: true,
+ title: true,
+ label: T.translate("event_list.progress_flags")
+ },
+ {
+ columnKey: "created",
+ sortable: true,
+ label: T.translate("event_list.created"),
+ render: (_, row) => row.created_display
+ },
+ {
+ columnKey: "modified",
+ sortable: true,
+ label: T.translate("event_list.modified")
+ },
+ {
+ columnKey: "submission_source",
+ sortable: true,
+ label: T.translate("event_list.submission_source"),
+ render: (source) => source || "N/A"
+ },
+ {
+ columnKey: "media_uploads",
+ sortable: false,
+ label: T.translate("event_list.media_uploads"),
+ render: (e, row) => {
+ if (!e?.length) return "N/A";
+ return (
+ <>
+ {e.map((m) => (
+
+ {
+ ev.preventDefault();
+ if (!row?.id || !currentSummitId) return false;
+ window.open(
+ `/app/summits/${currentSummitId}/events/${row.id}/materials/${m.id}`,
+ "_blank",
+ "noopener,noreferrer"
+ );
+ return false;
+ }}
+ >
+ {m.media_upload_type.name} - {m.created}
+
+
+
+ ))}
+ >
+ );
+ }
+ },
+ {
+ columnKey: "media_uploads_display",
+ sortable: false,
+ label: T.translate("event_list.media_uploads_display"),
+ render: (e, row) => {
+ const mediaUploads = row?.media_uploads || [];
+ if (!mediaUploads.length) return "N/A";
+ return (
+ <>
+ {mediaUploads.map((m) => (
+
+ {`"${m.media_upload_type.name}" : `}
+ {`${m.display_on_site ? "Yes" : "No"}`}
+
+
+ ))}
+ >
+ );
+ }
+ },
+ {
+ columnKey: "allow_feedback",
+ sortable: false,
+ label: T.translate("event_list.allow_feedback"),
+ render: (field) =>
+ field === undefined ? "N/A" : field === true ? "Yes" : "No",
+ editableField: (extraProps) => (
+ ({ ...base, zIndex: 9999 }),
+ control: (base, state) => ({
+ ...base,
+ zIndex: state.menuIsOpen ? HIGH_Z_INDEX : DEFAULT_Z_INDEX
+ })
+ }}
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...extraProps}
+ />
+ )
+ },
+ {
+ columnKey: "to_record",
+ sortable: false,
+ label: T.translate("event_list.to_record"),
+ render: (field) =>
+ field === undefined ? "N/A" : field === true ? "Yes" : "No",
+ editableField: (extraProps) => (
+ ({ ...base, zIndex: 9999 }),
+ control: (base, state) => ({
+ ...base,
+ zIndex: state.menuIsOpen ? HIGH_Z_INDEX : DEFAULT_Z_INDEX
+ })
+ }}
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...extraProps}
+ />
+ )
+ }
+];
+
+const eventTypeCapacityOptions = [
+ {
+ value: "allows_attendee_vote_filter",
+ label: T.translate("event_list.allows_attendee_vote_filter")
+ },
+ {
+ value: "allows_location_filter",
+ label: T.translate("event_list.allows_location_filter")
+ },
+ {
+ value: "allows_publishing_dates_filter",
+ label: T.translate("event_list.allows_publishing_dates_filter")
+ }
+];
+
+const levelOptions = [
+ { label: "Beginner", value: "beginner" },
+ { label: "Intermediate", value: "intermediate" },
+ { label: "Advanced", value: "advanced" },
+ { label: "N/A", value: "na" }
+];
+
+const selectionStatusOptions = [
+ { label: "Pending", value: "pending" },
+ { label: "Accepted", value: "accepted" },
+ { label: "Rejected", value: "rejected" },
+ { label: "Alternate", value: "alternate" }
+];
+
+const publishedStatusOptions = [
+ {
+ label: "Published",
+ value: "1"
+ },
+ {
+ label: "Non Published",
+ value: "0"
+ }
+];
+
+const rsvpOptions = [
+ {
+ label: "Has RSVP",
+ value: true
+ },
+ {
+ label: "No RSVP",
+ value: false
+ }
+];
+
+const streamingTypeOptions = [
+ { label: "LIVE", value: "LIVE" },
+ { label: "VOD", value: "VOD" }
+];
+
+const submissionStatusOptions = [
+ {
+ label: T.translate("event_list.submission_status_accepted"),
+ value: "Accepted"
+ },
+ {
+ label: T.translate("event_list.submission_status_received"),
+ value: "Received"
+ },
+ {
+ label: T.translate("event_list.submission_status_not_submitted"),
+ value: "NonReceived"
+ }
+];
+
+const reviewStatusOptions = [
+ {
+ label: T.translate("event_list.review_status_accepted"),
+ value: "Accepted"
+ },
+ {
+ label: T.translate("event_list.review_status_in_review"),
+ value: "InReview"
+ },
+ {
+ label: T.translate("event_list.review_status_no_submitted"),
+ value: "NotSubmitted"
+ },
+ {
+ label: T.translate("event_list.review_status_published"),
+ value: "Published"
+ },
+ {
+ label: T.translate("event_list.review_status_received"),
+ value: "Received"
+ },
+ {
+ label: T.translate("event_list.review_status_rejected"),
+ value: "Rejected"
+ }
+];
+
+const submissionSourceOptions = [
+ { label: "Admin", value: "Admin" },
+ { label: "Submission", value: "Submission" }
+];
+
+// queryTags' response shape differs depending on whether it's summit-scoped
+// (nested `tag.tag`) or global (flat `tag`); handle both defensively.
+const tagFormatOption = (item) => ({
+ value: item.id,
+ label:
+ typeof item.tag === "string" ? item.tag : item.tag?.tag || String(item.id)
+});
+
+export const getCriterias = (summit, mediaUploadTypes) => [
+ {
+ key: "event_type_capacity",
+ label: T.translate("event_list.event_type_capacity"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: eventTypeCapacityOptions,
+ multiple: true
+ }
+ },
+ customParser: (f) => {
+ const filter = [];
+
+ if (f.value.includes("allows_attendee_vote_filter")) {
+ filter.push("type_allows_attendee_vote==1");
+ }
+ if (f.value.includes("allows_location_filter")) {
+ filter.push("type_allows_location==1");
+ }
+ if (f.value.includes("allows_publishing_dates_filter")) {
+ filter.push("type_allows_publishing_dates==1");
+ }
+
+ return filter;
+ }
+ },
+ {
+ key: "selection_plan_id",
+ label: T.translate("event_list.selection_plan"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: summit.selection_plans.map((sp) => ({
+ label: sp.name,
+ value: sp.id
+ })),
+ multiple: true
+ }
+ }
+ },
+ {
+ key: "location_id",
+ label: T.translate("event_list.location"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: summit.locations.map((sp) => ({
+ label: sp.name,
+ value: sp.id
+ })),
+ multiple: true
+ }
+ }
+ },
+ {
+ key: "selection_status",
+ label: "Selection Status",
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: selectionStatusOptions,
+ multiple: true
+ }
+ }
+ },
+ {
+ key: "track_id",
+ label: T.translate("event_list.track"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: summit.tracks.map((t) => ({ label: t.name, value: t.id })),
+ multiple: true
+ }
+ }
+ },
+ {
+ key: "event_type_id",
+ label: "Activity Type",
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: summit.event_types.map((type) => ({
+ label: type.name,
+ value: type.id
+ })),
+ multiple: true
+ }
+ }
+ },
+ {
+ key: "speaker_id",
+ label: T.translate("event_list.speakers"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "speaker",
+ props: {
+ summitId: summit.id,
+ multiple: true
+ }
+ },
+ customParser: (f) => [
+ `speaker_id==${f.value.map((s) => s.value).join("||")}`
+ ]
+ },
+ {
+ key: "speaker_company",
+ label: T.translate("event_list.speaker_company"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "company",
+ props: {
+ queryFunction: querySpeakerCompany,
+ multiple: true
+ }
+ },
+ customParser: (f) => [
+ `speaker_company==${f.value
+ .map((c) => escapeFilterValue(c.raw.name))
+ .join("||")}`
+ ]
+ },
+ {
+ key: "level",
+ label: T.translate("event_list.level"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: levelOptions,
+ multiple: true
+ }
+ }
+ },
+ {
+ key: "tags",
+ label: T.translate("event_list.tags"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "asyncSelect",
+ props: {
+ queryFunction: (input, callback) =>
+ queryTags(summit.id, input, callback),
+ formatOption: tagFormatOption,
+ multiple: true
+ }
+ },
+ customParser: (f) => [
+ `tags==${f.value.map((t) => escapeFilterValue(t.label)).join("||")}`
+ ]
+ },
+ {
+ key: "published",
+ label: T.translate("event_list.published"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: publishedStatusOptions
+ }
+ }
+ },
+ {
+ key: "rsvp_type",
+ label: "Has RSVP?",
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: rsvpOptions
+ }
+ },
+ customParser: (f) => [`rsvp_type${f.value ? "<>" : "=="}None`]
+ },
+ {
+ key: "progress_flag",
+ label: T.translate("event_list.progress_flags"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: summit.presentation_action_types.map((pf) => ({
+ value: pf.id,
+ label: pf.label
+ })),
+ multiple: true
+ }
+ },
+ customParser: (f) => [
+ f.value.map((pf) => `actions==type_id==${pf}&&is_completed==1`).join(",")
+ ]
+ },
+ {
+ key: "created",
+ label: T.translate("event_list.created"),
+ operators: [OPERATORS.BEFORE, OPERATORS.AFTER],
+ values: {
+ type: "datetime",
+ props: {
+ mode: "datetime"
+ }
+ }
+ },
+ {
+ key: "last_edited",
+ label: T.translate("event_list.modified"),
+ operators: [OPERATORS.BEFORE, OPERATORS.AFTER],
+ values: {
+ type: "datetime",
+ props: {
+ mode: "datetime"
+ }
+ }
+ },
+ {
+ key: "start_date",
+ label: T.translate("event_list.start_date"),
+ operators: [OPERATORS.BEFORE, OPERATORS.AFTER],
+ values: {
+ type: "datetime",
+ props: {
+ mode: "datetime"
+ }
+ }
+ },
+ {
+ key: "end_date",
+ label: T.translate("event_list.end_date"),
+ operators: [OPERATORS.BEFORE, OPERATORS.AFTER],
+ values: {
+ type: "datetime",
+ props: {
+ mode: "datetime"
+ }
+ }
+ },
+ {
+ key: "duration",
+ label: T.translate("event_list.duration"),
+ operators: [OPERATORS.IS, OPERATORS.LESS, OPERATORS.GREATER],
+ values: {
+ type: "number",
+ props: {
+ min: 0,
+ integer: true
+ }
+ }
+ },
+ {
+ key: "speakers_count",
+ label: T.translate("event_list.speakers_count"),
+ operators: [OPERATORS.IS, OPERATORS.LESS, OPERATORS.GREATER],
+ values: {
+ type: "number",
+ props: {
+ min: 0,
+ integer: true
+ }
+ }
+ },
+ {
+ key: "submitters",
+ label: "Submitters",
+ operators: [OPERATORS.IS],
+ values: {
+ type: "asyncSelect",
+ props: {
+ queryFunction: queryMembers,
+ formatOption: (m) => ({
+ value: m.id,
+ label: `${m.first_name} ${m.last_name} (${m.email})`
+ }),
+ multiple: true
+ }
+ },
+ customParser: (f) => [
+ f.value
+ .flatMap((s) => {
+ const escapedFullName = escapeFilterValue(
+ `${s.raw.first_name} ${s.raw.last_name}`
+ );
+ const escapedEmail = escapeFilterValue(s.raw.email);
+ const fullNameFilter = `created_by_fullname==${escapedFullName}`;
+ const emailFilter = `created_by_email==${escapedEmail}`;
+ return [fullNameFilter, emailFilter];
+ })
+ .join(",")
+ ]
+ },
+ {
+ key: "created_by_company",
+ label: T.translate("event_list.submitter_company"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "company",
+ props: {
+ queryFunction: querySubmitterCompany,
+ multiple: true
+ }
+ },
+ customParser: (f) => [
+ `created_by_company==${f.value
+ .map((c) => escapeFilterValue(c.raw.name))
+ .join("||")}`
+ ]
+ },
+ {
+ key: "streaming_url",
+ label: T.translate("event_list.streaming_url"),
+ operators: [OPERATORS.IS, OPERATORS.LIKE_START, OPERATORS.LIKE],
+ values: {
+ type: "text",
+ props: {}
+ }
+ },
+ {
+ key: "meeting_url",
+ label: T.translate("event_list.meeting_url"),
+ operators: [OPERATORS.IS, OPERATORS.LIKE_START, OPERATORS.LIKE],
+ values: {
+ type: "text",
+ props: {}
+ }
+ },
+ {
+ key: "etherpad_link",
+ label: T.translate("event_list.etherpad_link"),
+ operators: [OPERATORS.IS, OPERATORS.LIKE_START, OPERATORS.LIKE],
+ values: {
+ type: "text",
+ props: {}
+ }
+ },
+ {
+ key: "streaming_type",
+ label: T.translate("event_list.streaming_type"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: streamingTypeOptions
+ }
+ }
+ },
+ {
+ key: "sponsor",
+ label: T.translate("event_list.sponsor"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "company",
+ props: {
+ multiple: true
+ }
+ },
+ customParser: (f) => [
+ `sponsor==${f.value.map((s) => escapeFilterValue(s.raw.name)).join("||")}`
+ ]
+ },
+ {
+ key: "all_companies",
+ label: "All Companies",
+ operators: [OPERATORS.IS],
+ values: {
+ type: "company",
+ props: {
+ queryFunction: queryAllCompanies,
+ multiple: true
+ }
+ },
+ customParser: (f) => {
+ const companies = f.value
+ .map((c) => escapeFilterValue(c.raw.name))
+ .join("||");
+ return [
+ `speaker_company==${companies},created_by_company==${companies},sponsor==${companies}`
+ ];
+ }
+ },
+ {
+ key: "submission_status",
+ label: T.translate("event_list.submission_status"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: submissionStatusOptions
+ }
+ }
+ },
+ {
+ key: "media_upload_with_type",
+ label: "Media Upload Type",
+ operators: [OPERATORS.HAS, OPERATORS.HAS_NOT],
+ values: {
+ type: "select",
+ props: {
+ options: mediaUploadTypes.map((type) => ({
+ value: type.id,
+ label: type.name
+ })),
+ multiple: true
+ }
+ },
+ customParser: (f) => {
+ const filter = [];
+
+ if (f.operator === OPERATORS.HAS.value) {
+ const value = Array.isArray(f.value) ? f.value.join("||") : f.value;
+ filter.push(`has_media_upload_with_type==${value}`);
+ } else {
+ const value = Array.isArray(f.value) ? f.value.join("&&") : f.value;
+ filter.push(`has_not_media_upload_with_type==${value}`);
+ }
+
+ return filter;
+ }
+ },
+ {
+ key: "review_status",
+ label: T.translate("event_list.review_status"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: reviewStatusOptions,
+ multiple: true
+ }
+ }
+ },
+ {
+ key: "submission_source",
+ label: T.translate("event_list.submission_source"),
+ operators: [OPERATORS.IS],
+ values: {
+ type: "select",
+ props: {
+ options: submissionSourceOptions
+ }
+ }
+ }
+];
diff --git a/src/pages/events/summit-event-list-page/index.js b/src/pages/events/summit-event-list-page/index.js
new file mode 100644
index 000000000..537839173
--- /dev/null
+++ b/src/pages/events/summit-event-list-page/index.js
@@ -0,0 +1,487 @@
+/**
+ * Copyright 2026 OpenStack Foundation
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * */
+
+import React, { useEffect, useState } from "react";
+import { connect } from "react-redux";
+import T from "i18n-react/dist/i18n-react";
+import Swal from "sweetalert2";
+import Dropdown from "openstack-uicore-foundation/lib/components/inputs/dropdown";
+import FreeTextSearch from "openstack-uicore-foundation/lib/components/free-text-search";
+import {
+ GridFilter,
+ useGridFilter
+} from "openstack-uicore-foundation/lib/components/mui/grid-filter";
+import BulkEditTable from "openstack-uicore-foundation/lib/components/mui/bulk-edit-table";
+import {
+ bulkUpdateEvents,
+ changeEventListSearchTerm,
+ deleteEvent,
+ exportEvents,
+ getEvents,
+ importEventsCSV,
+ importMP4AssetsFromMUX
+} from "../../../actions/event-actions";
+import { getMediaUploads } from "../../../actions/media-upload-actions";
+import { handleDDLSortByLabel } from "../../../utils/methods";
+import {
+ DEFAULT_CURRENT_PAGE,
+ DEFAULT_Z_INDEX,
+ HIGH_Z_INDEX,
+ MAX_PER_PAGE
+} from "../../../utils/constants";
+import SaveFilterCriteria from "../../../components/filters/save-filter-criteria";
+import SelectFilterCriteria from "../../../components/filters/select-filter-criteria";
+import {
+ deleteFilterCriteria,
+ saveFilterCriteria
+} from "../../../actions/filter-criteria-actions";
+import { CONTEXT_ACTIVITIES } from "../../../utils/filter-criteria-constants";
+import { buildNameIdDDL } from "../../../utils/events/summit-event-list-page.utils";
+import ImportModal from "./components/ImportModal";
+import ImportMUXModal from "./components/ImportMUXModal";
+import {
+ formatEventData,
+ getCriterias,
+ getOptionalColumns,
+ toApiSortKey,
+ toUiSortKey
+} from "./helpers";
+
+const FILTER_ID = "summit_activity_list";
+
+const SummitEventListPage = ({
+ events,
+ currentSummit,
+ extraColumns,
+ term,
+ currentPage,
+ perPage,
+ order,
+ orderDir,
+ totalEvents,
+ history,
+ mediaUploadTypes,
+ getEvents,
+ deleteEvent,
+ getMediaUploads,
+ exportEvents,
+ importEventsCSV,
+ importMP4AssetsFromMUX,
+ changeEventListSearchTerm,
+ saveFilterCriteria,
+ deleteFilterCriteria,
+ bulkUpdateEvents
+}) => {
+ const [showImportModal, setShowImportModal] = useState(false);
+ const [showImportFromMUXModal, setShowImportFromMUXModal] = useState(false);
+ const [selectedColumns, setSelectedColumns] = useState(extraColumns ?? []);
+ const [selectedFilterCriteria, setSelectedFilterCriteria] = useState(null);
+ const { parsedFilter, resetFilters, filterValues, setFilters } =
+ useGridFilter(FILTER_ID);
+
+ // eslint-disable-next-line no-underscore-dangle
+ const _getEvents = (params = {}) => {
+ const mergedParams = {
+ term,
+ page: currentPage,
+ perPage,
+ order,
+ orderDir,
+ ...params
+ };
+
+ const {
+ term: t,
+ page: p,
+ perPage: pp,
+ order: o,
+ orderDir: od
+ } = mergedParams;
+
+ getEvents(t, p, pp, o, od, parsedFilter, selectedColumns);
+ };
+
+ useEffect(() => {
+ if (currentSummit) {
+ getMediaUploads("", 1, MAX_PER_PAGE, "name", 1);
+ getEvents();
+ // GridFilter persists criteria to localStorage under a summit-agnostic
+ // FILTER_ID, so it survives a summit switch unless cleared explicitly here.
+ setSelectedFilterCriteria(null);
+ resetFilters();
+ }
+ }, [currentSummit?.id]);
+
+ useEffect(() => {
+ _getEvents();
+ }, [parsedFilter.join(",")]);
+
+ useEffect(() => {
+ if (selectedFilterCriteria) {
+ setFilters(selectedFilterCriteria.criteria);
+ } else {
+ resetFilters();
+ }
+ }, [selectedFilterCriteria]);
+
+ useEffect(() => {
+ setSelectedColumns(extraColumns ?? []);
+ }, [extraColumns]);
+
+ const handleMUXImport = (ev) => {
+ ev.preventDefault();
+ setShowImportFromMUXModal(true);
+ };
+
+ const handleEdit = (row) => {
+ history.push(`/app/summits/${currentSummit.id}/events/${row.id}`);
+ };
+
+ const handleExport = (ev) => {
+ ev.preventDefault();
+ exportEvents(term, order, orderDir, parsedFilter);
+ };
+
+ const handlePageChange = (page) => {
+ _getEvents({ page });
+ };
+
+ const handlePerPageChange = (newPerPage) => {
+ _getEvents({ perPage: newPerPage });
+ };
+
+ const handleSort = (index, key, dir) => {
+ _getEvents({ order: toApiSortKey(key), orderDir: dir });
+ };
+
+ const handleSearch = (newTerm) => {
+ _getEvents({ term: newTerm, page: DEFAULT_CURRENT_PAGE });
+ };
+
+ const handleNewEvent = () => {
+ history.push(`/app/summits/${currentSummit.id}/events/new`);
+ };
+
+ const handleDeleteEvent = (row) => {
+ Swal.fire({
+ title: T.translate("general.are_you_sure"),
+ text: `${T.translate("event_list.delete_event_warning")} ${row.title}`,
+ type: "warning",
+ showCancelButton: true,
+ confirmButtonColor: "#DD6B55",
+ confirmButtonText: T.translate("general.yes_delete")
+ }).then((result) => {
+ if (result.value) {
+ deleteEvent(row.id);
+ }
+ });
+ };
+
+ const handleTermChange = (newTerm) => {
+ changeEventListSearchTerm(newTerm);
+ };
+
+ const handleFilterCriteriaSave = ({ name, id, visibility }) => {
+ const filterToSave = {
+ id,
+ show_id: currentSummit.id,
+ name,
+ enabled_filters: filterValues.map((f) => f.criteria),
+ // only save criteria for enabled filters
+ criteria: filterValues,
+ context: CONTEXT_ACTIVITIES,
+ visibility
+ };
+
+ saveFilterCriteria(filterToSave);
+ };
+
+ const optionalColumns = getOptionalColumns(
+ currentSummit.selection_plans,
+ currentSummit.tracks,
+ currentSummit.event_types,
+ currentSummit.id
+ );
+
+ const columnDDLOptions = [
+ ...optionalColumns.map((oc) => ({ value: oc.columnKey, label: oc.label })),
+ {
+ value: "all_companies",
+ label: T.translate("event_list.all_companies")
+ },
+ {
+ value: "all",
+ label: T.translate("general.all")
+ }
+ ];
+
+ const handleColumnsChange = (ev) => {
+ const { value: newColumns } = ev.target;
+
+ if (newColumns.includes("all")) {
+ setSelectedColumns(
+ columnDDLOptions.map((opt) => opt.value).filter((v) => v !== "all")
+ );
+ return;
+ }
+
+ const allCompanies = ["submitter_company", "speaker_company", "sponsor"];
+ const hadAllCompanies = selectedColumns.includes("all_companies");
+ const hasAllCompanies = newColumns.includes("all_companies");
+
+ if (hadAllCompanies && !hasAllCompanies) {
+ setSelectedColumns(newColumns.filter((c) => !allCompanies.includes(c)));
+ return;
+ }
+
+ if (hasAllCompanies) {
+ const selectedCompanies = allCompanies.filter((c) =>
+ selectedColumns.includes(c)
+ ).length;
+ const newCompanies = allCompanies.filter((c) =>
+ newColumns.includes(c)
+ ).length;
+
+ setSelectedColumns(
+ newCompanies < selectedCompanies
+ ? newColumns.filter((c) => c !== "all_companies")
+ : [...new Set([...newColumns, ...allCompanies])]
+ );
+ return;
+ }
+
+ setSelectedColumns(newColumns);
+ };
+
+ const handleFilterCriteriaChange = (filterCriteria) => {
+ setSelectedFilterCriteria(filterCriteria);
+ };
+
+ const handleFilterCriteriaDelete = (filterCriteriaId) => {
+ deleteFilterCriteria(filterCriteriaId).then(() => {
+ setSelectedFilterCriteria(null);
+ });
+ };
+
+ const eventTypeOptions = buildNameIdDDL(currentSummit.event_types);
+
+ const fixedColumns = [
+ { columnKey: "id", label: T.translate("general.id"), sortable: true },
+ {
+ columnKey: "type",
+ label: T.translate("event_list.type"),
+ sortable: true,
+ // eslint-disable-next-line react/no-unstable-nested-components
+ editableField: (extraProps) => (
+ ({ ...base, zIndex: 9999 }),
+ control: (base, state) => ({
+ ...base,
+ zIndex: state.menuIsOpen ? HIGH_Z_INDEX : DEFAULT_Z_INDEX
+ })
+ }}
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...extraProps}
+ />
+ ),
+ render: (e) => e.name
+ },
+ {
+ columnKey: "title",
+ label: T.translate("event_list.title"),
+ sortable: true,
+ editableField: true,
+ placeholder: T.translate("bulk_actions_page.placeholders.event_title")
+ },
+ {
+ columnKey: "selection_status",
+ label: T.translate("event_list.selection_status"),
+ sortable: true,
+ render: (status, row) =>
+ status === "unaccepted" && row.is_published === true
+ ? "accepted"
+ : status
+ }
+ ];
+
+ const tableOptions = {
+ sortCol: toUiSortKey(order),
+ sortDir: orderDir,
+ className: "summit-event-list-table",
+ actions: {
+ edit: { onClick: handleEdit },
+ delete: { onClick: handleDeleteEvent }
+ }
+ };
+
+ const selectedOptionalColumns = optionalColumns.filter((c) =>
+ selectedColumns.includes(c.columnKey)
+ );
+
+ const tableColumns = [...fixedColumns, ...selectedOptionalColumns];
+
+ if (!currentSummit.id) return
;
+
+ const tableData = events.map((e) => formatEventData(e, currentSummit));
+
+ return (
+
+
+ {T.translate("event_list.event_list")} ({totalEvents})
+
+
+
+
+
+
+ {T.translate("event_list.add_event")}
+
+
+ {T.translate("general.export")}
+
+
+ {T.translate("event_list.mux_import")}
+
+
setShowImportModal(true)}
+ type="button"
+ >
+ {T.translate("event_list.import")}
+
+
+
+
+
+
+
+
+
+ {T.translate("event_list.select_fields")}
+
+
+
+
+
+ {events.length === 0 &&
{T.translate("event_list.no_events")}
}
+
+ {events.length > 0 && (
+
+
+
+ )}
+
+
setShowImportModal(false)}
+ onImport={importEventsCSV}
+ />
+
+ setShowImportFromMUXModal(false)}
+ onImport={importMP4AssetsFromMUX}
+ />
+
+ );
+};
+
+const mapStateToProps = ({
+ currentSummitState,
+ currentEventListState,
+ mediaUploadListState
+}) => ({
+ currentSummit: currentSummitState.currentSummit,
+ mediaUploadTypes: mediaUploadListState?.media_uploads ?? [],
+ ...currentEventListState
+});
+
+export default connect(mapStateToProps, {
+ getEvents,
+ deleteEvent,
+ exportEvents,
+ importEventsCSV,
+ importMP4AssetsFromMUX,
+ changeEventListSearchTerm,
+ saveFilterCriteria,
+ deleteFilterCriteria,
+ bulkUpdateEvents,
+ getMediaUploads
+})(SummitEventListPage);
diff --git a/src/reducers/events/event-list-reducer.js b/src/reducers/events/event-list-reducer.js
index d1ce06753..f8118a6c6 100644
--- a/src/reducers/events/event-list-reducer.js
+++ b/src/reducers/events/event-list-reducer.js
@@ -27,7 +27,7 @@ import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions";
momentDurationFormatSetup(moment);
const DEFAULT_STATE = {
- events: {},
+ events: [],
term: null,
order: "id",
orderDir: 1,
@@ -36,7 +36,7 @@ const DEFAULT_STATE = {
perPage: 10,
totalEvents: 0,
summitTZ: "",
- filters: {},
+ filters: [],
extraColumns: [],
selectionPlans: []
};
diff --git a/src/store.js b/src/store.js
index 326f84399..9d9d6e57c 100644
--- a/src/store.js
+++ b/src/store.js
@@ -16,6 +16,7 @@ import { loggedUserReducer } from "openstack-uicore-foundation/lib/security/redu
import thunk from "redux-thunk";
import { persistStore, persistCombineReducers } from "redux-persist";
import storage from "redux-persist/es/storage";
+import { allFiltersReducer } from "openstack-uicore-foundation/lib/components/mui/grid-filter";
import baseReducer from "./reducers/base-reducer";
import currentSummitReducer from "./reducers/summits/current-summit-reducer";
import directoryReducer from "./reducers/summits/directory-reducer";
@@ -185,6 +186,7 @@ const config = {
const reducers = persistCombineReducers(config, {
loggedUserState: loggedUserReducer,
+ allGridFiltersState: allFiltersReducer,
baseState: baseReducer,
directoryState: directoryReducer,
currentSummitState: currentSummitReducer,
diff --git a/src/styles/summit-event-list-page.less b/src/styles/summit-event-list-page.less
deleted file mode 100644
index fd043173d..000000000
--- a/src/styles/summit-event-list-page.less
+++ /dev/null
@@ -1,23 +0,0 @@
-.filters-row {
- margin: 25px -15px 0px;
- display: flex;
- flex-wrap: wrap;
- align-items: start;
-
- .col-md-6,
- .col-md-3 {
- margin-bottom: 10px;
- }
-}
-
-.event-list-date-picker {
- > div:first-of-type {
- width: 100%;
- }
-}
-
-@media (max-width: 991px) {
- .filters-row {
- display: block;
- }
-}
diff --git a/src/utils/__tests__/summitUtils.test.js b/src/utils/__tests__/summitUtils.test.js
deleted file mode 100644
index 39c6f894f..000000000
--- a/src/utils/__tests__/summitUtils.test.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import { formatEventData } from "../summitUtils";
-
-describe("summitUtils.formatEventData", () => {
- const summit = {
- time_zone: { name: "UTC" },
- time_zone_id: "UTC"
- };
-
- test("returns speakers_count based on speakers length when type.use_speakers is true", () => {
- const event = {
- id: 10,
- summit_id: 1,
- title: "Test Event",
- is_published: false,
- type: {
- use_speakers: true,
- allows_location: false,
- allows_attendee_vote: false,
- allows_publishing_dates: false
- },
- speakers: [
- { first_name: "Jane", last_name: "Doe", company: "ACME" },
- { first_name: "John", last_name: "Smith", company: "Globex" }
- ],
- tags: [],
- sponsors: []
- };
-
- const result = formatEventData(event, summit);
-
- expect(result.speakers_count).toBe(2);
- });
-
- test("returns \"0\" when type.use_speakers is true and speakers list is empty", () => {
- const event = {
- id: 12,
- summit_id: 1,
- title: "No Speakers Yet",
- is_published: false,
- type: {
- use_speakers: true,
- allows_location: false,
- allows_attendee_vote: false,
- allows_publishing_dates: false
- },
- speakers: [],
- tags: [],
- sponsors: []
- };
-
- const result = formatEventData(event, summit);
-
- expect(result.speakers_count).toBe("0");
- });
-
- test("returns N/A and does not throw when event type is missing", () => {
- const event = {
- id: 11,
- summit_id: 1,
- title: "Type-less Event",
- is_published: false,
- speakers: [],
- tags: [],
- sponsors: []
- };
-
- let result;
- expect(() => {
- result = formatEventData(event, summit);
- }).not.toThrow();
- expect(result.speakers_count).toBe("N/A");
- });
-});
diff --git a/src/utils/methods.js b/src/utils/methods.js
index a2bc7edab..b22d02493 100644
--- a/src/utils/methods.js
+++ b/src/utils/methods.js
@@ -644,3 +644,9 @@ export const getFileUploadAllowedExtensions = () => {
export const isImageUrl = (url) =>
/\.(jpe?g|png|gif|webp|svg|bmp)(\?|$)/i.test(url);
+
+export const formatDuration = (duration) => {
+ const d = moment.duration(duration, "seconds");
+ const formatted = d.format("mm:ss", { trim: false });
+ return formatted !== "00:00" ? formatted : "TBD";
+};
diff --git a/src/utils/summitUtils.js b/src/utils/summitUtils.js
deleted file mode 100644
index 525446f23..000000000
--- a/src/utils/summitUtils.js
+++ /dev/null
@@ -1,163 +0,0 @@
-import moment from "moment-timezone";
-import { MILLISECONDS_IN_SECOND } from "./constants";
-
-// Default columns to show
-export const defaultColumns = ["id", "event_type", "title", "selection_status"];
-
-export const editableColumns = [
- "event_type",
- "title",
- "speakers",
- "track",
- "selection_plan",
- "streaming_url",
- "meeting_url",
- "etherpad_link"
-];
-
-const formatDuration = (duration) => {
- const d = moment.duration(duration, "seconds");
- return d.format("mm:ss") !== "00" ? d.format("mm:ss") : "TBD";
-};
-
-export const formatEventData = (e, summit) => {
- const published_date = e.is_published
- ? moment(e.published_date * MILLISECONDS_IN_SECOND)
- .tz(summit.time_zone.name)
- .format("MMMM Do YYYY, h:mm a")
- : "No";
-
- let speakers_companies =
- Array.isArray(e.speakers) && e.speakers.length > 0
- ? e.speakers?.map((e) => e.company)
- : [];
- speakers_companies =
- speakers_companies.length > 0
- ? speakers_companies.filter(
- (item, index) =>
- item !== "" && speakers_companies.indexOf(item) === index
- )
- : [];
-
- const event_type_capacity = [];
-
- if (e.type?.allows_location) event_type_capacity.push("Allows Location");
- if (e.type?.allows_attendee_vote)
- event_type_capacity.push("Allows Attendee Vote");
- if (e.type?.allows_publishing_dates)
- event_type_capacity.push("Allows Publishing Dates");
-
- let speakers_count;
-
- if (e.type?.use_speakers) {
- if (e.speakers && e.speakers.length > 0) {
- speakers_count = e.speakers.length;
- } else {
- speakers_count = "0";
- }
- } else {
- speakers_count = "N/A";
- }
-
- return {
- id: e.id,
- type: e.type,
- summit_id: e.summit_id,
- title: e.title,
- status: e.status ?? "Not Submitted",
- selection_status:
- e.selection_status === "unaccepted" && e.is_published === true
- ? "accepted"
- : e.selection_status,
- published_date,
- created_by_fullname: e.hasOwnProperty("created_by")
- ? `${e.created_by.first_name} ${e.created_by.last_name} (${e.created_by.email})`
- : "TBD",
- submitter_company: e.hasOwnProperty("created_by")
- ? e.created_by.company
- : "N/A",
- speakers:
- Array.isArray(e.speakers) && e.speakers.length > 0
- ? e.speakers.map((s) => `${s.first_name} ${s.last_name}`).join(", ")
- : "N/A",
- speaker_company:
- speakers_companies.length > 0
- ? speakers_companies.reduce(
- (accumulator, company) =>
- accumulator + (accumulator !== "" ? ", " : "") + company,
- ""
- )
- : "N/A",
- duration:
- e.type?.allows_publishing_dates && e.duration
- ? formatDuration(e?.duration)
- : "N/A",
- speakers_count,
- event_type_capacity: event_type_capacity.reduce(
- (accumulator, capacity) =>
- accumulator + (accumulator !== "" ? ", " : "") + capacity,
- ""
- ),
- track: e?.track?.name ? e?.track?.name : "TBD",
- level: e.level ? e.level : "N/A",
- tags:
- Array.isArray(e.tags) && e.tags.length > 0
- ? e.tags.reduce(
- (accumulator, t) =>
- accumulator + (accumulator !== "" ? ", " : "") + t.tag,
- ""
- )
- : "N/A",
- selection_plan: e.selection_plan ? e.selection_plan : "N/A",
- location: e.location?.name ? e.location?.name : "N/A",
- streaming_url: e.streaming_url ? e.streaming_url : "N/A",
- meeting_url: e.meeting_url ? e.meeting_url : "N/A",
- etherpad_link: e.etherpad_link ? e.etherpad_link : "N/A",
- streaming_type: e.streaming_type ? e.streaming_type : "N/A",
- start_date: e.start_date
- ? moment(e.start_date * MILLISECONDS_IN_SECOND)
- .tz(summit.time_zone.name)
- .format("MMMM Do YYYY, h:mm a")
- : "TBD",
- end_date: e.end_date
- ? moment(e.end_date * MILLISECONDS_IN_SECOND)
- .tz(summit.time_zone.name)
- .format("MMMM Do YYYY, h:mm a")
- : "TBD",
- sponsor:
- Array.isArray(e.sponsors) && e.sponsors.length > 0
- ? e.sponsors.map((s) => s.name).join(", ")
- : "N/A",
- media_uploads:
- Array.isArray(e.media_uploads) && e.media_uploads?.length > 0
- ? e?.media_uploads?.map((m) => ({
- ...m,
- created: moment(m.created * MILLISECONDS_IN_SECOND)
- .tz(summit.time_zone.name)
- .format("MMMM Do YYYY, h:mm a")
- }))
- : "N/A",
- created: e.created
- ? moment(e.created * MILLISECONDS_IN_SECOND)
- .tz(summit.time_zone_id)
- .format("MMMM Do YYYY, h:mm a")
- : "TBD",
- modified: e.last_edited
- ? moment(e.last_edited * MILLISECONDS_IN_SECOND)
- .tz(summit.time_zone_id)
- .format("MMMM Do YYYY, h:mm a")
- : "TBD",
- progress_flags: e?.actions
- ?.map((a) => `${a.type.label} (${a.is_completed ? "ON" : "OFF"})`)
- .join(", "),
- submission_source: e.submission_source ? e.submission_source : "N/A",
- review_status: e.review_status ?? "N/A"
- };
-};
-
-export const getIdValue = (prop) => {
- if (prop && typeof prop === "number") {
- return prop;
- }
- return prop?.id;
-};
diff --git a/yarn.lock b/yarn.lock
index 2181ae020..827316200 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9056,10 +9056,10 @@ open@^10.0.3:
is-inside-container "^1.0.0"
wsl-utils "^0.1.0"
-openstack-uicore-foundation@5.0.35:
- version "5.0.35"
- resolved "https://registry.yarnpkg.com/openstack-uicore-foundation/-/openstack-uicore-foundation-5.0.35.tgz#370ce04f7dfcef4cd5a7e3f85a6b1bcc9e9db57e"
- integrity sha512-dxguCaF6qh6ilgtNnhZ3mUocM7YUycd3KjVlnLuNV5QpMdOHdEmd0QjooH+XmW/o4mBYMgqcmyIhVaa0YH7z1Q==
+openstack-uicore-foundation@5.0.36-beta.2:
+ version "5.0.36-beta.2"
+ resolved "https://registry.yarnpkg.com/openstack-uicore-foundation/-/openstack-uicore-foundation-5.0.36-beta.2.tgz#6d175e10fb24b4902e7d4ec88594b80f6e6eb64b"
+ integrity sha512-JyzM4mss5dAIlOsctwrKo3UxlLbu4PoXqaK+ORRwWZ4U8sszjZgY9IQ1WZbqGi/WYcE1m/DBM4sanoB9SdIXvg==
dependencies:
use-sync-external-store "^1.6.0"
@@ -10015,7 +10015,7 @@ react-input-autosize@^2.2.1:
dependencies:
prop-types "^15.5.8"
-react-is@^16.13.1, react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0:
+react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -10035,7 +10035,7 @@ react-is@^19.0.0, react-is@^19.2.3:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.5.tgz#7e7b54143e9313fed787b23fd4295d5a23872ad9"
integrity sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==
-react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
+react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
@@ -10056,20 +10056,7 @@ react-overlays@^0.7.4:
prop-types-extra "^1.0.1"
warning "^3.0.0"
-react-redux@^5.0.7:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.2.tgz#b19cf9e21d694422727bf798e934a916c4080f57"
- integrity sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==
- dependencies:
- "@babel/runtime" "^7.1.2"
- hoist-non-react-statics "^3.3.0"
- invariant "^2.2.4"
- loose-envify "^1.1.0"
- prop-types "^15.6.1"
- react-is "^16.6.0"
- react-lifecycles-compat "^3.0.0"
-
-react-redux@^7.2.0:
+react-redux@^7.1.0, react-redux@^7.2.0:
version "7.2.9"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d"
integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==
@@ -10275,7 +10262,7 @@ redux-thunk@^2.3.0:
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b"
integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==
-redux@^3.6.0, redux@^3.7.2:
+redux@^3.6.0:
version "3.7.2"
resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
integrity sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==
@@ -10285,7 +10272,7 @@ redux@^3.6.0, redux@^3.7.2:
loose-envify "^1.1.0"
symbol-observable "^1.0.3"
-redux@^4.0.0, redux@^4.0.4, redux@^4.2.0:
+redux@^4.0.0, redux@^4.0.4, redux@^4.2.0, redux@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==