From af06a06736472d4f406070fcf60a47c523662c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 7 Apr 2026 17:54:14 -0300 Subject: [PATCH 1/5] fix: denormalize module data when a show page is recieved, add unit tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../__tests__/show-pages-list-reducer.test.js | 469 ++++++++++++++++++ .../sponsors/show-pages-list-reducer.js | 38 +- 2 files changed, 500 insertions(+), 7 deletions(-) create mode 100644 src/reducers/sponsors/__tests__/show-pages-list-reducer.test.js diff --git a/src/reducers/sponsors/__tests__/show-pages-list-reducer.test.js b/src/reducers/sponsors/__tests__/show-pages-list-reducer.test.js new file mode 100644 index 000000000..ca73b03c0 --- /dev/null +++ b/src/reducers/sponsors/__tests__/show-pages-list-reducer.test.js @@ -0,0 +1,469 @@ +import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; +import showPagesListReducer, { + DEFAULT_STATE +} from "../show-pages-list-reducer"; +import { SET_CURRENT_SUMMIT } from "../../../actions/summit-actions"; +import { + REQUEST_SHOW_PAGES, + RECEIVE_SHOW_PAGES, + RECEIVE_SHOW_PAGE, + SHOW_PAGE_ARCHIVED, + SHOW_PAGE_UNARCHIVED, + SHOW_PAGE_DELETED, + RESET_SHOW_PAGE_FORM +} from "../../../actions/show-pages-actions"; +import { RECEIVE_GLOBAL_SPONSORSHIPS } from "../../../actions/sponsor-forms-actions"; +import { + PAGE_MODULES_DOWNLOAD, + PAGES_MODULE_KINDS +} from "../../../utils/constants"; + +jest.mock("openstack-uicore-foundation/lib/utils/methods", () => ({ + epochToMomentTimeZone: jest.fn((value, tz) => `moment-${value}-${tz}`) +})); + +jest.mock("i18n-react/dist/i18n-react", () => ({ + translate: jest.fn((key) => key) +})); + +function createInitialState(overrides = {}) { + return { ...DEFAULT_STATE, ...overrides }; +} + +describe("showPagesListReducer", () => { + let initialState; + + beforeEach(() => { + initialState = createInitialState(); + }); + + describe("SET_CURRENT_SUMMIT", () => { + it("resets to default state", () => { + const dirtyState = createInitialState({ term: "foo", currentPage: 5 }); + const result = showPagesListReducer(dirtyState, { + type: SET_CURRENT_SUMMIT + }); + expect(result).toStrictEqual(DEFAULT_STATE); + }); + }); + + describe("LOGOUT_USER", () => { + it("resets to default state", () => { + const dirtyState = createInitialState({ term: "bar", perPage: 50 }); + const result = showPagesListReducer(dirtyState, { type: LOGOUT_USER }); + expect(result).toStrictEqual(DEFAULT_STATE); + }); + }); + + describe("REQUEST_SHOW_PAGES", () => { + it("stores pagination and filter params and clears showPages", () => { + const stateWithPages = createInitialState({ + showPages: [{ id: 1 }], + currentPage: 3, + perPage: 20 + }); + + const result = showPagesListReducer(stateWithPages, { + type: REQUEST_SHOW_PAGES, + payload: { + term: "acme", + order: "name", + orderDir: -1, + page: 2, + perPage: 25, + hideArchived: true, + summitTZ: "America/New_York" + } + }); + + expect(result.showPages).toStrictEqual([]); + expect(result.term).toBe("acme"); + expect(result.order).toBe("name"); + expect(result.orderDir).toBe(-1); + expect(result.currentPage).toBe(2); + expect(result.perPage).toBe(25); + expect(result.hideArchived).toBe(true); + expect(result.summitTZ).toBe("America/New_York"); + }); + }); + + describe("RECEIVE_SHOW_PAGES", () => { + const makePayload = (data = []) => ({ + response: { + current_page: 2, + total: 42, + last_page: 5, + data + } + }); + + it("updates pagination metadata", () => { + const result = showPagesListReducer(initialState, { + type: RECEIVE_SHOW_PAGES, + payload: makePayload() + }); + + expect(result.currentPage).toBe(2); + expect(result.totalCount).toBe(42); + expect(result.lastPage).toBe(5); + }); + + it("maps page data to list items", () => { + const result = showPagesListReducer(initialState, { + type: RECEIVE_SHOW_PAGES, + payload: makePayload([ + { + id: 10, + code: "CODE-A", + name: "Page A", + apply_to_all_types: false, + sponsorship_types: [{ name: "Gold" }, { name: "Silver" }], + modules_count: { + info_modules_count: 1, + media_request_modules_count: 2, + document_download_modules_count: 3 + }, + is_archived: false + } + ]) + }); + + expect(result.showPages).toStrictEqual([ + { + id: 10, + code: "CODE-A", + name: "Page A", + tier: "Gold, Silver", + info_mod: 1, + upload_mod: 2, + download_mod: 3, + is_archived: false + } + ]); + }); + + it("sets tier to all_tiers translation when apply_to_all_types is true", () => { + const result = showPagesListReducer(initialState, { + type: RECEIVE_SHOW_PAGES, + payload: makePayload([ + { + id: 11, + code: "CODE-B", + name: "Page B", + apply_to_all_types: true, + sponsorship_types: [], + modules_count: { + info_modules_count: 0, + media_request_modules_count: 0, + document_download_modules_count: 0 + }, + is_archived: true + } + ]) + }); + + expect(result.showPages[0].tier).toBe("show_pages.all_tiers"); + expect(result.showPages[0].is_archived).toBe(true); + }); + }); + + describe("SHOW_PAGE_ARCHIVED", () => { + it("sets is_archived=true on the target page", () => { + const state = createInitialState({ + showPages: [ + { id: 1, is_archived: false }, + { id: 2, is_archived: false } + ] + }); + + const result = showPagesListReducer(state, { + type: SHOW_PAGE_ARCHIVED, + payload: { pageId: 1 } + }); + + expect(result.showPages.find((p) => p.id === 1).is_archived).toBe(true); + expect(result.showPages.find((p) => p.id === 2).is_archived).toBe(false); + }); + }); + + describe("SHOW_PAGE_UNARCHIVED", () => { + it("sets is_archived=false on the target page", () => { + const state = createInitialState({ + showPages: [ + { id: 1, is_archived: true }, + { id: 2, is_archived: true } + ] + }); + + const result = showPagesListReducer(state, { + type: SHOW_PAGE_UNARCHIVED, + payload: { pageId: 2 } + }); + + expect(result.showPages.find((p) => p.id === 1).is_archived).toBe(true); + expect(result.showPages.find((p) => p.id === 2).is_archived).toBe(false); + }); + }); + + describe("SHOW_PAGE_DELETED", () => { + it("removes the page and decrements totalCount", () => { + const state = createInitialState({ + showPages: [{ id: 1 }, { id: 2 }, { id: 3 }], + totalCount: 3 + }); + + const result = showPagesListReducer(state, { + type: SHOW_PAGE_DELETED, + payload: { pageId: 2 } + }); + + expect(result.showPages.map((p) => p.id)).toStrictEqual([1, 3]); + expect(result.totalCount).toBe(2); + }); + }); + + describe("RESET_SHOW_PAGE_FORM", () => { + it("resets currentShowPage to the empty default", () => { + const state = createInitialState({ + currentShowPage: { id: 99, code: "X", name: "Dirty" } + }); + + const result = showPagesListReducer(state, { + type: RESET_SHOW_PAGE_FORM + }); + + expect(result.currentShowPage).toStrictEqual({ + code: "", + name: "", + sponsorship_types: [], + modules: [] + }); + }); + }); + + describe("RECEIVE_SHOW_PAGE", () => { + const basePageData = { + id: 5, + code: "CODE-5", + name: "Page Five", + apply_to_all_types: false, + sponsorship_types: [{ id: 1 }, { id: 2 }], + modules: [] + }; + + it("sets sponsorship_types from the response when not apply_to_all_types", () => { + const result = showPagesListReducer(initialState, { + type: RECEIVE_SHOW_PAGE, + payload: { response: basePageData } + }); + + expect(result.currentShowPage.sponsorship_types).toStrictEqual([ + { id: 1 }, + { id: 2 } + ]); + }); + + it("sets sponsorship_types to [\"all\"] when apply_to_all_types is true", () => { + const result = showPagesListReducer(initialState, { + type: RECEIVE_SHOW_PAGE, + payload: { + response: { ...basePageData, apply_to_all_types: true } + } + }); + + expect(result.currentShowPage.sponsorship_types).toStrictEqual(["all"]); + }); + + it("converts upload_deadline via epochToMomentTimeZone using summitTZ", () => { + const state = createInitialState({ summitTZ: "America/Argentina" }); + + const result = showPagesListReducer(state, { + type: RECEIVE_SHOW_PAGE, + payload: { + response: { + ...basePageData, + modules: [ + { + id: 1, + kind: PAGES_MODULE_KINDS.INFO, + upload_deadline: 1700000000 + } + ] + } + } + }); + + expect(result.currentShowPage.modules[0].upload_deadline).toBe( + "moment-1700000000-America/Argentina" + ); + }); + + it("falls back to UTC for upload_deadline when summitTZ is null", () => { + const result = showPagesListReducer(initialState, { + type: RECEIVE_SHOW_PAGE, + payload: { + response: { + ...basePageData, + modules: [ + { + id: 1, + kind: PAGES_MODULE_KINDS.INFO, + upload_deadline: 1700000000 + } + ] + } + } + }); + + expect(result.currentShowPage.modules[0].upload_deadline).toBe( + "moment-1700000000-UTC" + ); + }); + + it("does not set upload_deadline when module has none", () => { + const result = showPagesListReducer(initialState, { + type: RECEIVE_SHOW_PAGE, + payload: { + response: { + ...basePageData, + modules: [{ id: 1, kind: PAGES_MODULE_KINDS.INFO }] + } + } + }); + + expect(result.currentShowPage.modules[0].upload_deadline).toBeUndefined(); + }); + + describe("DOCUMENT module handling", () => { + it("wraps file into array and adds file_path/public_url when file is present", () => { + const file = { + id: 99, + storage_key: "s3://bucket/file.pdf", + file_url: "https://cdn.example.com/file.pdf", + name: "file.pdf" + }; + + const result = showPagesListReducer(initialState, { + type: RECEIVE_SHOW_PAGE, + payload: { + response: { + ...basePageData, + modules: [ + { + id: 10, + kind: PAGES_MODULE_KINDS.DOCUMENT, + file + } + ] + } + } + }); + + const mod = result.currentShowPage.modules[0]; + expect(mod.type).toBe(PAGE_MODULES_DOWNLOAD.FILE); + expect(mod.file).toStrictEqual([ + { + ...file, + file_path: file.storage_key, + public_url: file.file_url + } + ]); + }); + + it("sets type to URL when DOCUMENT module has no file", () => { + const result = showPagesListReducer(initialState, { + type: RECEIVE_SHOW_PAGE, + payload: { + response: { + ...basePageData, + modules: [ + { + id: 11, + kind: PAGES_MODULE_KINDS.DOCUMENT, + external_url: "https://example.com/doc" + } + ] + } + } + }); + + const mod = result.currentShowPage.modules[0]; + expect(mod.type).toBe(PAGE_MODULES_DOWNLOAD.URL); + expect(mod.file).toBeUndefined(); + }); + + it("does not set type on non-DOCUMENT modules", () => { + const result = showPagesListReducer(initialState, { + type: RECEIVE_SHOW_PAGE, + payload: { + response: { + ...basePageData, + modules: [ + { id: 12, kind: PAGES_MODULE_KINDS.INFO }, + { id: 13, kind: PAGES_MODULE_KINDS.MEDIA } + ] + } + } + }); + + expect(result.currentShowPage.modules[0].type).toBeUndefined(); + expect(result.currentShowPage.modules[1].type).toBeUndefined(); + }); + }); + }); + + describe("RECEIVE_GLOBAL_SPONSORSHIPS", () => { + const makePayload = (currentPage, data) => ({ + response: { + current_page: currentPage, + last_page: 3, + total: 10, + data + } + }); + + it("replaces items on first page", () => { + const state = createInitialState({ + sponsorships: { + items: [{ id: 1, name: "Old" }], + currentPage: 1, + lastPage: 1, + total: 1 + } + }); + + const result = showPagesListReducer(state, { + type: RECEIVE_GLOBAL_SPONSORSHIPS, + payload: makePayload(1, [{ id: 2, type: { name: "Gold" } }]) + }); + + expect(result.sponsorships.items).toStrictEqual([ + { id: 2, name: "Gold" } + ]); + expect(result.sponsorships.currentPage).toBe(1); + expect(result.sponsorships.lastPage).toBe(3); + expect(result.sponsorships.total).toBe(10); + }); + + it("appends items on subsequent pages", () => { + const state = createInitialState({ + sponsorships: { + items: [{ id: 1, name: "Gold" }], + currentPage: 1, + lastPage: 3, + total: 10 + } + }); + + const result = showPagesListReducer(state, { + type: RECEIVE_GLOBAL_SPONSORSHIPS, + payload: makePayload(2, [{ id: 2, type: { name: "Silver" } }]) + }); + + expect(result.sponsorships.items).toStrictEqual([ + { id: 1, name: "Gold" }, + { id: 2, name: "Silver" } + ]); + }); + }); +}); diff --git a/src/reducers/sponsors/show-pages-list-reducer.js b/src/reducers/sponsors/show-pages-list-reducer.js index dc5e2f7f1..3cf82c7fb 100644 --- a/src/reducers/sponsors/show-pages-list-reducer.js +++ b/src/reducers/sponsors/show-pages-list-reducer.js @@ -25,6 +25,10 @@ import { } from "../../actions/show-pages-actions"; import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; import { RECEIVE_GLOBAL_SPONSORSHIPS } from "../../actions/sponsor-forms-actions"; +import { + PAGE_MODULES_DOWNLOAD, + PAGES_MODULE_KINDS +} from "../../utils/constants"; const DEFAULT_SHOW_PAGE = { code: "", @@ -132,19 +136,39 @@ const showPagesListReducer = (state = DEFAULT_STATE, action) => { ? ["all"] : pageData.sponsorship_types.map((st) => st.id); - const currentShowPage = { - ...pageData, - modules: pageData.modules.map((m) => ({ - ...m, - ...(m.upload_deadline + const modules = pageData.modules.map((module) => { + const tmpModule = { + ...module, + ...(module.upload_deadline ? { upload_deadline: epochToMomentTimeZone( - m.upload_deadline, + 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 currentShowPage = { + ...pageData, + modules, sponsorship_types: sponsorshipTypeIds }; From faeb609381deeea557a6d8fa73b6dc540896d34c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 7 Apr 2026 16:51:41 -0300 Subject: [PATCH 2/5] fix: update page normalization functions to remove unnecesary fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/show-pages-actions.js | 37 +++++++++++++++++++--------- src/actions/sponsor-pages-actions.js | 37 +++++++++++++++++++--------- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/actions/show-pages-actions.js b/src/actions/show-pages-actions.js index 19befc106..e067acc86 100644 --- a/src/actions/show-pages-actions.js +++ b/src/actions/show-pages-actions.js @@ -28,6 +28,8 @@ import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, DEFAULT_PER_PAGE, + PAGE_MODULES_DOWNLOAD, + PAGE_MODULES_MEDIA_TYPES, PAGES_MODULE_KINDS } from "../utils/constants"; import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; @@ -139,19 +141,32 @@ const normalizeShowPage = (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) { + if (module.upload_deadline) { + normalizedModule.upload_deadline = moment + .utc(module.upload_deadline) + .unix(); + } + + if (module.file_type_id) { + normalizedModule.file_type_id = + module.file_type_id?.value || module.file_type_id; + } + + if (module.type === PAGE_MODULES_MEDIA_TYPES.INPUT) { + delete normalizedModule.file_type_id; + delete normalizedModule.max_file_size; + } } - 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; + if (module.kind === PAGES_MODULE_KINDS.DOCUMENT) { + if (module.type === PAGE_MODULES_DOWNLOAD.FILE) { + normalizedModule.file = module.file?.[0] || null; + delete normalizedModule.external_url; + } else { + delete normalizedModule.file; + delete normalizedModule.file_id; + } } delete normalizedModule._tempId; diff --git a/src/actions/sponsor-pages-actions.js b/src/actions/sponsor-pages-actions.js index 4a9d3cd75..b52f68020 100644 --- a/src/actions/sponsor-pages-actions.js +++ b/src/actions/sponsor-pages-actions.js @@ -32,6 +32,8 @@ import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, DEFAULT_PER_PAGE, + PAGE_MODULES_DOWNLOAD, + PAGE_MODULES_MEDIA_TYPES, PAGES_MODULE_KINDS } from "../utils/constants"; @@ -569,19 +571,32 @@ const normalizeSponsorCustomPage = (entity, summitTZ) => { normalizedEntity.modules = entity.modules.map((module) => { const normalizedModule = { ...module }; - if (module.kind === PAGES_MODULE_KINDS.MEDIA && module.upload_deadline) { - normalizedModule.upload_deadline = moment - .tz(module.upload_deadline, summitTZ) - .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.MEDIA) { + if (module.upload_deadline) { + normalizedModule.upload_deadline = moment + .tz(module.upload_deadline, summitTZ) + .unix(); + } + + if (module.file_type_id) { + normalizedModule.file_type_id = + module.file_type_id?.value || module.file_type_id; + } + + if (module.type === PAGE_MODULES_MEDIA_TYPES.INPUT) { + delete normalizedModule.file_type_id; + delete normalizedModule.max_file_size; + } } - if (module.kind === PAGES_MODULE_KINDS.DOCUMENT && module.file) { - normalizedModule.file = module.file[0] || null; + if (module.kind === PAGES_MODULE_KINDS.DOCUMENT) { + if (module.type === PAGE_MODULES_DOWNLOAD.FILE) { + normalizedModule.file = module.file?.[0] || null; + delete normalizedModule.external_url; + } else { + delete normalizedModule.file; + delete normalizedModule.file_id; + } } delete normalizedModule._tempId; From f4a1da1e684316e8d88c015b973f6ad9dc8c76e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Fri, 10 Apr 2026 14:32:01 -0300 Subject: [PATCH 3/5] fix: unify code into single page template utils file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/page-template-actions.js | 45 +---------- src/actions/show-pages-actions.js | 43 +---------- src/actions/sponsor-pages-actions.js | 51 ++----------- .../sponsors/show-pages-list-reducer.js | 39 ++-------- .../page-template-reducer.js | 36 +-------- src/utils/page-template.js | 75 +++++++++++++++++++ 6 files changed, 95 insertions(+), 194 deletions(-) create mode 100644 src/utils/page-template.js diff --git a/src/actions/page-template-actions.js b/src/actions/page-template-actions.js index 3340525b2..09c3493aa 100644 --- a/src/actions/page-template-actions.js +++ b/src/actions/page-template-actions.js @@ -12,7 +12,6 @@ * */ import T from "i18n-react/dist/i18n-react"; -import moment from "moment-timezone"; import { getRequest, putRequest, @@ -28,12 +27,10 @@ import { getAccessTokenSafely } from "../utils/methods"; import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, - DEFAULT_PER_PAGE, - PAGE_MODULES_DOWNLOAD, - PAGE_MODULES_MEDIA_TYPES, - PAGES_MODULE_KINDS + DEFAULT_PER_PAGE } from "../utils/constants"; import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; +import { normalizePageTemplateModules } from "../utils/page-template"; import { GLOBAL_PAGE_CLONED } from "./sponsor-pages-actions"; export const ADD_PAGE_TEMPLATE = "ADD_PAGE_TEMPLATE"; @@ -147,43 +144,7 @@ export const resetPageTemplateForm = () => (dispatch) => { const normalizeEntity = (entity) => { const normalizedEntity = { ...entity }; - - normalizedEntity.modules = entity.modules.map((module) => { - const normalizedModule = { ...module }; - - if (module.kind === PAGES_MODULE_KINDS.MEDIA) { - if (module.upload_deadline) { - normalizedModule.upload_deadline = moment - .utc(module.upload_deadline) - .unix(); - } - - if (module.file_type_id) { - normalizedModule.file_type_id = - module.file_type_id?.value || module.file_type_id; - } - - if (module.type === PAGE_MODULES_MEDIA_TYPES.INPUT) { - delete normalizedModule.file_type_id; - delete normalizedModule.max_file_size; - } - } - - if (module.kind === PAGES_MODULE_KINDS.DOCUMENT) { - if (module.type === PAGE_MODULES_DOWNLOAD.FILE) { - normalizedModule.file = module.file?.[0] || null; - delete normalizedModule.external_url; - } else { - delete normalizedModule.file; - delete normalizedModule.file_id; - } - } - - delete normalizedModule._tempId; - - return normalizedModule; - }); - + normalizedEntity.modules = normalizePageTemplateModules(entity.modules); return normalizedEntity; }; diff --git a/src/actions/show-pages-actions.js b/src/actions/show-pages-actions.js index e067acc86..6d2997854 100644 --- a/src/actions/show-pages-actions.js +++ b/src/actions/show-pages-actions.js @@ -22,17 +22,14 @@ import { stopLoading } from "openstack-uicore-foundation/lib/utils/actions"; import T from "i18n-react/dist/i18n-react"; -import moment from "moment-timezone"; import { escapeFilterValue, getAccessTokenSafely } from "../utils/methods"; import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, - DEFAULT_PER_PAGE, - PAGE_MODULES_DOWNLOAD, - PAGE_MODULES_MEDIA_TYPES, - PAGES_MODULE_KINDS + DEFAULT_PER_PAGE } from "../utils/constants"; import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; +import { normalizePageTemplateModules } from "../utils/page-template"; export const REQUEST_SHOW_PAGES = "REQUEST_SHOW_PAGES"; export const RECEIVE_SHOW_PAGES = "RECEIVE_SHOW_PAGES"; @@ -138,41 +135,7 @@ const normalizeShowPage = (entity) => { delete normalizedEntity.sponsorship_types; } - normalizedEntity.modules = entity.modules.map((module) => { - const normalizedModule = { ...module }; - - if (module.kind === PAGES_MODULE_KINDS.MEDIA) { - if (module.upload_deadline) { - normalizedModule.upload_deadline = moment - .utc(module.upload_deadline) - .unix(); - } - - if (module.file_type_id) { - normalizedModule.file_type_id = - module.file_type_id?.value || module.file_type_id; - } - - if (module.type === PAGE_MODULES_MEDIA_TYPES.INPUT) { - delete normalizedModule.file_type_id; - delete normalizedModule.max_file_size; - } - } - - if (module.kind === PAGES_MODULE_KINDS.DOCUMENT) { - if (module.type === PAGE_MODULES_DOWNLOAD.FILE) { - normalizedModule.file = module.file?.[0] || null; - delete normalizedModule.external_url; - } else { - delete normalizedModule.file; - delete normalizedModule.file_id; - } - } - - delete normalizedModule._tempId; - - return normalizedModule; - }); + normalizedEntity.modules = normalizePageTemplateModules(entity.modules); return normalizedEntity; }; diff --git a/src/actions/sponsor-pages-actions.js b/src/actions/sponsor-pages-actions.js index b52f68020..6fdc4c979 100644 --- a/src/actions/sponsor-pages-actions.js +++ b/src/actions/sponsor-pages-actions.js @@ -22,20 +22,14 @@ 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 -} from "../utils/methods"; +import { getAccessTokenSafely } from "../utils/methods"; import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, - DEFAULT_PER_PAGE, - PAGE_MODULES_DOWNLOAD, - PAGE_MODULES_MEDIA_TYPES, - PAGES_MODULE_KINDS + DEFAULT_PER_PAGE } from "../utils/constants"; +import { normalizePageTemplateModules } from "../utils/page-template"; export const GLOBAL_PAGE_CLONED = "GLOBAL_PAGE_CLONED"; export const RESET_EDIT_PAGE = "RESET_EDIT_PAGE"; @@ -568,41 +562,10 @@ const normalizeSponsorCustomPage = (entity, summitTZ) => { normalizedEntity.allowed_add_ons = entity.allowed_add_ons.map((e) => e.id); } - normalizedEntity.modules = entity.modules.map((module) => { - const normalizedModule = { ...module }; - - if (module.kind === PAGES_MODULE_KINDS.MEDIA) { - if (module.upload_deadline) { - normalizedModule.upload_deadline = moment - .tz(module.upload_deadline, summitTZ) - .unix(); - } - - if (module.file_type_id) { - normalizedModule.file_type_id = - module.file_type_id?.value || module.file_type_id; - } - - if (module.type === PAGE_MODULES_MEDIA_TYPES.INPUT) { - delete normalizedModule.file_type_id; - delete normalizedModule.max_file_size; - } - } - - if (module.kind === PAGES_MODULE_KINDS.DOCUMENT) { - if (module.type === PAGE_MODULES_DOWNLOAD.FILE) { - normalizedModule.file = module.file?.[0] || null; - delete normalizedModule.external_url; - } else { - delete normalizedModule.file; - delete normalizedModule.file_id; - } - } - - delete normalizedModule._tempId; - - return normalizedModule; - }); + normalizedEntity.modules = normalizePageTemplateModules( + entity.modules, + summitTZ + ); return normalizedEntity; }; diff --git a/src/reducers/sponsors/show-pages-list-reducer.js b/src/reducers/sponsors/show-pages-list-reducer.js index 3cf82c7fb..5c68c8328 100644 --- a/src/reducers/sponsors/show-pages-list-reducer.js +++ b/src/reducers/sponsors/show-pages-list-reducer.js @@ -13,7 +13,6 @@ import T from "i18n-react/dist/i18n-react"; import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; -import { epochToMomentTimeZone } from "openstack-uicore-foundation/lib/utils/methods"; import { RECEIVE_SHOW_PAGE, RECEIVE_SHOW_PAGES, @@ -25,10 +24,7 @@ import { } from "../../actions/show-pages-actions"; import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; import { RECEIVE_GLOBAL_SPONSORSHIPS } from "../../actions/sponsor-forms-actions"; -import { - PAGE_MODULES_DOWNLOAD, - PAGES_MODULE_KINDS -} from "../../utils/constants"; +import { denormalizePageModules } from "../../utils/page-template"; const DEFAULT_SHOW_PAGE = { code: "", @@ -136,35 +132,10 @@ const showPagesListReducer = (state = DEFAULT_STATE, action) => { ? ["all"] : pageData.sponsorship_types.map((st) => st.id); - const modules = pageData.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( + pageData.modules, + state.summitTZ || "UTC" + ); const currentShowPage = { ...pageData, diff --git a/src/reducers/sponsors_inventory/page-template-reducer.js b/src/reducers/sponsors_inventory/page-template-reducer.js index 7a80fde9f..3cbada147 100644 --- a/src/reducers/sponsors_inventory/page-template-reducer.js +++ b/src/reducers/sponsors_inventory/page-template-reducer.js @@ -11,7 +11,6 @@ * limitations under the License. * */ -import moment from "moment-timezone"; import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; import { PAGE_TEMPLATE_ADDED, @@ -19,11 +18,7 @@ import { RECEIVE_PAGE_TEMPLATE, RESET_PAGE_TEMPLATE_FORM } from "../../actions/page-template-actions"; -import { - MILLISECONDS_IN_SECOND, - PAGE_MODULES_DOWNLOAD, - PAGES_MODULE_KINDS -} from "../../utils/constants"; +import { denormalizePageModules } from "../../utils/page-template"; export const DEFAULT_ENTITY = { id: 0, @@ -55,34 +50,7 @@ const pageTemplateReducer = (state = DEFAULT_STATE, action) => { case RECEIVE_PAGE_TEMPLATE: { const entity = { ...payload.response }; - entity.modules = entity.modules.map((module) => { - const tmpModule = { - ...module, - ...(module.upload_deadline - ? { - upload_deadline: moment( - module.upload_deadline * MILLISECONDS_IN_SECOND - ) - } - : {}) - }; - - 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; - }); + entity.modules = denormalizePageModules(entity.modules); return { ...state, diff --git a/src/utils/page-template.js b/src/utils/page-template.js new file mode 100644 index 000000000..a779455ec --- /dev/null +++ b/src/utils/page-template.js @@ -0,0 +1,75 @@ +import moment from "moment-timezone"; +import { epochToMomentTimeZone } from "openstack-uicore-foundation/lib/utils/methods"; +import { + MILLISECONDS_IN_SECOND, + PAGE_MODULES_DOWNLOAD, + PAGE_MODULES_MEDIA_TYPES, + PAGES_MODULE_KINDS +} from "./constants"; + +export const denormalizePageModules = (modules, timeZone) => + modules.map((module) => { + const tmpModule = { + ...module, + ...(module.upload_deadline + ? { + upload_deadline: timeZone + ? epochToMomentTimeZone(module.upload_deadline, timeZone) + : moment(module.upload_deadline * MILLISECONDS_IN_SECOND) + } + : {}) + }; + + 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; + }); + +export const normalizePageTemplateModules = (modules, timeZone) => + modules.map((module) => { + const normalizedModule = { ...module }; + + if (module.kind === PAGES_MODULE_KINDS.MEDIA) { + if (module.upload_deadline) { + normalizedModule.upload_deadline = timeZone + ? moment.tz(module.upload_deadline, timeZone).unix() + : moment.utc(module.upload_deadline).unix(); + } + + if (module.file_type_id) { + normalizedModule.file_type_id = + module.file_type_id?.value || module.file_type_id; + } + + if (module.type === PAGE_MODULES_MEDIA_TYPES.INPUT) { + delete normalizedModule.file_type_id; + delete normalizedModule.max_file_size; + } + } + + if (module.kind === PAGES_MODULE_KINDS.DOCUMENT) { + if (module.type === PAGE_MODULES_DOWNLOAD.FILE) { + normalizedModule.file = module.file?.[0] || null; + delete normalizedModule.external_url; + } else { + delete normalizedModule.file; + delete normalizedModule.file_id; + } + } + + delete normalizedModule._tempId; + + return normalizedModule; + }); From 59ab1d734746580a6cf686d009598bc3cb2930d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Fri, 10 Apr 2026 14:50:21 -0300 Subject: [PATCH 4/5] fix: add default values on page template utils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/utils/page-template.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/page-template.js b/src/utils/page-template.js index a779455ec..45ad5f168 100644 --- a/src/utils/page-template.js +++ b/src/utils/page-template.js @@ -7,7 +7,7 @@ import { PAGES_MODULE_KINDS } from "./constants"; -export const denormalizePageModules = (modules, timeZone) => +export const denormalizePageModules = (modules = [], timeZone = null) => modules.map((module) => { const tmpModule = { ...module, @@ -37,7 +37,7 @@ export const denormalizePageModules = (modules, timeZone) => return tmpModule; }); -export const normalizePageTemplateModules = (modules, timeZone) => +export const normalizePageTemplateModules = (modules = [], timeZone = null) => modules.map((module) => { const normalizedModule = { ...module }; From 51c16f1b73ee31b13a9858d842b512ed278ed560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Wed, 22 Apr 2026 14:49:08 -0300 Subject: [PATCH 5/5] fix: adjust test cases, add new test file, restore removed code from rebase 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 | 8 +- .../__tests__/show-pages-list-reducer.test.js | 5 +- src/utils/__tests__/page-template.test.js | 167 ++++++++++++++++++ 3 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 src/utils/__tests__/page-template.test.js diff --git a/src/actions/sponsor-pages-actions.js b/src/actions/sponsor-pages-actions.js index 6fdc4c979..22e7e92d1 100644 --- a/src/actions/sponsor-pages-actions.js +++ b/src/actions/sponsor-pages-actions.js @@ -22,12 +22,16 @@ import { escapeFilterValue } from "openstack-uicore-foundation/lib/utils/actions"; import T from "i18n-react/dist/i18n-react"; -import { getAccessTokenSafely } from "../utils/methods"; +import { + getAccessTokenSafely, + normalizeSelectAllField +} from "../utils/methods"; import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, - DEFAULT_PER_PAGE + DEFAULT_PER_PAGE, + PAGES_MODULE_KINDS } from "../utils/constants"; import { normalizePageTemplateModules } from "../utils/page-template"; diff --git a/src/reducers/sponsors/__tests__/show-pages-list-reducer.test.js b/src/reducers/sponsors/__tests__/show-pages-list-reducer.test.js index ca73b03c0..66845db60 100644 --- a/src/reducers/sponsors/__tests__/show-pages-list-reducer.test.js +++ b/src/reducers/sponsors/__tests__/show-pages-list-reducer.test.js @@ -257,10 +257,7 @@ describe("showPagesListReducer", () => { payload: { response: basePageData } }); - expect(result.currentShowPage.sponsorship_types).toStrictEqual([ - { id: 1 }, - { id: 2 } - ]); + expect(result.currentShowPage.sponsorship_types).toStrictEqual([1, 2]); }); it("sets sponsorship_types to [\"all\"] when apply_to_all_types is true", () => { diff --git a/src/utils/__tests__/page-template.test.js b/src/utils/__tests__/page-template.test.js new file mode 100644 index 000000000..b5e9f2197 --- /dev/null +++ b/src/utils/__tests__/page-template.test.js @@ -0,0 +1,167 @@ +import { normalizePageTemplateModules } from "../page-template"; +import { + PAGE_MODULES_DOWNLOAD, + PAGE_MODULES_MEDIA_TYPES, + PAGES_MODULE_KINDS +} from "../constants"; + +jest.mock("openstack-uicore-foundation/lib/utils/methods", () => ({ + epochToMomentTimeZone: jest.fn((value, tz) => `moment-${value}-${tz}`) +})); + +jest.mock("moment-timezone", () => { + const mockUnix = jest.fn(() => 1700000000); + const mockMoment = { + unix: mockUnix + }; + const moment = jest.fn(() => mockMoment); + moment.tz = jest.fn(() => mockMoment); + moment.utc = jest.fn(() => mockMoment); + return moment; +}); + +describe("normalizePageTemplateModules", () => { + it("should return an empty array when called with no arguments or an empty array", () => { + expect(normalizePageTemplateModules()).toStrictEqual([]); + expect(normalizePageTemplateModules([])).toStrictEqual([]); + }); + + it("should remove _tempId from any module", () => { + const module = { + kind: PAGES_MODULE_KINDS.INFO, + title: "Info", + _tempId: "abc" + }; + const [result] = normalizePageTemplateModules([module]); + expect(result._tempId).toBeUndefined(); + }); + + describe("MEDIA kind — FILE type", () => { + it("should convert upload_deadline to unix using moment.utc when no timeZone provided", () => { + const module = { + kind: PAGES_MODULE_KINDS.MEDIA, + type: PAGE_MODULES_MEDIA_TYPES.FILE, + upload_deadline: "2024-01-15T00:00:00Z", + file_type_id: 3, + max_file_size: 1024 + }; + const [result] = normalizePageTemplateModules([module]); + expect(result.upload_deadline).toBe(1700000000); + }); + + it("should convert upload_deadline to unix using moment.tz when timeZone is provided", () => { + const module = { + kind: PAGES_MODULE_KINDS.MEDIA, + type: PAGE_MODULES_MEDIA_TYPES.FILE, + upload_deadline: "2024-01-15T00:00:00Z", + file_type_id: 3, + max_file_size: 1024 + }; + const [result] = normalizePageTemplateModules( + [module], + "America/New_York" + ); + expect(result.upload_deadline).toBe(1700000000); + }); + + it("should resolve file_type_id from a select option object", () => { + const module = { + kind: PAGES_MODULE_KINDS.MEDIA, + type: PAGE_MODULES_MEDIA_TYPES.FILE, + file_type_id: { value: 7, label: "PDF" } + }; + const [result] = normalizePageTemplateModules([module]); + expect(result.file_type_id).toBe(7); + }); + + it("should keep file_type_id as-is when it is already a primitive", () => { + const module = { + kind: PAGES_MODULE_KINDS.MEDIA, + type: PAGE_MODULES_MEDIA_TYPES.FILE, + file_type_id: 5 + }; + const [result] = normalizePageTemplateModules([module]); + expect(result.file_type_id).toBe(5); + }); + + it("should preserve max_file_size for FILE type", () => { + const module = { + kind: PAGES_MODULE_KINDS.MEDIA, + type: PAGE_MODULES_MEDIA_TYPES.FILE, + max_file_size: 2048 + }; + const [result] = normalizePageTemplateModules([module]); + expect(result.max_file_size).toBe(2048); + }); + }); + + describe("MEDIA kind — INPUT type", () => { + it("should delete file_type_id and max_file_size but still normalize upload_deadline", () => { + const module = { + kind: PAGES_MODULE_KINDS.MEDIA, + type: PAGE_MODULES_MEDIA_TYPES.INPUT, + upload_deadline: "2024-01-15T00:00:00Z", + file_type_id: 3, + max_file_size: 1024 + }; + const [result] = normalizePageTemplateModules([module]); + expect(result.file_type_id).toBeUndefined(); + expect(result.max_file_size).toBeUndefined(); + expect(result.upload_deadline).toBe(1700000000); + }); + + it("should not include upload_deadline when not set", () => { + const module = { + kind: PAGES_MODULE_KINDS.MEDIA, + type: PAGE_MODULES_MEDIA_TYPES.INPUT + }; + const [result] = normalizePageTemplateModules([module]); + expect(result.upload_deadline).toBeUndefined(); + }); + }); + + describe("DOCUMENT kind — FILE type", () => { + it("should extract the first element from the file array and delete external_url", () => { + const fileObj = { + id: 10, + storage_key: "key/file.pdf", + file_url: "https://cdn/file.pdf" + }; + const module = { + kind: PAGES_MODULE_KINDS.DOCUMENT, + type: PAGE_MODULES_DOWNLOAD.FILE, + file: [fileObj], + external_url: "https://example.com" + }; + const [result] = normalizePageTemplateModules([module]); + expect(result.file).toStrictEqual(fileObj); + expect(result.external_url).toBeUndefined(); + }); + + it("should set file to null when the file array is empty", () => { + const module = { + kind: PAGES_MODULE_KINDS.DOCUMENT, + type: PAGE_MODULES_DOWNLOAD.FILE, + file: [] + }; + const [result] = normalizePageTemplateModules([module]); + expect(result.file).toBeNull(); + }); + }); + + describe("DOCUMENT kind — URL type", () => { + it("should delete file and file_id but preserve external_url", () => { + const module = { + kind: PAGES_MODULE_KINDS.DOCUMENT, + type: PAGE_MODULES_DOWNLOAD.URL, + file: [{ id: 1 }], + file_id: 1, + external_url: "https://example.com" + }; + const [result] = normalizePageTemplateModules([module]); + expect(result.file).toBeUndefined(); + expect(result.file_id).toBeUndefined(); + expect(result.external_url).toBe("https://example.com"); + }); + }); +});