diff --git a/ecc/blocks/attendee-management-table/attendee-management-table.js b/ecc/blocks/attendee-management-table/attendee-management-table.js index 768e162b..a35ea821 100644 --- a/ecc/blocks/attendee-management-table/attendee-management-table.js +++ b/ecc/blocks/attendee-management-table/attendee-management-table.js @@ -1,5 +1,5 @@ /* eslint-disable max-len */ -import { getAllEventAttendees, getEvents } from '../../scripts/esp-controller.js'; +import { getAllEventAttendees, getEventsForUser } from '../../scripts/esp-controller.js'; import { LIBS } from '../../scripts/scripts.js'; import { getIcon, @@ -12,7 +12,7 @@ import { } from '../../scripts/utils.js'; import SearchablePicker from '../../components/searchable-picker/searchable-picker.js'; import FilterMenu from '../../components/filter-menu/filter-menu.js'; -import { initProfileLogicTree } from '../../scripts/event-apis.js'; +import { getUser, initProfileLogicTree, userHasAccessToEvent } from '../../scripts/profile.js'; const { createTag } = await import(`${LIBS}/utils/utils.js`); @@ -512,16 +512,6 @@ function buildDashboardTable(props, config) { populateTable(props, config); } -async function getEventsArray() { - const resp = await getEvents(); - - if (resp.error) { - return []; - } - - return resp.events; -} - function renderTableLoadingOverlay(props) { const tableContainer = props.el.querySelector('.dashboard-table-container'); const loadingOverlay = createTag('div', { class: 'loading-overlay' }); @@ -628,7 +618,7 @@ async function buildDashboard(el, config) { createTag('div', { class: 'dashboard-body-container' }, '', { parent: mainContainer }); const uspEventId = new URLSearchParams(window.location.search).get('eventId'); - const events = await getEventsArray(); + const events = await getEventsForUser(); const props = { el, @@ -644,8 +634,13 @@ async function buildDashboard(el, config) { let data = []; if (props.currentEventId) { - const resp = await getAllEventAttendees(props.currentEventId); - if (resp && !resp.error) data = resp; + if (userHasAccessToEvent(await getUser(), props.currentEventId)) { + const resp = await getAllEventAttendees(props.currentEventId); + if (resp && !resp.error) data = resp; + } else { + buildNoAccessScreen(el); + return; + } } props.data = data; @@ -722,7 +717,7 @@ export default async function init(el) { return; } - initProfileLogicTree({ + await initProfileLogicTree('attendee-management-table', { noProfile: () => { signIn(); }, diff --git a/ecc/blocks/ecc-dashboard/ecc-dashboard.js b/ecc/blocks/ecc-dashboard/ecc-dashboard.js index 0224e0c2..833eb3c3 100644 --- a/ecc/blocks/ecc-dashboard/ecc-dashboard.js +++ b/ecc/blocks/ecc-dashboard/ecc-dashboard.js @@ -2,7 +2,7 @@ import { createEvent, deleteEvent, getEventImages, - getEvents, + getEventsForUser, publishEvent, unpublishEvent, } from '../../scripts/esp-controller.js'; @@ -16,8 +16,9 @@ import { getEventServiceEnv, getDevToken, } from '../../scripts/utils.js'; -import { quickFilter } from '../../scripts/event-data-handler.js'; -import { initProfileLogicTree } from '../../scripts/event-apis.js'; + +import { quickFilter } from '../event-creation-form/data-handler.js'; +import { initProfileLogicTree } from '../../scripts/profile.js'; const { createTag } = await import(`${LIBS}/utils/utils.js`); @@ -316,10 +317,10 @@ function initMoreOptions(props, config, eventObj, row) { return; } - const newJson = await getEvents(); - props.data = newJson.events; - props.filteredData = newJson.events; - props.paginatedData = newJson.events; + const newJson = await getEventsForUser(); + props.data = newJson; + props.filteredData = newJson; + props.paginatedData = newJson; const modTimeHeader = props.el.querySelector('th.sortable.modificationTime'); if (modTimeHeader) { props.currentSort = { field: 'modificationTime', el: modTimeHeader }; @@ -361,7 +362,7 @@ function initMoreOptions(props, config, eventObj, row) { return; } - const newJson = await getEvents(); + const newJson = await getEventsForUser(); props.data = newJson.events; props.filteredData = newJson.events; props.paginatedData = newJson.events; @@ -647,16 +648,6 @@ function buildDashboardTable(props, config) { } } -async function getEventsArray() { - const resp = await getEvents(); - - if (resp.error) { - return []; - } - - return resp.events; -} - function buildNoEventScreen(el, config) { el.classList.add('no-events'); @@ -681,7 +672,7 @@ async function buildDashboard(el, config) { currentSort: {}, }; - const data = await getEventsArray(); + const data = await getEventsForUser(); if (!data?.length) { buildNoEventScreen(el, config); } else { @@ -738,7 +729,7 @@ export default async function init(el) { return; } - initProfileLogicTree({ + await initProfileLogicTree('ecc-dashboard', { noProfile: () => { signIn(); }, diff --git a/ecc/blocks/event-creation-form/event-creation-form.js b/ecc/blocks/event-creation-form/event-creation-form.js index 3cb3f620..4fc05d14 100644 --- a/ecc/blocks/event-creation-form/event-creation-form.js +++ b/ecc/blocks/event-creation-form/event-creation-form.js @@ -26,9 +26,9 @@ import ProductSelector from '../../components/product-selector/product-selector. import ProductSelectorGroup from '../../components/product-selector-group/product-selector-group.js'; import PartnerSelector from '../../components/partner-selector/partner-selector.js'; import PartnerSelectorGroup from '../../components/partner-selector-group/partner-selector-group.js'; -import getJoinedData, { getFilteredCachedResponse, hasContentChanged, quickFilter, setPayloadCache, setResponseCache } from '../../scripts/event-data-handler.js'; +import getJoinedData, { getFilteredCachedResponse, hasContentChanged, quickFilter, setPayloadCache, setResponseCache } from './data-handler.js'; import CustomSearch from '../../components/custom-search/custom-search.js'; -import { initProfileLogicTree } from '../../scripts/event-apis.js'; +import { initProfileLogicTree } from '../../scripts/profile.js'; const { createTag } = await import(`${LIBS}/utils/utils.js`); const { decorateButtons } = await import(`${LIBS}/utils/decorate.js`); @@ -1060,7 +1060,7 @@ export default async function init(el) { return; } - initProfileLogicTree({ + initProfileLogicTree('event-creation-form', { noProfile: () => { signIn(); }, diff --git a/ecc/blocks/event-format-component/controller.js b/ecc/blocks/event-format-component/controller.js index 10bb9df5..f14b5bb7 100644 --- a/ecc/blocks/event-format-component/controller.js +++ b/ecc/blocks/event-format-component/controller.js @@ -1,5 +1,5 @@ /* eslint-disable no-unused-vars */ -import { getAllSeries } from '../../scripts/esp-controller.js'; +import { getSeriesForUser } from '../../scripts/esp-controller.js'; import BlockMediator from '../../scripts/deps/block-mediator.min.js'; import { LIBS } from '../../scripts/scripts.js'; import { changeInputValue } from '../../scripts/utils.js'; @@ -76,14 +76,26 @@ async function populateSeriesOptions(props, component) { const seriesSelect = component.querySelector('#series-select-input'); if (!seriesSelect) return; - const { series } = await getAllSeries(); + const series = await getSeriesForUser(); + if (!series) { seriesSelect.pending = false; seriesSelect.disabled = true; return; } - Object.values(series).forEach((val) => { + Object.values(series).filter((s) => { + const hasRequiredVals = s.seriesId && s.seriesName; + const isPublished = s.seriesStatus?.toLowerCase() === 'published'; + + const currentCloud = props.eventDataResp.cloudType || props.payload.cloudType; + const isInCurrentCloud = s.cloudType === currentCloud; + + return hasRequiredVals && isPublished && isInCurrentCloud; + }).forEach((val) => { + if (!val.seriesId || !val.seriesName) return; + if (val.seriesStatus?.toLowerCase() !== 'published') return; + const opt = createTag('sp-menu-item', { value: val.seriesId }, val.seriesName); seriesSelect.append(opt); }); diff --git a/ecc/blocks/event-format-component/event-format-component.js b/ecc/blocks/event-format-component/event-format-component.js index 3cab23a9..15b71d84 100644 --- a/ecc/blocks/event-format-component/event-format-component.js +++ b/ecc/blocks/event-format-component/event-format-component.js @@ -1,4 +1,5 @@ /* eslint-disable max-len */ +import { SUPPORTED_CLOUDS } from '../../constants/constants.js'; import { LIBS } from '../../scripts/scripts.js'; import { generateToolTip } from '../../scripts/utils.js'; @@ -15,10 +16,8 @@ async function decorateCloudTagSelect(column) { // FIXME: cloulds shouldn't be hardcoded // const clouds = await getClouds(); - // const clouds = [{ id: 'CreativeCloud', name: 'Creative Cloud' }, { id: 'DX', name: 'Experience Cloud' }]; - const clouds = [{ id: 'CreativeCloud', name: 'Creative Cloud' }]; - Object.entries(clouds).forEach(([, val]) => { + Object.entries(SUPPORTED_CLOUDS).forEach(([, val]) => { const opt = createTag('sp-menu-item', { value: val.id }, val.name); select.append(opt); }); @@ -69,8 +68,6 @@ export default function init(el) { cols.forEach(async (c, ci) => { if (ci === 0) decorateCloudTagSelect(c); if (ci === 1) decorateSeriesSelect(c); - // if (ci === 2) decorateNewSeriesBtnAndModal(c); - // if (ci === 2) decorateCheckbox(c); }); } diff --git a/ecc/blocks/event-partners-component/controller.js b/ecc/blocks/event-partners-component/controller.js index 308fc14e..e5673da9 100644 --- a/ecc/blocks/event-partners-component/controller.js +++ b/ecc/blocks/event-partners-component/controller.js @@ -7,7 +7,7 @@ import { removeSponsorFromEvent, updateSponsorInEvent, } from '../../scripts/esp-controller.js'; -import { getFilteredCachedResponse } from '../../scripts/event-data-handler.js'; +import { getFilteredCachedResponse } from '../event-creation-form/data-handler.js'; let PARTNERS_SERIES_ID; diff --git a/ecc/scripts/event-data-handler.js b/ecc/blocks/form-handler/data-handler.js similarity index 95% rename from ecc/scripts/event-data-handler.js rename to ecc/blocks/form-handler/data-handler.js index 40dd55c2..72e0be81 100644 --- a/ecc/scripts/event-data-handler.js +++ b/ecc/blocks/form-handler/data-handler.js @@ -4,7 +4,6 @@ let responseCache = {}; let payloadCache = {}; const submissionFilter = [ - // from payload and response 'agenda', 'topics', 'eventType', @@ -57,6 +56,13 @@ export function setPayloadCache(payload) { } export function getFilteredCachedPayload() { + const { topics } = payloadCache; + + if (topics) { + payloadCache.topics = Object.values(topics).reduce((acc, val) => acc.concat(val), []); + } + + console.log('payloadCache', payloadCache); return payloadCache; } diff --git a/ecc/blocks/form-handler/form-handler.js b/ecc/blocks/form-handler/form-handler.js index bee8ab17..ee21092f 100644 --- a/ecc/blocks/form-handler/form-handler.js +++ b/ecc/blocks/form-handler/form-handler.js @@ -26,9 +26,9 @@ import ProductSelector from '../../components/product-selector/product-selector. import ProductSelectorGroup from '../../components/product-selector-group/product-selector-group.js'; import PartnerSelector from '../../components/partner-selector/partner-selector.js'; import PartnerSelectorGroup from '../../components/partner-selector-group/partner-selector-group.js'; -import getJoinedData, { getFilteredCachedResponse, hasContentChanged, quickFilter, setPayloadCache, setResponseCache } from '../../scripts/event-data-handler.js'; +import getJoinedData, { getFilteredCachedResponse, hasContentChanged, quickFilter, setPayloadCache, setResponseCache } from './data-handler.js'; +import { getUser, initProfileLogicTree, userHasAccessToEvent } from '../../scripts/profile.js'; import CustomSearch from '../../components/custom-search/custom-search.js'; -import { initProfileLogicTree } from '../../scripts/event-apis.js'; const { createTag } = await import(`${LIBS}/utils/utils.js`); const { decorateButtons } = await import(`${LIBS}/utils/decorate.js`); @@ -233,22 +233,28 @@ async function loadEventData(props) { const eventId = urlParams.get('eventId'); if (eventId) { - setTimeout(() => { - if (!props.eventDataResp.eventId) { - const toastArea = props.el.querySelector('.toast-area'); - if (!toastArea) return; - - const toast = createTag('sp-toast', { open: true, timeout: 10000 }, 'Event data is taking longer than usual to load. Please check if the Adobe corp. VPN is connected or if the eventId URL Param is valid.', { parent: toastArea }); - toast.addEventListener('close', () => { - toast.remove(); - }); - } - }, 5000); + const user = await getUser(); + if (userHasAccessToEvent(user, eventId)) { + setTimeout(() => { + if (!props.eventDataResp.eventId) { + const toastArea = props.el.querySelector('.toast-area'); + if (!toastArea) return; + + const toast = createTag('sp-toast', { open: true, timeout: 10000 }, 'Event data is taking longer than usual to load. Please check if the Adobe corp. VPN is connected or if the eventId URL Param is valid.', { parent: toastArea }); + toast.addEventListener('close', () => { + toast.remove(); + }); + } + }, 5000); - props.el.classList.add('disabled'); - const eventData = await getEvent(eventId); - props.eventDataResp = { ...props.eventDataResp, ...eventData }; - props.el.classList.remove('disabled'); + props.el.classList.add('disabled'); + const eventData = await getEvent(eventId); + props.eventDataResp = { ...props.eventDataResp, ...eventData }; + props.el.classList.remove('disabled'); + } else { + buildNoAccessScreen(props.el); + props.el.classList.remove('loading'); + } } } @@ -1056,7 +1062,7 @@ export default async function init(el) { return; } - initProfileLogicTree({ + await initProfileLogicTree('event-creation-form', { noProfile: () => { signIn(); }, diff --git a/ecc/blocks/img-upload-component/controller.js b/ecc/blocks/img-upload-component/controller.js index 07ebf771..95060dd3 100644 --- a/ecc/blocks/img-upload-component/controller.js +++ b/ecc/blocks/img-upload-component/controller.js @@ -1,7 +1,7 @@ /* eslint-disable no-unused-vars */ import { deleteImage, getEventImages, uploadImage } from '../../scripts/esp-controller.js'; import { LIBS } from '../../scripts/scripts.js'; -import { getFilteredCachedResponse } from '../../scripts/event-data-handler.js'; +import { getFilteredCachedResponse } from '../event-creation-form/data-handler.js'; const { createTag } = await import(`${LIBS}/utils/utils.js`); diff --git a/ecc/blocks/profile-component/controller.js b/ecc/blocks/profile-component/controller.js index 30770932..237f3fe9 100644 --- a/ecc/blocks/profile-component/controller.js +++ b/ecc/blocks/profile-component/controller.js @@ -6,7 +6,7 @@ import { removeSpeakerFromEvent, getEventSpeaker, } from '../../scripts/esp-controller.js'; -import { getFilteredCachedResponse } from '../../scripts/event-data-handler.js'; +import { getFilteredCachedResponse } from '../event-creation-form/data-handler.js'; export async function onSubmit(component, props) { if (component.closest('.fragment')?.classList.contains('hidden')) return; diff --git a/ecc/blocks/series-creation-form/series-creation-form.js b/ecc/blocks/series-creation-form/series-creation-form.js index ef0a1b7d..c41fa02a 100644 --- a/ecc/blocks/series-creation-form/series-creation-form.js +++ b/ecc/blocks/series-creation-form/series-creation-form.js @@ -15,7 +15,7 @@ import { getSeriesById, } from '../../scripts/esp-controller.js'; import getJoinedData, { getFilteredCachedResponse, quickFilter, setPayloadCache, setResponseCache } from './data-handler.js'; -import { initProfileLogicTree } from '../../scripts/event-apis.js'; +import { initProfileLogicTree } from '../../scripts/profile.js'; const { createTag } = await import(`${LIBS}/utils/utils.js`); const { decorateButtons } = await import(`${LIBS}/utils/decorate.js`); @@ -815,7 +815,7 @@ export default async function init(el) { return; } - initProfileLogicTree({ + initProfileLogicTree('series-creation-form', { noProfile: () => { signIn(); }, diff --git a/ecc/blocks/series-dashboard/series-dashboard.js b/ecc/blocks/series-dashboard/series-dashboard.js index aab088da..55bfe781 100644 --- a/ecc/blocks/series-dashboard/series-dashboard.js +++ b/ecc/blocks/series-dashboard/series-dashboard.js @@ -15,7 +15,7 @@ import { getEventServiceEnv, getDevToken, } from '../../scripts/utils.js'; -import { initProfileLogicTree } from '../../scripts/event-apis.js'; +import { initProfileLogicTree } from '../../scripts/profile.js'; import { quickFilter } from '../series-creation-form/data-handler.js'; const { createTag } = await import(`${LIBS}/utils/utils.js`); @@ -606,7 +606,7 @@ function buildLoadingScreen(el) { el.classList.add('loading'); const loadingScreen = createTag('sp-theme', { color: 'light', scale: 'medium', class: 'loading-screen' }); createTag('sp-progress-circle', { size: 'l', indeterminate: true }, '', { parent: loadingScreen }); - createTag('sp-field-label', {}, 'Loading Series dashboard...', { parent: loadingScreen }); + createTag('sp-field-label', {}, 'Loading event series dashboard...', { parent: loadingScreen }); el.prepend(loadingScreen); } @@ -633,7 +633,7 @@ export default async function init(el) { return; } - initProfileLogicTree({ + initProfileLogicTree('series-dashboard', { noProfile: () => { signIn(); }, diff --git a/ecc/blocks/series-details-component/series-details-component.js b/ecc/blocks/series-details-component/series-details-component.js index b17b6ca7..68312b56 100644 --- a/ecc/blocks/series-details-component/series-details-component.js +++ b/ecc/blocks/series-details-component/series-details-component.js @@ -1,3 +1,4 @@ +import { SUPPORTED_CLOUDS } from '../../constants/constants.js'; import { LIBS } from '../../scripts/scripts.js'; import { generateToolTip, @@ -19,9 +20,8 @@ async function decorateCloudTagSelect(column) { // FIXME: cloulds shouldn't be hardcoded // const clouds = await getClouds(); - const clouds = [{ id: 'CreativeCloud', name: 'Creative Cloud' }, { id: 'DX', name: 'Experience Cloud' }]; - Object.entries(clouds).forEach(([, val]) => { + Object.entries(SUPPORTED_CLOUDS).forEach(([, val]) => { const opt = createTag('sp-menu-item', { value: val.id }, val.name); select.append(opt); }); diff --git a/ecc/constants/constants.js b/ecc/constants/constants.js index 939a98e8..fabd3d92 100644 --- a/ecc/constants/constants.js +++ b/ecc/constants/constants.js @@ -1,2 +1,3 @@ export const LINK_REGEX = '^https:\\/\\/[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,63}(:[0-9]{1,5})?(\\/.*)?$'; export const ALLOWED_ACCOUNT_TYPES = ['type3', 'type2e']; +export const SUPPORTED_CLOUDS = [{ id: 'CreativeCloud', name: 'Creative Cloud' }, { id: 'DX', name: 'Experience Cloud' }]; diff --git a/ecc/samples/sample-form/sample-form.js b/ecc/samples/sample-form/sample-form.js index ba838a0c..206e5767 100644 --- a/ecc/samples/sample-form/sample-form.js +++ b/ecc/samples/sample-form/sample-form.js @@ -14,7 +14,7 @@ import { publishSeries, } from '../../scripts/esp-controller.js'; import getJoinedData, { getFilteredCachedResponse, quickFilter, setPayloadCache, setResponseCache } from './data-handler.js'; -import { initProfileLogicTree } from '../../scripts/event-apis.js'; +import { initProfileLogicTree } from '../../scripts/profile.js'; const { createTag } = await import(`${LIBS}/utils/utils.js`); const { decorateButtons } = await import(`${LIBS}/utils/decorate.js`); @@ -220,7 +220,7 @@ async function loadData(props) { // fetch data to prefill the form props.el.classList.add('disabled'); - // const data = await getSeries(id); + // const data = await get(id); const data = {}; props.response = { ...props.response, ...data }; props.el.classList.remove('disabled'); @@ -810,7 +810,7 @@ export default async function init(el) { return; } - initProfileLogicTree({ + initProfileLogicTree('sample-form', { noProfile: () => { signIn(); }, diff --git a/ecc/scripts/esp-controller.js b/ecc/scripts/esp-controller.js index bdd35346..2bb00416 100644 --- a/ecc/scripts/esp-controller.js +++ b/ecc/scripts/esp-controller.js @@ -1,5 +1,6 @@ import { LIBS } from './scripts.js'; import { getDevToken, getEventServiceEnv, getSecret } from './utils.js'; +import { getUser, userHasAccessToBU, userHasAccessToEvent, userHasAccessToSeries } from './profile.js'; const API_CONFIG = { esl: { @@ -711,6 +712,36 @@ export async function getEvents() { } } +export async function getEventsForUser() { + const user = await getUser(); + + if (!user) return []; + + const resp = await getEvents(); + if (!resp.error) { + const { role } = user; + + if (role === 'admin') return resp.events; + + if (role === 'manager') return resp.events.filter((e) => userHasAccessToBU(user, e.cloudType)); + + if (role === 'creator') { + return resp.events + .filter((e) => userHasAccessToBU(user, e.cloudType)) + .filter((e) => userHasAccessToSeries(user, e.seriesId)); + } + + if (role === 'editor') { + return resp.events + .filter((e) => userHasAccessToBU(user, e.cloudType)) + .filter((e) => userHasAccessToSeries(user, e.seriesId)) + .filter((e) => userHasAccessToEvent(user, e.eventId)); + } + } + + return []; +} + export async function getEvent(eventId) { const { host } = API_CONFIG.esp[getEventServiceEnv()]; const options = await constructRequestOptions('GET'); @@ -913,6 +944,28 @@ export async function archiveSeries(seriesId, seriesData) { } } +export async function getSeriesForUser() { + const user = await getUser(); + + if (!user) return []; + + const { series } = await getAllSeries(); + + if (series) { + const { role } = user; + + if (role === 'admin') return series; + if (role === 'manager') return series.filter((s) => userHasAccessToBU(user, s.cloudType)); + if (role === 'creator' || role === 'editor') { + return series + .filter((s) => userHasAccessToBU(user, s.cloudType)) + .filter((s) => userHasAccessToSeries(user, s.seriesId)); + } + } + + return []; +} + export async function deleteSeries(seriesId) { const { host } = API_CONFIG.esp[getEventServiceEnv()]; const options = await constructRequestOptions('DELETE'); diff --git a/ecc/scripts/event-apis.js b/ecc/scripts/event-apis.js deleted file mode 100644 index 5bc41b3a..00000000 --- a/ecc/scripts/event-apis.js +++ /dev/null @@ -1,98 +0,0 @@ -import BlockMediator from './deps/block-mediator.min.js'; -import { ALLOWED_ACCOUNT_TYPES } from '../constants/constants.js'; - -export async function getProfile() { - const { feds, adobeProfile, fedsConfig, adobeIMS } = window; - - const getUserProfile = () => { - if (fedsConfig?.universalNav) { - return feds?.services?.universalnav?.interface?.adobeProfile?.getUserProfile() - || adobeProfile?.getUserProfile(); - } - - return ( - feds?.services?.profile?.interface?.adobeProfile?.getUserProfile() - || adobeProfile?.getUserProfile() - || adobeIMS?.getProfile() - ); - }; - - const profile = await getUserProfile(); - - if (profile) return profile; - - window.lana?.log('Failed to get user profile data.'); - return {}; -} - -export async function captureProfile() { - try { - const profile = await getProfile(); - BlockMediator.set('imsProfile', profile); - } catch { - if (window.adobeIMS) { - BlockMediator.set('imsProfile', { noProfile: true }); - } - } -} - -export function lazyCaptureProfile() { - let attempCounter = 0; - const profileRetryer = setInterval(async () => { - if (!window.adobeIMS) { - attempCounter += 1; - return; - } - - if (attempCounter >= 10) { - clearInterval(profileRetryer); - } - - try { - const profile = await getProfile(); - BlockMediator.set('imsProfile', profile); - clearInterval(profileRetryer); - } catch { - if (window.adobeIMS) { - clearInterval(profileRetryer); - BlockMediator.set('imsProfile', { noProfile: true }); - } - - attempCounter += 1; - } - }, 1000); -} - -export function initProfileLogicTree(callbacks) { - const { noProfile, noAccessProfile, validProfile } = callbacks; - - const profile = BlockMediator.get('imsProfile'); - - if (profile) { - if (profile.noProfile) { - noProfile(); - } else if (!ALLOWED_ACCOUNT_TYPES.includes(profile.account_type)) { - noAccessProfile(); - } else { - validProfile(profile); - } - - return; - } - - if (!profile) { - const unsubscribe = BlockMediator.subscribe('imsProfile', ({ newValue }) => { - if (newValue) { - if (newValue.noProfile) { - noProfile(); - } else if (!ALLOWED_ACCOUNT_TYPES.includes(newValue.account_type)) { - noAccessProfile(); - } else { - validProfile(newValue); - } - } - - unsubscribe(); - }); - } -} diff --git a/ecc/scripts/profile.js b/ecc/scripts/profile.js new file mode 100644 index 00000000..3bcd1d39 --- /dev/null +++ b/ecc/scripts/profile.js @@ -0,0 +1,217 @@ +import BlockMediator from './deps/block-mediator.min.js'; + +let usersCache = []; + +export async function getProfile() { + const { feds, adobeProfile, fedsConfig, adobeIMS } = window; + + const getUserProfile = () => { + if (fedsConfig?.universalNav) { + return feds?.services?.universalnav?.interface?.adobeProfile?.getUserProfile() + || adobeProfile?.getUserProfile(); + } + + return ( + feds?.services?.profile?.interface?.adobeProfile?.getUserProfile() + || adobeProfile?.getUserProfile() + || adobeIMS?.getProfile() + ); + }; + + const profile = await getUserProfile(); + + if (profile) return profile; + + window.lana?.log('Failed to get user profile data.'); + return {}; +} + +export async function captureProfile() { + try { + const profile = await getProfile(); + BlockMediator.set('imsProfile', profile); + } catch { + if (window.adobeIMS) { + BlockMediator.set('imsProfile', { noProfile: true }); + } + } +} + +export function lazyCaptureProfile() { + let attempCounter = 0; + const profileRetryer = setInterval(async () => { + if (!window.adobeIMS) { + attempCounter += 1; + return; + } + + if (attempCounter >= 10) { + clearInterval(profileRetryer); + } + + try { + const profile = await getProfile(); + BlockMediator.set('imsProfile', profile); + clearInterval(profileRetryer); + } catch { + if (window.adobeIMS) { + clearInterval(profileRetryer); + BlockMediator.set('imsProfile', { noProfile: true }); + } + + attempCounter += 1; + } + }, 1000); +} + +export async function getUser() { + const profile = BlockMediator.get('imsProfile'); + + if (!profile || profile.noProfile) { + const devToken = sessionStorage.getItem('devToken'); + if (devToken && window.location.hostname === 'localhost') { + return { + role: 'admin', + email: 'admin@adobe.com', + 'business-units': 'all', + series: 'all', + events: 'all', + }; + } + + return null; + } + + const { email } = profile; + + if (!email) return null; + + if (usersCache.length === 0) { + const resp = await fetch('/ecc/system/users.json') + .then((r) => r) + .catch((e) => window.lana?.log(`Failed to fetch Google Places API key: ${e}`)); + + if (!resp.ok) return null; + + const json = await resp.json(); + usersCache = json.data; + } + + const user = usersCache.find((s) => s.email.toLowerCase() === email.toLowerCase()); + return user; +} + +export function userHasAccessToBU(user, bu) { + if (!user) return false; + + const userBU = user['business-units']; + + if (!userBU) return false; + + if (userBU.toLowerCase() === 'all') return true; + + const businessUnits = userBU.split(',').map((b) => b.trim()); + return businessUnits.length === 0 || businessUnits.includes(bu); +} + +export function userHasAccessToSeries(user, seriesId) { + if (!user) return false; + + const userSeries = user.series; + + if (!userSeries) return false; + + if (userSeries.toLowerCase() === 'all') return true; + + const series = userSeries.split(',').map((b) => b.trim()); + return series.length === 0 || series.includes(seriesId); +} + +export function userHasAccessToEvent(user, eventId) { + if (!user) return false; + + const userEvents = user.events; + + if (!userEvents) return false; + + if (userEvents.toLowerCase() === 'all') return true; + + const events = userEvents.split(',').map((b) => b.trim()); + return events.length === 0 || events.includes(eventId); +} + +export function userHasAccessToView(user, blockName) { + const { role } = user; + const managerAccess = [ + 'ecc-dashboard', + 'event-creation-form', + 'series-dashboard', + 'series-creation-form', + ]; + + const creatorAccess = [ + 'ecc-dashboard', + 'event-creation-form', + ]; + + const editorAccess = [ + 'ecc-dashboard', + 'event-creation-form', + ]; + + if (!role) return false; + + if (role === 'admin') return true; + + if (role === 'manager') { + return managerAccess.includes(blockName); + } + + if (role === 'creator') { + return creatorAccess.includes(blockName); + } + + if (role === 'editor') { + return editorAccess.includes(blockName); + } + + return false; +} + +export async function initProfileLogicTree(blockName, callbacks) { + const { noProfile, noAccessProfile, validProfile } = callbacks; + + const profile = BlockMediator.get('imsProfile'); + let user; + + if (profile) { + user = await getUser(); + if (profile.noProfile) { + noProfile(); + } else if (!user || !userHasAccessToView(user, blockName)) { + noAccessProfile(); + } else { + validProfile(profile); + } + + return; + } + + if (!profile) { + const unsubscribe = BlockMediator.subscribe('imsProfile', ({ newValue }) => { + getUser().then((u) => { + if (newValue) { + if (newValue.noProfile) { + noProfile(); + } else if (!u) { + noAccessProfile(); + } else { + validProfile(newValue); + } + } + + unsubscribe(); + }); + }); + } +} diff --git a/ecc/scripts/scripts.js b/ecc/scripts/scripts.js index a6a67c57..4478b4ca 100644 --- a/ecc/scripts/scripts.js +++ b/ecc/scripts/scripts.js @@ -10,7 +10,7 @@ * governing permissions and limitations under the License. */ -import { lazyCaptureProfile } from './event-apis.js'; +import { lazyCaptureProfile } from './profile.js'; function convertEccIcon(n) { const createSVGIcon = (iconName) => { diff --git a/ecc/scripts/utils.js b/ecc/scripts/utils.js index f4aaf4cd..3c8d15b7 100644 --- a/ecc/scripts/utils.js +++ b/ecc/scripts/utils.js @@ -146,10 +146,11 @@ export function buildNoAccessScreen(el) { const h1 = createTag('h1', {}, 'You do not have sufficient access to view.'); const area = createTag('div', { class: 'no-access-area' }); - const noAccessDescription = createTag('p', {}, 'An Adobe corporate account is required to access this feature.'); + const noAccessDescription = createTag('p', {}, 'If you have another authorized account, please sign in with that account to access this page.'); + const requestAccessButton = createTag('a', { class: 'con-button primary' }, 'Request Access'); el.append(h1, area); - area.append(getIcon('browser-access-forbidden-lg'), noAccessDescription); + area.append(getIcon('browser-access-forbidden-lg'), noAccessDescription, requestAccessButton); } export function querySelectorAllDeep(selector, root = document) { diff --git a/package.json b/package.json index 4cd69260..8574cda9 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "test:watch": "npm test -- --watch", "lint": "npm run lint:js && npm run lint:css", "lint:js": "eslint .", - "lint:css": "stylelint 'blocks/**/*.css' 'styles/*.css'" + "lint:css": "stylelint 'ecc/blocks/**/*.css' 'ecc/styles/*.css'" }, "repository": { "type": "git",