From 6853676dc30eaf387cf4e7bfe13928105231cad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Fri, 15 May 2026 09:56:15 -0300 Subject: [PATCH 1/3] fix: use denormalize function for modules for sponsor managed pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/sponsor-pages-actions.js | 27 +------- .../sponsor-page-pages-list-reducer.js | 61 ++++--------------- 2 files changed, 14 insertions(+), 74 deletions(-) diff --git a/src/actions/sponsor-pages-actions.js b/src/actions/sponsor-pages-actions.js index 7ce4ec5ba..b4778ae66 100644 --- a/src/actions/sponsor-pages-actions.js +++ b/src/actions/sponsor-pages-actions.js @@ -22,7 +22,6 @@ import { escapeFilterValue } from "openstack-uicore-foundation/lib/utils/actions"; import T from "i18n-react/dist/i18n-react"; -import moment from "moment-timezone"; import { getAccessTokenSafely, normalizeSelectAllField @@ -31,8 +30,7 @@ import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, - DEFAULT_PER_PAGE, - PAGES_MODULE_KINDS + DEFAULT_PER_PAGE } from "../utils/constants"; import { normalizePageTemplateModules } from "../utils/page-template"; @@ -309,28 +307,7 @@ const normalizeSponsorManagedPageToCustomize = (entity) => { ) }; - normalizedEntity.modules = entity.modules.map((module) => { - const normalizedModule = { ...module }; - - if (module.kind === PAGES_MODULE_KINDS.MEDIA && module.upload_deadline) { - normalizedModule.upload_deadline = moment - .utc(module.upload_deadline) - .unix(); - } - - if (module.kind === PAGES_MODULE_KINDS.MEDIA && module.file_type_id) { - normalizedModule.file_type_id = - module.file_type_id?.value || module.file_type_id; - } - - if (module.kind === PAGES_MODULE_KINDS.DOCUMENT && module.file) { - normalizedModule.file = module.file[0] || null; - } - - delete normalizedModule._tempId; - - return normalizedModule; - }); + normalizedEntity.modules = normalizePageTemplateModules(entity.modules); delete normalizedEntity.page_ptr_id; delete normalizedEntity.sponsorship_types; diff --git a/src/reducers/sponsors/sponsor-page-pages-list-reducer.js b/src/reducers/sponsors/sponsor-page-pages-list-reducer.js index 1d79a6fe1..e30e9d436 100644 --- a/src/reducers/sponsors/sponsor-page-pages-list-reducer.js +++ b/src/reducers/sponsors/sponsor-page-pages-list-reducer.js @@ -12,7 +12,6 @@ * */ import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; -import { epochToMomentTimeZone } from "openstack-uicore-foundation/lib/utils/methods"; import { REQUEST_SPONSOR_MANAGED_PAGES, RECEIVE_SPONSOR_MANAGED_PAGES, @@ -28,10 +27,8 @@ import { SET_CURRENT_SUMMIT, RECEIVE_SUMMIT_SPONSORSHIP_TYPES } from "../../actions/summit-actions"; -import { - PAGE_MODULES_DOWNLOAD, - PAGES_MODULE_KINDS -} from "../../utils/constants"; +import { PAGES_MODULE_KINDS } from "../../utils/constants"; +import { denormalizePageModules } from "../../utils/page-template"; const DEFAULT_PAGE = { code: "", @@ -182,54 +179,20 @@ const sponsorPagePagesListReducer = (state = DEFAULT_STATE, action) => { case RECEIVE_SPONSOR_MANAGED_PAGE: { const editPage = payload.response; - const currentEditPage = { - ...editPage, - modules: editPage.modules.map((m) => ({ - ...m, - ...(m.upload_deadline - ? { - upload_deadline: epochToMomentTimeZone( - m.upload_deadline, - state.summitTZ || "UTC" - ) - } - : {}) - })) - }; - return { ...state, currentEditPage }; + const modules = denormalizePageModules( + editPage.modules, + state.summitTZ || "UTC" + ); + + return { ...state, currentEditPage: { ...editPage, modules } }; } case RECEIVE_SPONSOR_CUSTOMIZED_PAGE: { const customizedPage = payload.response; - const modules = customizedPage.modules.map((module) => { - const tmpModule = { - ...module, - ...(module.upload_deadline - ? { - upload_deadline: epochToMomentTimeZone( - module.upload_deadline, - state.summitTZ || "UTC" - ) - } - : {}) - }; - - if (module.kind === PAGES_MODULE_KINDS.DOCUMENT) { - if (module.file) { - tmpModule.file = [ - { - ...module.file, - file_path: module.file.storage_key, - public_url: module.file.file_url - } - ]; - tmpModule.type = PAGE_MODULES_DOWNLOAD.FILE; - } else { - tmpModule.type = PAGE_MODULES_DOWNLOAD.URL; - } - } - return tmpModule; - }); + const modules = denormalizePageModules( + customizedPage.modules, + state.summitTZ || "UTC" + ); return { ...state, currentEditPage: { ...customizedPage, modules } }; } From d41be1065d6186d9a90d5610be9e485cc7e7921f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 9 Jun 2026 13:37:27 -0300 Subject: [PATCH 2/3] fix: add test cases for pages modules checking files and dates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../sponsor-page-pages-list-reducer.test.js | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/src/reducers/sponsors/__tests__/sponsor-page-pages-list-reducer.test.js b/src/reducers/sponsors/__tests__/sponsor-page-pages-list-reducer.test.js index e9f14abc1..fd342f7f8 100644 --- a/src/reducers/sponsors/__tests__/sponsor-page-pages-list-reducer.test.js +++ b/src/reducers/sponsors/__tests__/sponsor-page-pages-list-reducer.test.js @@ -1,4 +1,12 @@ import { RECEIVE_SUMMIT_SPONSORSHIP_TYPES } from "../../../actions/summit-actions"; +import { + RECEIVE_SPONSOR_MANAGED_PAGE, + RECEIVE_SPONSOR_CUSTOMIZED_PAGE +} from "../../../actions/sponsor-pages-actions"; +import { + PAGE_MODULES_DOWNLOAD, + PAGES_MODULE_KINDS +} from "../../../utils/constants"; import sponsorPagePagesListReducer, { DEFAULT_STATE } from "../sponsor-page-pages-list-reducer"; @@ -16,6 +24,115 @@ function createInitialState(overrides = {}) { } describe("sponsorPagePagesListReducer", () => { + const dispatchReceiveManagedPage = (state, modules) => + sponsorPagePagesListReducer(state, { + type: RECEIVE_SPONSOR_MANAGED_PAGE, + payload: { response: { id: 5, code: "P1", modules } } + }); + + const dispatchReceiveCustomizedPage = (state, modules) => + sponsorPagePagesListReducer(state, { + type: RECEIVE_SPONSOR_CUSTOMIZED_PAGE, + payload: { response: { id: 5, code: "P1", modules } } + }); + + describe("RECEIVE_SPONSOR_MANAGED_PAGE", () => { + it("wraps a DOCUMENT module file in an array and sets type to FILE", () => { + const file = { + id: 10, + storage_key: "uploads/doc.pdf", + file_url: "https://cdn/doc.pdf" + }; + const state = createInitialState({ summitTZ: "America/Los_Angeles" }); + + const result = dispatchReceiveManagedPage(state, [ + { id: 1, kind: PAGES_MODULE_KINDS.DOCUMENT, file } + ]); + + const [mod] = result.currentEditPage.modules; + expect(mod.file).toEqual([ + { ...file, file_path: file.storage_key, public_url: file.file_url } + ]); + expect(mod.type).toBe(PAGE_MODULES_DOWNLOAD.FILE); + }); + + it("sets type to URL for a DOCUMENT module without a file", () => { + const state = createInitialState({ summitTZ: "UTC" }); + + const result = dispatchReceiveManagedPage(state, [ + { id: 2, kind: PAGES_MODULE_KINDS.DOCUMENT, file: null } + ]); + + const [mod] = result.currentEditPage.modules; + expect(mod.type).toBe(PAGE_MODULES_DOWNLOAD.URL); + }); + + it("converts upload_deadline for a MEDIA module using summitTZ", () => { + const state = createInitialState({ summitTZ: "America/Los_Angeles" }); + + const result = dispatchReceiveManagedPage(state, [ + { id: 3, kind: PAGES_MODULE_KINDS.MEDIA, upload_deadline: 1700000000 } + ]); + + const [mod] = result.currentEditPage.modules; + expect(mod.upload_deadline).toBe("moment-1700000000-America/Los_Angeles"); + }); + + it("falls back to UTC when summitTZ is not set", () => { + const state = createInitialState({ summitTZ: "" }); + + const result = dispatchReceiveManagedPage(state, [ + { id: 4, kind: PAGES_MODULE_KINDS.MEDIA, upload_deadline: 1700000000 } + ]); + + const [mod] = result.currentEditPage.modules; + expect(mod.upload_deadline).toBe("moment-1700000000-UTC"); + }); + }); + + describe("RECEIVE_SPONSOR_CUSTOMIZED_PAGE", () => { + it("wraps a DOCUMENT module file in an array and sets type to FILE", () => { + const file = { + id: 20, + storage_key: "uploads/spec.pdf", + file_url: "https://cdn/spec.pdf" + }; + const state = createInitialState({ summitTZ: "UTC" }); + + const result = dispatchReceiveCustomizedPage(state, [ + { id: 1, kind: PAGES_MODULE_KINDS.DOCUMENT, file } + ]); + + const [mod] = result.currentEditPage.modules; + expect(mod.file).toEqual([ + { ...file, file_path: file.storage_key, public_url: file.file_url } + ]); + expect(mod.type).toBe(PAGE_MODULES_DOWNLOAD.FILE); + }); + + it("sets type to URL for a DOCUMENT module without a file", () => { + const state = createInitialState({ summitTZ: "UTC" }); + + const result = dispatchReceiveCustomizedPage(state, [ + { id: 2, kind: PAGES_MODULE_KINDS.DOCUMENT, file: null } + ]); + + const [mod] = result.currentEditPage.modules; + expect(mod.type).toBe(PAGE_MODULES_DOWNLOAD.URL); + }); + + it("converts upload_deadline for a MEDIA module using summitTZ", () => { + const state = createInitialState({ summitTZ: "America/Los_Angeles" }); + + const result = dispatchReceiveCustomizedPage(state, [ + { id: 3, kind: PAGES_MODULE_KINDS.MEDIA, upload_deadline: 1700000000 } + ]); + + const [mod] = result.currentEditPage.modules; + expect(mod.upload_deadline).toBe("moment-1700000000-America/Los_Angeles"); + }); + }); + describe("RECEIVE_SUMMIT_SPONSORSHIP_TYPES", () => { const makePayload = (currentPage, data) => ({ response: { From ac1938d627a58dff320d5359a3c3c62248106a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 9 Jun 2026 14:21:31 -0300 Subject: [PATCH 3/3] fix: add sponsor-pages-actions unit test to check file payload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../__tests__/sponsor-pages-actions.test.js | 107 ++++++++++++++++++ src/actions/sponsor-pages-actions.js | 2 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/actions/__tests__/sponsor-pages-actions.test.js diff --git a/src/actions/__tests__/sponsor-pages-actions.test.js b/src/actions/__tests__/sponsor-pages-actions.test.js new file mode 100644 index 000000000..2643abfa4 --- /dev/null +++ b/src/actions/__tests__/sponsor-pages-actions.test.js @@ -0,0 +1,107 @@ +import { normalizeSponsorManagedPageToCustomize } from "../sponsor-pages-actions"; +import { + PAGE_MODULES_DOWNLOAD, + PAGES_MODULE_KINDS +} from "../../utils/constants"; + +jest.mock("moment-timezone", () => { + const mockMoment = { unix: jest.fn(() => 1700000000) }; + const moment = jest.fn(() => mockMoment); + moment.tz = jest.fn(() => mockMoment); + moment.utc = jest.fn(() => mockMoment); + return moment; +}); + +jest.mock("openstack-uicore-foundation/lib/utils/actions", () => ({ + createAction: jest.fn(), + getRequest: jest.fn(), + postRequest: jest.fn(), + putRequest: jest.fn(), + deleteRequest: jest.fn(), + startLoading: jest.fn(), + stopLoading: jest.fn(), + escapeFilterValue: jest.fn() +})); + +jest.mock("openstack-uicore-foundation/lib/security/actions", () => ({ + LOGOUT_USER: "LOGOUT_USER" +})); + +jest.mock("../../utils/methods", () => ({ + getAccessTokenSafely: jest.fn(), + normalizeSelectAllField: jest.fn(() => ({ + apply_to_all_add_ons: false, + allowed_add_ons: [] + })) +})); + +jest.mock("i18n-react/dist/i18n-react", () => ({ + __esModule: true, + default: { translate: (key) => key } +})); + +const buildEntity = (modules = []) => ({ + id: 10, + code: "P1", + allowed_add_ons: [], + page_ptr_id: 99, + sponsorship_types: [1], + summit_id: 5, + template_id: 3, + modules_count: 2, + modules +}); + +describe("normalizeSponsorManagedPageToCustomize", () => { + describe("DOCUMENT module — FILE type", () => { + it("includes the file when it is new (no id or file_id)", () => { + const newFile = { name: "contract.pdf" }; + const entity = buildEntity([ + { + kind: PAGES_MODULE_KINDS.DOCUMENT, + type: PAGE_MODULES_DOWNLOAD.FILE, + file: [newFile] + } + ]); + + const result = normalizeSponsorManagedPageToCustomize(entity); + + expect(result.modules[0].file).toEqual(newFile); + }); + + it("omits the file when it already exists (id present) — isNewFile guard", () => { + const existingFile = { id: 42, name: "brief.pdf", file_id: 7 }; + const entity = buildEntity([ + { + kind: PAGES_MODULE_KINDS.DOCUMENT, + type: PAGE_MODULES_DOWNLOAD.FILE, + file: [existingFile] + } + ]); + + const result = normalizeSponsorManagedPageToCustomize(entity); + + expect(result.modules[0].file).toBeUndefined(); + }); + }); + + describe("DOCUMENT module — URL type", () => { + it("omits file and file_id from the payload", () => { + const entity = buildEntity([ + { + kind: PAGES_MODULE_KINDS.DOCUMENT, + type: PAGE_MODULES_DOWNLOAD.URL, + file: [{ id: 5 }], + file_id: 5, + external_url: "https://example.com" + } + ]); + + const result = normalizeSponsorManagedPageToCustomize(entity); + + expect(result.modules[0].file).toBeUndefined(); + expect(result.modules[0].file_id).toBeUndefined(); + expect(result.modules[0].external_url).toBe("https://example.com"); + }); + }); +}); diff --git a/src/actions/sponsor-pages-actions.js b/src/actions/sponsor-pages-actions.js index b4778ae66..b5db07e7a 100644 --- a/src/actions/sponsor-pages-actions.js +++ b/src/actions/sponsor-pages-actions.js @@ -297,7 +297,7 @@ const normalizeSponsorManagedPage = (entity) => { return normalizedEntity; }; -const normalizeSponsorManagedPageToCustomize = (entity) => { +export const normalizeSponsorManagedPageToCustomize = (entity) => { const normalizedEntity = { ...entity, ...normalizeSelectAllField(