Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/actions/speaker-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ export const UNSELECT_ALL_SUMMIT_SPEAKERS = "UNSELECT_ALL_SUMMIT_SPEAKERS";
export const SEND_SPEAKERS_EMAILS = "SEND_SPEAKERS_EMAILS";
export const SET_SPEAKERS_CURRENT_FLOW_EVENT =
"SET_SPEAKERS_CURRENT_FLOW_EVENT";
export const REQUEST_SPEAKERS_ACTIVITIES_COUNT =
"REQUEST_SPEAKERS_ACTIVITIES_COUNT";
export const RECEIVE_SPEAKERS_ACTIVITIES_COUNT =
"RECEIVE_SPEAKERS_ACTIVITIES_COUNT";

const normalizeEntity = (entity) => {
const normalizedEntity = { ...entity };
Expand Down Expand Up @@ -882,6 +886,18 @@ const parseFilters = (filters) => {
return filter;
};

const getSpeakersActivitiesCount =
(summitId, filter, accessToken) => (dispatch) => {
const params = { access_token: accessToken };
if (filter.length > 0) params["filter[]"] = filter;
return getRequest(
createAction(REQUEST_SPEAKERS_ACTIVITIES_COUNT),
createAction(RECEIVE_SPEAKERS_ACTIVITIES_COUNT),
`${window.API_BASE_URL}/api/v1/summits/${summitId}/speakers/all/events/count`,
authErrorHandler
)(params)(dispatch);
};

export const getSpeakersBySummit =
(
term = null,
Expand Down Expand Up @@ -928,6 +944,8 @@ export const getSpeakersBySummit =
params.order = `${orderDirSign}${order}`;
}

dispatch(getSpeakersActivitiesCount(currentSummit.id, filter, accessToken));

return getRequest(
createAction(REQUEST_SPEAKERS_BY_SUMMIT),
createAction(RECEIVE_SPEAKERS_BY_SUMMIT),
Expand Down
20 changes: 20 additions & 0 deletions src/actions/submitter-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,27 @@ export const UNSELECT_ALL_SUMMIT_SUBMITTERS = "UNSELECT_ALL_SUMMIT_SUBMITTERS";
export const SEND_SUBMITTERS_EMAILS = "SEND_SUBMITTERS_EMAILS";
export const SET_SUBMITTERS_CURRENT_FLOW_EVENT =
"SET_SUBMITTERS_CURRENT_FLOW_EVENT";
export const REQUEST_SUBMITTERS_ACTIVITIES_COUNT =
"REQUEST_SUBMITTERS_ACTIVITIES_COUNT";
export const RECEIVE_SUBMITTERS_ACTIVITIES_COUNT =
"RECEIVE_SUBMITTERS_ACTIVITIES_COUNT";

export const initSubmittersList = () => async (dispatch) => {
dispatch(createAction(INIT_SUBMITTERS_LIST_PARAMS)());
};

const getSubmittersActivitiesCount =
(summitId, filter, accessToken) => (dispatch) => {
const params = { access_token: accessToken };
if (filter.length > 0) params["filter[]"] = filter;
return getRequest(
createAction(REQUEST_SUBMITTERS_ACTIVITIES_COUNT),
createAction(RECEIVE_SUBMITTERS_ACTIVITIES_COUNT),
`${window.API_BASE_URL}/api/v1/summits/${summitId}/submitters/all/events/count`,
authErrorHandler
)(params)(dispatch);
};

export const getSubmittersBySummit =
(
term = null,
Expand Down Expand Up @@ -97,6 +113,10 @@ export const getSubmittersBySummit =
params.order = `${orderDirSign}${order}`;
}

dispatch(
getSubmittersActivitiesCount(currentSummit.id, filter, accessToken)
);

return getRequest(
createAction(REQUEST_SUBMITTERS_BY_SUMMIT),
createAction(RECEIVE_SUBMITTERS_BY_SUMMIT),
Expand Down
6 changes: 4 additions & 2 deletions src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"member": "Member",
"members": "Members",
"event": "Activity",
"activities": "Activities",
"group": "Group",
"yes": "Yes",
"no": "No",
Expand Down Expand Up @@ -1045,7 +1046,7 @@
"send_emails_title": "You are about to send an EMAIL BLAST to selected speakers !",
"should_send_copy_2_submitter": "Also send to submitter?",
"allows_to_reassign": "Allow to reassign?",
"items_qty": "Selected {qty} Speakers",
"items_qty": "Selected {qty} Speakers | {activitiesQty} Activities",
"placeholders": {
"search_speakers": "Search by Full Name, Email, Speaker Id, Member Id, Title Or Abstract",
"test_recipient": "Optional Test Recipient"
Expand All @@ -1059,7 +1060,8 @@
"send_emails_title": "You are about to send an EMAIL BLAST to selected submitters !",
"resend_done": "Emails sent successfully.",
"submitters": "Submitters",
"submitters_no_speakers": "Submitters (no speakers)"
"submitters_no_speakers": "Submitters (no speakers)",
"items_qty": "Selected {qty} Submitters | {activitiesQty} Activities"
},
"speaker_attendance_list": {
"speaker_attendance_list": "Speaker Attendance List",
Expand Down
38 changes: 31 additions & 7 deletions src/pages/summit_speakers/summit-speakers-list-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import { connect } from "react-redux";
import T from "i18n-react/dist/i18n-react";
import Swal from "sweetalert2";
import { Modal, Pagination } from "react-bootstrap";
import FreeTextSearch from "openstack-uicore-foundation/lib/components/free-text-search"
import SelectableTable from "openstack-uicore-foundation/lib/components/table-selectable"
import Dropdown from "openstack-uicore-foundation/lib/components/inputs/dropdown"
import FreeTextSearch from "openstack-uicore-foundation/lib/components/free-text-search";
import SelectableTable from "openstack-uicore-foundation/lib/components/table-selectable";
import Dropdown from "openstack-uicore-foundation/lib/components/inputs/dropdown";
import Input from "openstack-uicore-foundation/lib/components/inputs/text-input";
import SpeakerPromoCodeSpecForm from "../../components/forms/speakers-promo-code-spec-form";
import {
Expand Down Expand Up @@ -702,8 +702,11 @@ class SummitSpeakersListPage extends React.Component {
order,
orderDir,
totalItems,
totalActivities,
selectedCount,
selectedAll,
selectedItems,
excludedItems,
selectionPlanFilter,
trackFilter,
trackGroupFilter,
Expand All @@ -713,6 +716,20 @@ class SummitSpeakersListPage extends React.Component {
currentFlowEvent
} = this.getSubjectProps();

const selectedActivities = (() => {
if (selectedAll && excludedItems.length === 0) return totalActivities;
const relevant = selectedAll
? items.filter((item) => !excludedItems.includes(item.id))
: items.filter((item) => selectedItems.includes(item.id));
const ids = new Set();
relevant.forEach((item) => {
(item.accepted_presentations || []).forEach((p) => ids.add(p.id));
(item.alternate_presentations || []).forEach((p) => ids.add(p.id));
(item.rejected_presentations || []).forEach((p) => ids.add(p.id));
});
return ids.size;
})();
Comment on lines +719 to +731
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Selected activities count is incorrect for cross-page selections.

At Lines 721-724, the calculation only inspects current-page items. When selections span multiple pages (or selectedAll with exclusions), selectedActivities no longer reflects all selected entities, so the UI shows inaccurate totals.

You’ll need a selection model that tracks activity ids beyond the current page (or a backend selected-count endpoint) to keep this number correct.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/summit_speakers/summit-speakers-list-page.js` around lines 719 -
731, The selectedActivities computation in the selectedActivities IIFE only
scans the current-page items, so cross-page selections (selectedAll with
exclusions or selectedItems from other pages) are missed; update the logic to
derive the count from a global selection model of activity ids instead of
page-local items: when selectedAll is true compute count as totalActivities
minus the number of activity ids represented by excludedItems (or fetch excluded
activity ids via the backend), and when selectedAll is false compute count as
the size of the union of activity ids tracked in a global selectedActivityIds
set/state (or an API-provided selected-count), replacing the current per-page
traversal in the selectedActivities calculation and using symbols
selectedActivities, selectedAll, excludedItems, selectedItems, items,
totalActivities to locate and update the code.


const columns = [
{
columnKey: "full_name",
Expand Down Expand Up @@ -901,7 +918,11 @@ class SummitSpeakersListPage extends React.Component {
{this.state.source === sources.speakers
? T.translate("summit_speakers_list.summit_speakers_list")
: T.translate("summit_submitters_list.summit_submitters_list")}{" "}
({totalItems})
({totalItems}{" "}
{this.state.source === sources.speakers
? T.translate("summit_speakers_list.speakers")
: T.translate("summit_submitters_list.submitters")}{" "}
| {totalActivities} {T.translate("general.activities")})
</h3>
<div className="row">
<div className="col-md-6">
Expand Down Expand Up @@ -1044,9 +1065,12 @@ class SummitSpeakersListPage extends React.Component {
<div>
<span>
<b>
{T.translate("summit_speakers_list.items_qty", {
qty: selectedCount
})}
{T.translate(
this.state.source === sources.speakers
? "summit_speakers_list.items_qty"
: "summit_submitters_list.items_qty",
{ qty: selectedCount, activitiesQty: selectedActivities }
)}
</b>
</span>
<SelectableTable
Expand Down
7 changes: 6 additions & 1 deletion src/reducers/summit_speakers/summit-speakers-list-reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
SELECT_ALL_SUMMIT_SPEAKERS,
UNSELECT_ALL_SUMMIT_SPEAKERS,
SEND_SPEAKERS_EMAILS,
SET_SPEAKERS_CURRENT_FLOW_EVENT
SET_SPEAKERS_CURRENT_FLOW_EVENT,
RECEIVE_SPEAKERS_ACTIVITIES_COUNT
} from "../../actions/speaker-actions";

import {
Expand All @@ -38,6 +39,7 @@ const DEFAULT_STATE = {
lastPage: 1,
perPage: 10,
totalItems: 0,
totalActivities: 0,
selectedCount: 0,
selectedItems: [],
excludedItems: [],
Expand Down Expand Up @@ -205,6 +207,9 @@ const summitSpeakersListReducer = (state = DEFAULT_STATE, action = {}) => {
case SET_SPEAKERS_CURRENT_FLOW_EVENT: {
return { ...state, currentFlowEvent: payload };
}
case RECEIVE_SPEAKERS_ACTIVITIES_COUNT: {
return { ...state, totalActivities: payload.response.count };
}
Comment on lines +210 to +212
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Prevent stale count responses from clobbering current speaker totals.

At Line 211, the reducer accepts whichever count response arrives last. Under rapid filter/search changes, stale responses can overwrite the active query’s totalActivities.

Please gate this update with request context (request id/filter hash/current summit) so only the latest relevant response updates state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/reducers/summit_speakers/summit-speakers-list-reducer.js` around lines
210 - 212, The RECEIVE_SPEAKERS_ACTIVITIES_COUNT reducer is unconditionally
writing payload.response.count and can be clobbered by stale responses; change
it to only update totalActivities when the incoming response matches the current
request context (e.g., compare payload.requestId or a filterHash and
payload.summitId against the request-tracking fields stored in state such as
state.currentRequestId/state.currentFilterHash/state.currentSummitId). Ensure
the action that triggers the async fetch includes a stable identifier (requestId
or filterHash + summitId) and that the reducer for the request start stores that
identifier on state (e.g., in the handler that sets loading state), then in the
RECEIVE_SPEAKERS_ACTIVITIES_COUNT case only set totalActivities =
payload.response.count if payload.requestId === state.currentRequestId (or
payload.filterHash === state.currentFilterHash && payload.summitId ===
state.currentSummitId); otherwise return state unchanged.

default:
return state;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
SELECT_ALL_SUMMIT_SUBMITTERS,
UNSELECT_ALL_SUMMIT_SUBMITTERS,
SEND_SUBMITTERS_EMAILS,
SET_SUBMITTERS_CURRENT_FLOW_EVENT
SET_SUBMITTERS_CURRENT_FLOW_EVENT,
RECEIVE_SUBMITTERS_ACTIVITIES_COUNT
} from "../../actions/submitter-actions";

import {
Expand All @@ -38,6 +39,7 @@ const DEFAULT_STATE = {
lastPage: 1,
perPage: 10,
totalItems: 0,
totalActivities: 0,
selectedCount: 0,
selectedItems: [],
excludedItems: [],
Expand Down Expand Up @@ -193,6 +195,9 @@ const summitSubmittersListReducer = (state = DEFAULT_STATE, action) => {
case SET_SUBMITTERS_CURRENT_FLOW_EVENT: {
return { ...state, currentFlowEvent: payload };
}
case RECEIVE_SUBMITTERS_ACTIVITIES_COUNT: {
return { ...state, totalActivities: payload.response.count };
}
Comment on lines +198 to +200
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Guard against stale activity-count responses overwriting current filters.

At Line 199, totalActivities is updated from any arriving response. If users change filters/search quickly, older async responses can land late and replace the latest count, so header totals drift from current results.

Consider attaching query context (e.g., currentSummitId + filter hash/request id) to the count request and ignoring mismatched responses in the reducer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/reducers/summit_submitters/summit-submitters-list-reducer.js` around
lines 198 - 200, The reducer unconditionally sets totalActivities on
RECEIVE_SUBMITTERS_ACTIVITIES_COUNT allowing stale async responses to overwrite
current totals; modify the flow so the count request includes a query context/id
(e.g., requestId or filterHash and currentSummitId) in the action payload and
store the latestRequestId (or latestFilterHash/currentSummitId) on the reducer
state (e.g., state.latestSubmittersCountRequestId); in the
RECEIVE_SUBMITTERS_ACTIVITIES_COUNT branch, compare payload.requestId (or
payload.filterHash + payload.summitId) with state.latestSubmittersCountRequestId
(or state.currentFilterHash/state.currentSummitId) and only set totalActivities
= payload.response.count when they match, otherwise ignore the action.

default:
return state;
}
Expand Down
2 changes: 2 additions & 0 deletions src/reducers/summits/current-summit-reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@ const currentSummitReducer = (state = DEFAULT_STATE, action) => {
};
}
case RECEIVE_REG_LITE_SETTINGS: {
if (!payload.response) return state;
const { data } = payload.response;
const reg_lite_marketing_settings = {};

Expand All @@ -695,6 +696,7 @@ const currentSummitReducer = (state = DEFAULT_STATE, action) => {
return { ...state, reg_lite_marketing_settings: newMarketingSettings };
}
case RECEIVE_PRINT_APP_SETTINGS: {
if (!payload.response) return state;
const { data } = payload.response;
const print_app_marketing_settings = {};

Expand Down
2 changes: 1 addition & 1 deletion webpack.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module.exports = merge(common, {
devtool: "inline-source-map",
devServer: {
historyApiFallback: true,
server: { type: "https" }
server: { type: "http" }
},
output: {
filename: "[name].js",
Expand Down
Loading