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
116 changes: 116 additions & 0 deletions src/actions/__tests__/event-actions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import configureStore from "redux-mock-store";
import thunk from "redux-thunk";
import flushPromises from "flush-promises";
import { getRequest } from "openstack-uicore-foundation/lib/utils/actions";
import { getEvents } from "../event-actions";
import * as methods from "../../utils/methods";

jest.mock("openstack-uicore-foundation/lib/utils/actions", () => ({
__esModule: true,
...jest.requireActual("openstack-uicore-foundation/lib/utils/actions"),
getRequest: jest.fn()
}));

describe("Event Actions", () => {
const middlewares = [thunk];
const mockStore = configureStore(middlewares);

let capturedParams = null;

beforeEach(() => {
jest.spyOn(methods, "getAccessTokenSafely").mockResolvedValue("TOKEN");
getRequest.mockClear();

getRequest.mockImplementation(
(requestActionCreator, receiveActionCreator) =>
(params = {}) =>
(dispatch) => {
capturedParams = params;

if (
requestActionCreator &&
typeof requestActionCreator === "function"
) {
dispatch(requestActionCreator({}));
}

return new Promise((resolve) => {
if (typeof receiveActionCreator === "function") {
dispatch(receiveActionCreator({ response: {} }));
} else {
dispatch(receiveActionCreator);
}

resolve({ response: {} });
});
}
);
});

afterEach(() => {
jest.restoreAllMocks();
capturedParams = null;
});

test("builds speakers_count between filter using [] syntax", async () => {
const store = mockStore({
currentSummitState: {
currentSummit: {
id: 1,
time_zone: { name: "UTC" }
}
}
});

store.dispatch(
getEvents(null, 1, 10, "id", 1, { speakers_count_filter: [1, 3] }, [])
);

await flushPromises();

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 () => {
const store = mockStore({
currentSummitState: {
currentSummit: {
id: 1,
time_zone: { name: "UTC" }
}
}
});

store.dispatch(getEvents());

await flushPromises();

expect(getRequest).toHaveBeenCalledTimes(1);
expect(capturedParams).toBeTruthy();
expect(capturedParams.fields).toContain("type.use_speakers");
});

test("builds speakers_count operator filter when value is not an array", async () => {
const store = mockStore({
currentSummitState: {
currentSummit: {
id: 1,
time_zone: { name: "UTC" }
}
}
});

store.dispatch(
getEvents(null, 1, 10, "id", 1, { speakers_count_filter: ">=2" }, [])
);

await flushPromises();

expect(getRequest).toHaveBeenCalledTimes(1);
expect(capturedParams).toBeTruthy();
expect(capturedParams["filter[]"]).toContain("speakers_count>=2");
});
});
4 changes: 2 additions & 2 deletions src/actions/event-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ const parseFilters = (filters, term = null) => {
if (Array.isArray(filters.speakers_count_filter)) {
// between
filter.push(
`speakers_count[]]${filters.speakers_count_filter[0]}&&${filters.speakers_count_filter[1]}`
`speakers_count[]${filters.speakers_count_filter[0]}&&${filters.speakers_count_filter[1]}`
);
} else {
filter.push(`speakers_count${filters.speakers_count_filter}`);
Expand Down Expand Up @@ -562,7 +562,7 @@ export const getEvents =
relations:
"none,speakers.none,selection_plan.none,track.none,type.none,created_by.none,location.none,media_uploads.media_upload_type.none",
fields:
"id,created,last_edited,title,start_date,end_date,summit_id,duration,class_name,is_published,level,published_date,meeting_url,status,progress,selection_status,streaming_url,streaming_type,etherpad_link,location.id,location.name,speakers.id,speakers.first_name,speakers.last_name,speakers.company,track.name,track.id,created_by.first_name,created_by.last_name,created_by.email,created_by.company,selection_plan.name,selection_plan.id,media_uploads.id,media_uploads.presentation_id,media_uploads.created,media_uploads.class_name,media_uploads.display_on_site,media_uploads.media_upload_type.name,media_uploads.media_upload_type.id,type.id,type.name,sponsors.id,sponsors.name,allow_feedback,to_record,review_status",
"id,created,last_edited,title,start_date,end_date,summit_id,duration,class_name,is_published,level,published_date,meeting_url,status,progress,selection_status,streaming_url,streaming_type,etherpad_link,location.id,location.name,speakers.id,speakers.first_name,speakers.last_name,speakers.company,track.name,track.id,created_by.first_name,created_by.last_name,created_by.email,created_by.company,selection_plan.name,selection_plan.id,media_uploads.id,media_uploads.presentation_id,media_uploads.created,media_uploads.class_name,media_uploads.display_on_site,media_uploads.media_upload_type.name,media_uploads.media_upload_type.id,type.id,type.name,type.use_speakers,sponsors.id,sponsors.name,allow_feedback,to_record,review_status",
Comment on lines 564 to +565
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Request the other type.* flags that formatEventData already depends on.

src/utils/summitUtils.js also reads type.allows_location, type.allows_attendee_vote, and type.allows_publishing_dates (Lines 44-48 and 92-94 there). Since Line 565 still omits them, the list formatter keeps treating those capabilities as falsy on real event-list payloads, so event_type_capacity/duration remain wrong even after this fix.

Suggested patch
-        "id,created,last_edited,title,start_date,end_date,summit_id,duration,class_name,is_published,level,published_date,meeting_url,status,progress,selection_status,streaming_url,streaming_type,etherpad_link,location.id,location.name,speakers.id,speakers.first_name,speakers.last_name,speakers.company,track.name,track.id,created_by.first_name,created_by.last_name,created_by.email,created_by.company,selection_plan.name,selection_plan.id,media_uploads.id,media_uploads.presentation_id,media_uploads.created,media_uploads.class_name,media_uploads.display_on_site,media_uploads.media_upload_type.name,media_uploads.media_upload_type.id,type.id,type.name,type.use_speakers,sponsors.id,sponsors.name,allow_feedback,to_record,review_status",
+        "id,created,last_edited,title,start_date,end_date,summit_id,duration,class_name,is_published,level,published_date,meeting_url,status,progress,selection_status,streaming_url,streaming_type,etherpad_link,location.id,location.name,speakers.id,speakers.first_name,speakers.last_name,speakers.company,track.name,track.id,created_by.first_name,created_by.last_name,created_by.email,created_by.company,selection_plan.name,selection_plan.id,media_uploads.id,media_uploads.presentation_id,media_uploads.created,media_uploads.class_name,media_uploads.display_on_site,media_uploads.media_upload_type.name,media_uploads.media_upload_type.id,type.id,type.name,type.use_speakers,type.allows_location,type.allows_attendee_vote,type.allows_publishing_dates,sponsors.id,sponsors.name,allow_feedback,to_record,review_status",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/event-actions.js` around lines 564 - 565, The fields list in
event-actions.js omits type.allows_location, type.allows_attendee_vote, and
type.allows_publishing_dates which formatEventData in src/utils/summitUtils.js
depends on; update the fields string (the CSV after "fields:") to include these
three type.* flags so summitUtils.formatEventData sees the real event-type
capabilities and correctly computes event_type_capacity and duration.

page,
per_page: perPage,
access_token: accessToken
Expand Down
71 changes: 71 additions & 0 deletions src/utils/__tests__/summitUtils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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: []
};

expect(() => formatEventData(event, summit)).not.toThrow();
const result = formatEventData(event, summit);
expect(result.speakers_count).toBe("N/A");
});
});
2 changes: 1 addition & 1 deletion src/utils/summitUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const formatEventData = (e, summit) => {

let speakers_count;

if (e.type.use_speakers) {
if (e.type?.use_speakers) {
if (e.speakers && e.speakers.length > 0) {
speakers_count = e.speakers.length;
} else {
Expand Down