From 0840f078ab983e91ccb66ed38c846801d46a1d19 Mon Sep 17 00:00:00 2001 From: Tim Yao <31641325+tim-yao@users.noreply.github.com> Date: Thu, 22 Aug 2019 12:15:20 +1000 Subject: [PATCH] [SDPA-3045] Added request id in all route requests (#500) * Added request id in all route requests --- examples/vic-gov-au/store/index.js | 4 +- packages/ripple-nuxt-tide/lib/core/logger.js | 4 +- .../ripple-nuxt-tide/lib/core/middleware.js | 151 +++++++++--------- .../ripple-nuxt-tide/lib/core/tide-helper.js | 7 +- packages/ripple-nuxt-tide/lib/core/tide.js | 74 +++++---- packages/ripple-nuxt-tide/lib/module.js | 12 +- .../lib/server-middleware/request-id.js | 14 ++ .../{logger.js => request-log.js} | 9 +- .../ripple-nuxt-tide/lib/templates/plugin.js | 13 +- .../lib/templates/request-id.js | 19 +++ .../modules/publication/tide.middleware.js | 56 +++---- .../ripple-nuxt-tide/modules/site/plugin.js | 6 +- packages/ripple-nuxt-tide/package.json | 1 + yarn.lock | 4 + 14 files changed, 222 insertions(+), 152 deletions(-) create mode 100644 packages/ripple-nuxt-tide/lib/server-middleware/request-id.js rename packages/ripple-nuxt-tide/lib/server-middleware/{logger.js => request-log.js} (56%) create mode 100644 packages/ripple-nuxt-tide/lib/templates/request-id.js diff --git a/examples/vic-gov-au/store/index.js b/examples/vic-gov-au/store/index.js index 6a4bd7b73..671037083 100644 --- a/examples/vic-gov-au/store/index.js +++ b/examples/vic-gov-au/store/index.js @@ -1,9 +1,9 @@ import { logger } from '@dpc-sdp/ripple-nuxt-tide/lib/core' export const actions = { - async nuxtServerInit ({ dispatch }) { + async nuxtServerInit ({ dispatch }, { req }) { try { - await dispatch('tide/init') + await dispatch('tide/init', { requestId: req.requestId }) } catch (error) { if (process.server) { logger.error('Tide API server has an error.', { error, label: 'App' }) diff --git a/packages/ripple-nuxt-tide/lib/core/logger.js b/packages/ripple-nuxt-tide/lib/core/logger.js index 16219b1e4..8d2c466a6 100644 --- a/packages/ripple-nuxt-tide/lib/core/logger.js +++ b/packages/ripple-nuxt-tide/lib/core/logger.js @@ -28,7 +28,7 @@ if (!process.client) { // Format for our console output. const printFormat = format.printf(info => { - const { timestamp, message, level, label, error } = info + const { timestamp, message, level, label, error, ...rest } = info const printLabel = label ? `[${label}] ` : ' ' let log = `${timestamp} ${printLabel}${level} ${message}` // Only if there is an error @@ -36,6 +36,8 @@ const printFormat = format.printf(info => { if (error) { log = error.stack ? `${log}\n${error.stack}` : log } + + log += ' ' + JSON.stringify(rest) return log }) diff --git a/packages/ripple-nuxt-tide/lib/core/middleware.js b/packages/ripple-nuxt-tide/lib/core/middleware.js index daa492d6b..8445927e5 100644 --- a/packages/ripple-nuxt-tide/lib/core/middleware.js +++ b/packages/ripple-nuxt-tide/lib/core/middleware.js @@ -4,9 +4,10 @@ import { isPreviewPath } from '../../modules/authenticated-content/lib/preview' import logger from './logger' // Fetch page data from Tide API by current path -export default async function (context, results) { - results.tidePage = null - results.tideErrorType = null +export default async function (context, pageData) { + pageData.tidePage = null + pageData.tideErrorType = null + // TODO: refactor below page process logic. // Currently we just put logic here for a quick work, // review them and move them into tide modules if possible. @@ -37,6 +38,8 @@ export default async function (context, results) { } } + const headersConfig = { authToken, requestId: context.route.requestId } + try { let response = null @@ -46,9 +49,9 @@ export default async function (context, results) { } const { 2: type, 3: id, 4: rev } = context.route.path.split('/') const section = context.route.query.section ? context.route.query.section : null - response = await context.app.$tide.getPreviewPage(type, id, rev, section, tideParams, authToken) + response = await context.app.$tide.getPreviewPage(type, id, rev, section, tideParams, headersConfig) } else { - response = await context.app.$tide.getPageByPath(context.route.path, tideParams, authToken) + response = await context.app.$tide.getPageByPath(context.route.path, tideParams, headersConfig) } // If redirect required. @@ -70,16 +73,16 @@ export default async function (context, results) { default: response.appPageTitle = response.title } - results.tidePage = response + pageData.tidePage = response } catch (error) { - results.tidePage = false + pageData.tidePage = false switch (context.app.tideResErrCode) { case 404: - results.tideErrorType = '404' + pageData.tideErrorType = '404' break default: - results.tideErrorType = 'other' + pageData.tideErrorType = 'other' if (process.server) { logger.error('Failed to get the page data.', { error }) } @@ -88,9 +91,9 @@ export default async function (context, results) { context.store.dispatch('tide/setCurrentUrl', context.route.fullPath) - if (results.tidePage) { + if (pageData.tidePage) { // Allow custom class on page, custom middleware can extend this class for custom styling. - results.tidePage.class = [] + pageData.tidePage.class = [] // Add page classes based on tide page path let pageClass if (context.route.path === '/') { @@ -98,53 +101,53 @@ export default async function (context, results) { } else { pageClass = pathToClass(context.route.path) } - results.tidePage.class.push(`tide-page--${pageClass}`) + pageData.tidePage.class.push(`tide-page--${pageClass}`) // Preprocess data let asyncTasks = [] const addComponentFromPromise = (promise, name) => { const addMapping = promise.then(res => { - results.tidePage[name] = res + pageData.tidePage[name] = res }) asyncTasks.push(addMapping) } // Sidebar logic // TODO: Update below check list for more sidebar items - if (results.tidePage.field_show_related_content || - results.tidePage.field_show_social_sharing || - results.tidePage.field_show_whats_next || - results.tidePage.field_show_site_section_nav || - results.tidePage.field_show_publication_nav || - results.tidePage.field_landing_page_show_contact) { + if (pageData.tidePage.field_show_related_content || + pageData.tidePage.field_show_social_sharing || + pageData.tidePage.field_show_whats_next || + pageData.tidePage.field_show_site_section_nav || + pageData.tidePage.field_show_publication_nav || + pageData.tidePage.field_landing_page_show_contact) { sidebar = true } // Head logic - results.tidePage.appMetatag = metatagConverter(results.tidePage.metatag_normalized) + pageData.tidePage.appMetatag = metatagConverter(pageData.tidePage.metatag_normalized) try { // Get dynamic components for landing page Header - if (results.tidePage.field_landing_page_header) { - const getMapping = mapping.get(results.tidePage.field_landing_page_header, 'landingPageHeader') + if (pageData.tidePage.field_landing_page_header) { + const getMapping = mapping.get(pageData.tidePage.field_landing_page_header, 'landingPageHeader') addComponentFromPromise(getMapping, 'appHeaderComponents') } // Get dynamic components for landing page - if (results.tidePage.field_landing_page_component) { - const getMapping = mapping.get(results.tidePage.field_landing_page_component, 'landingPageComponents') + if (pageData.tidePage.field_landing_page_component) { + const getMapping = mapping.get(pageData.tidePage.field_landing_page_component, 'landingPageComponents') addComponentFromPromise(getMapping, 'appDComponents') } - if (!results.tidePage.sidebarComponents || !Array.isArray(results.tidePage.sidebarComponents)) { - results.tidePage.sidebarComponents = [] + if (!pageData.tidePage.sidebarComponents || !Array.isArray(pageData.tidePage.sidebarComponents)) { + pageData.tidePage.sidebarComponents = [] } // TODO : Move this to a separate middleware if (sidebar) { // Related content - if (results.tidePage.field_show_related_content) { - const links = results.tidePage.field_related_links + if (pageData.tidePage.field_show_related_content) { + const links = pageData.tidePage.field_related_links .filter(link => link.field_paragraph_link && link.field_paragraph_link.title) .map(link => { return { @@ -154,7 +157,7 @@ export default async function (context, results) { }) if (links && links.length > 0) { - results.tidePage.sidebarComponents.push({ + pageData.tidePage.sidebarComponents.push({ name: 'rpl-related-links', order: 101, data: { @@ -165,8 +168,8 @@ export default async function (context, results) { } } // Whats next - if (results.tidePage.field_show_whats_next) { - const links = results.tidePage.field_whats_next + if (pageData.tidePage.field_show_whats_next) { + const links = pageData.tidePage.field_whats_next .filter(link => link.field_paragraph_link && link.field_paragraph_link.title) .map(link => { return { @@ -178,7 +181,7 @@ export default async function (context, results) { }) if (links) { - results.tidePage.sidebarComponents.push({ + pageData.tidePage.sidebarComponents.push({ name: 'rpl-whats-next', order: 103, data: { @@ -189,8 +192,8 @@ export default async function (context, results) { } } // site section nav - if (results.tidePage.section) { - const addSectionNavMenu = await context.app.$tide.getSiteData(results.tidePage.section).then(async siteData => { + if (pageData.tidePage.section) { + const addSectionNavMenu = await context.app.$tide.getSiteData(headersConfig, pageData.tidePage.section).then(async siteData => { // save alerts if site section has them if (context.app.$tide.isModuleEnabled('alert')) { if (siteData.site_alerts && siteData.site_alerts.length > 0) { @@ -200,13 +203,13 @@ export default async function (context, results) { // Section navigation component will only use the main menu. return siteData.hierarchicalMenus.menuMain }) - if (results.tidePage.field_show_site_section_nav && addSectionNavMenu && results.tidePage.field_landing_page_nav_title) { - results.tidePage.sidebarComponents.push({ + if (pageData.tidePage.field_show_site_section_nav && addSectionNavMenu && pageData.tidePage.field_landing_page_nav_title) { + pageData.tidePage.sidebarComponents.push({ name: 'rpl-site-section-navigation', order: 100, data: { menu: addSectionNavMenu, - title: results.tidePage.field_landing_page_nav_title, + title: pageData.tidePage.field_landing_page_nav_title, activeLink: context.route.path } }) @@ -214,20 +217,20 @@ export default async function (context, results) { } // contact us - if (results.tidePage.field_landing_page_show_contact && results.tidePage.field_landing_page_contact) { - results.tidePage.appContact = await mapping.get(results.tidePage.field_landing_page_contact) - if (results.tidePage.appContact) { - results.tidePage.sidebarComponents.push({ + if (pageData.tidePage.field_landing_page_show_contact && pageData.tidePage.field_landing_page_contact) { + pageData.tidePage.appContact = await mapping.get(pageData.tidePage.field_landing_page_contact) + if (pageData.tidePage.appContact) { + pageData.tidePage.sidebarComponents.push({ name: 'rpl-contact', order: 104, - data: results.tidePage.appContact.data + data: pageData.tidePage.appContact.data }) } } // social sharing - if (results.tidePage.field_show_social_sharing) { - results.tidePage.sidebarComponents.push({ + if (pageData.tidePage.field_show_social_sharing) { + pageData.tidePage.sidebarComponents.push({ name: 'rpl-share-this', order: 105, data: { @@ -247,54 +250,54 @@ export default async function (context, results) { // Hero banner // Hero banner background image let heroBgImage = '' - if (results.tidePage.field_landing_page_hero_image && results.tidePage.field_landing_page_hero_image.field_media_image) { - heroBgImage = results.tidePage.field_landing_page_hero_image.field_media_image.url + if (pageData.tidePage.field_landing_page_hero_image && pageData.tidePage.field_landing_page_hero_image.field_media_image) { + heroBgImage = pageData.tidePage.field_landing_page_hero_image.field_media_image.url } - results.tidePage.appHeroBgImage = heroBgImage + pageData.tidePage.appHeroBgImage = heroBgImage // Hero banner Core fields let heroBanner = { type: 'heroBanner', - pageTitle: results.tidePage.appPageTitle, - introText: results.tidePage.field_news_intro_text || results.tidePage.field_landing_page_intro_text || results.tidePage.field_page_intro_text || results.tidePage.field_profile_intro_text || '' + pageTitle: pageData.tidePage.appPageTitle, + introText: pageData.tidePage.field_news_intro_text || pageData.tidePage.field_landing_page_intro_text || pageData.tidePage.field_page_intro_text || pageData.tidePage.field_profile_intro_text || '' } // Add hero banner modifier(paragraphs) - if (results.tidePage.field_landing_page_hero_banner) { - heroBanner = Object.assign(heroBanner, results.tidePage.field_landing_page_hero_banner) + if (pageData.tidePage.field_landing_page_hero_banner) { + heroBanner = Object.assign(heroBanner, pageData.tidePage.field_landing_page_hero_banner) } // Additional fields may will be moved into core or modifier - heroBanner.keyJourneys = results.tidePage.field_landing_page_key_journeys || {} - heroBanner.theme = results.tidePage.field_landing_page_hero_theme + heroBanner.keyJourneys = pageData.tidePage.field_landing_page_key_journeys || {} + heroBanner.theme = pageData.tidePage.field_landing_page_hero_theme heroBanner.showLinks = !heroBgImage - heroBanner.logo = results.tidePage.field_landing_page_hero_logo ? results.tidePage.field_landing_page_hero_logo.field_media_image.url : null + heroBanner.logo = pageData.tidePage.field_landing_page_hero_logo ? pageData.tidePage.field_landing_page_hero_logo.field_media_image.url : null // Add bottom graphic. - if (!heroBgImage && !results.tidePage.field_landing_page_c_primary) { - const hasBottomImage = (results.tidePage.field_bottom_graphical_image && results.tidePage.field_bottom_graphical_image.field_media_image) - heroBanner.backgroundGraphic = (hasBottomImage) ? results.tidePage.field_bottom_graphical_image.field_media_image.url : '/img/header-pattern-bottom.png' + if (!heroBgImage && !pageData.tidePage.field_landing_page_c_primary) { + const hasBottomImage = (pageData.tidePage.field_bottom_graphical_image && pageData.tidePage.field_bottom_graphical_image.field_media_image) + heroBanner.backgroundGraphic = (hasBottomImage) ? pageData.tidePage.field_bottom_graphical_image.field_media_image.url : '/img/header-pattern-bottom.png' } addComponentFromPromise(mapping.get(heroBanner), 'appHeroBanner') // Landing pages - if (results.tidePage.field_landing_page_c_primary) { - addComponentFromPromise(mapping.get(results.tidePage.field_landing_page_c_primary), 'appCampaignPrimary') + if (pageData.tidePage.field_landing_page_c_primary) { + addComponentFromPromise(mapping.get(pageData.tidePage.field_landing_page_c_primary), 'appCampaignPrimary') } - if (results.tidePage.field_landing_page_c_secondary) { - const cSecondary = results.tidePage.field_landing_page_c_secondary + if (pageData.tidePage.field_landing_page_c_secondary) { + const cSecondary = pageData.tidePage.field_landing_page_c_secondary cSecondary.type = cSecondary.type + '--field_landing_page_c_secondary' addComponentFromPromise(mapping.get(cSecondary), 'appCampaignSecondary') } // breadcrumbs - if (results.tidePage.path) { + if (pageData.tidePage.path) { const { breadcrumbs } = require('@dpc-sdp/ripple-nuxt-tide/lib/core/breadcrumbs') - const path = results.tidePage.path ? results.tidePage.path.alias : context.route.path - results.tidePage.breadcrumbs = breadcrumbs(path, results.tidePage.appPageTitle, context.store.state.tide.siteData.hierarchicalMenus.menuMain) + const path = pageData.tidePage.path ? pageData.tidePage.path.alias : context.route.path + pageData.tidePage.breadcrumbs = breadcrumbs(path, pageData.tidePage.appPageTitle, context.store.state.tide.siteData.hierarchicalMenus.menuMain) } // Load all components asynchronously, allow fail @@ -307,20 +310,20 @@ export default async function (context, results) { await Promise.all(asyncTasks) } // Add Page meta tags - if (results.tidePage) { + if (pageData.tidePage) { // Set details. - const title = results.tidePage.appMetatag.title || results.tidePage.appPageTitle || 'Page not found' - const description = results.tidePage.appMetatag.description || results.tidePage.field_news_intro_text || results.tidePage.field_landing_page_intro_text || results.tidePage.field_page_intro_text || results.tidePage.field_landing_page_summary || '' + const title = pageData.tidePage.appMetatag.title || pageData.tidePage.appPageTitle || 'Page not found' + const description = pageData.tidePage.appMetatag.description || pageData.tidePage.field_news_intro_text || pageData.tidePage.field_landing_page_intro_text || pageData.tidePage.field_page_intro_text || pageData.tidePage.field_landing_page_summary || '' const url = context.store.state.absoluteUrl || '' // Set image. - const mediaImage = results.tidePage.field_featured_image ? results.tidePage.field_featured_image.field_media_image : null + const mediaImage = pageData.tidePage.field_featured_image ? pageData.tidePage.field_featured_image.field_media_image : null const image = mediaImage ? mediaImage.url : '' const imageAlt = mediaImage ? mediaImage.meta.alt : '' - const siteSection = results.tidePage.field_node_site && results.tidePage.field_node_site.find(site => site.drupal_internal__tid === parseInt(results.tidePage.section, 10)) + const siteSection = pageData.tidePage.field_node_site && pageData.tidePage.field_node_site.find(site => site.drupal_internal__tid === parseInt(pageData.tidePage.section, 10)) - results.tidePage.head = { + pageData.tidePage.head = { htmlAttrs: { - lang: results.tidePage.langcode || 'en' + lang: pageData.tidePage.langcode || 'en' }, title: title, meta: [ @@ -340,14 +343,14 @@ export default async function (context, results) { { name: 'twitter:image:alt', hid: 'hid:image:alt', content: imageAlt }, // Custom page meta { name: 'sitesection', content: siteSection ? siteSection.name : '' }, - { name: 'content-type', content: results.tidePage.type && results.tidePage.type.replace('node--', '') } + { name: 'content-type', content: pageData.tidePage.type && pageData.tidePage.type.replace('node--', '') } ] } } - results.tideLayout = { + pageData.tideLayout = { sidebar: sidebar } - return results + return pageData } diff --git a/packages/ripple-nuxt-tide/lib/core/tide-helper.js b/packages/ripple-nuxt-tide/lib/core/tide-helper.js index 4d444786a..955f04150 100644 --- a/packages/ripple-nuxt-tide/lib/core/tide-helper.js +++ b/packages/ripple-nuxt-tide/lib/core/tide-helper.js @@ -1,6 +1,11 @@ - +import cuid from 'cuid' import mime from 'mime-types' +// Generate a unique id +export const generateId = () => { + return cuid() +} + // Private helpers export const mergeIncludes = (includes, includesMergedIn) => { if (includesMergedIn) { diff --git a/packages/ripple-nuxt-tide/lib/core/tide.js b/packages/ripple-nuxt-tide/lib/core/tide.js index fe4af9196..d3f790846 100644 --- a/packages/ripple-nuxt-tide/lib/core/tide.js +++ b/packages/ripple-nuxt-tide/lib/core/tide.js @@ -19,9 +19,9 @@ export const tide = (axios, site, config) => ({ * @param {String} resource Resource type e.g. / * @param {Object} params Object to convert to QueryString. Passed in URL. * @param {String} id Resource UUID - * @param {String} authToken Authentication token + * @param {Object} headersConfig Tide API request headers config object:{ authToken: '', requestId: '' } */ - get: async function (resource, params = {}, id = '', authToken) { + get: async function (resource, params = {}, id = '', headersConfig = {}) { // axios config const axiosConfig = { baseUrl: config.baseUrl, @@ -31,26 +31,34 @@ export const tide = (axios, site, config) => ({ if (this.isModuleEnabled('authenticatedContent')) { // Set 'X-Authorization' header if authToken present - if (authToken && !isTokenExpired(authToken)) { - _.merge(axiosConfig.headers, { 'X-Authorization': `Bearer ${authToken}` }) + if (headersConfig.authToken && !isTokenExpired(headersConfig.authToken)) { + _.merge(axiosConfig.headers, { 'X-Authorization': `Bearer ${headersConfig.authToken}` }) } } + if (headersConfig.requestId) { + axiosConfig.headers['X-Request-Id'] = headersConfig.requestId + } + const siteParam = 'site=' + site const url = `${apiPrefix}${resource}${id ? `/${id}` : ''}?${siteParam}${Object.keys(params).length ? `&${qs.stringify(params, { indices: false })}` : ''}` return axios.$get(url, axiosConfig) }, post: async function (resource, data = {}, id = '') { + // axios config + const axiosConfig = { + baseUrl: config.baseUrl, + auth: config.auth, + headers: { + 'Content-Type': 'application/vnd.api+json;charset=UTF-8', + 'X-Request-Id': helper.generateId() + } + } const siteParam = resource === 'user/register' ? '?site=' + site : '' const url = `${apiPrefix}${resource}${id ? `/${id}` : ''}${siteParam}` - let headers = { - 'Content-Type': 'application/vnd.api+json;charset=UTF-8' - } - _.merge(config, { headers: headers }) - - return axios.$post(url, data, config) + return axios.$post(url, data, axiosConfig) }, getMenuFields: function () { @@ -60,8 +68,8 @@ export const tide = (axios, site, config) => ({ } }, - async getSitesData (params = {}) { - const sites = await this.get('taxonomy_term/sites', params) + async getSitesData (params = {}, headersConfig = {}) { + const sites = await this.get('taxonomy_term/sites', params, '', headersConfig) if (typeof sites === 'undefined' || typeof sites.data === 'undefined') { return new Error('Failed to get sites data. It can be a operation error or configuration error if it\'s the first time to setup this app.') } else { @@ -69,8 +77,8 @@ export const tide = (axios, site, config) => ({ } }, - async getSitesDomainMap () { - const sites = await this.getSitesData() + async getSitesDomainMap (headersConfig = {}) { + const sites = await this.getSitesData({}, headersConfig) let sitesDomainMap = {} let domain = '' @@ -98,10 +106,10 @@ export const tide = (axios, site, config) => ({ return config && config.modules && config.modules[checkForModule] === 1 }, - getSiteData: async function (tid = null) { - // TODO: this method need to be reviewed when we do SDPA-585. - // So it can support without tide_site enabled. - const siteId = tid || site + // TODO: this method need to be reviewed when we do SDPA-585. + // So it can support without tide_site enabled. + getSiteData: async function (headersConfig = {}, siteId = null) { + siteId = siteId || site const include = [ 'field_site_logo', 'field_site_footer_logos', @@ -129,7 +137,7 @@ export const tide = (axios, site, config) => ({ value: siteId } } - const response = await this.get(`taxonomy_term/sites`, params) + const response = await this.get(`taxonomy_term/sites`, params, '', headersConfig) if (!response || response.error) { return new Error('Could not get site data. Please check your site id and Tide site setting.') } @@ -137,7 +145,7 @@ export const tide = (axios, site, config) => ({ } try { - siteData.menus = await this.getSiteMenus(siteData) + siteData.menus = await this.getSiteMenus(siteData, headersConfig) } catch (error) { if (process.server) { logger.error('Get menus from Tide failed:', { error }) @@ -163,13 +171,13 @@ export const tide = (axios, site, config) => ({ return siteData }, - getSiteMenus: async function (siteData) { + getSiteMenus: async function (siteData, headersConfig) { const siteMenus = {} const menuFields = this.getMenuFields() for (let menu in menuFields) { if (siteData[menuFields[menu]] !== undefined) { try { - siteMenus[menu] = await this.getMenu(siteData[menuFields[menu]].drupal_internal__id) + siteMenus[menu] = await this.getMenu(siteData[menuFields[menu]].drupal_internal__id, headersConfig) } catch (error) { if (process.server) { logger.error('Get site menus error: ', { error }) @@ -186,7 +194,7 @@ export const tide = (axios, site, config) => ({ return siteMenus }, - getMenu: async function (menuName) { + getMenu: async function (menuName, headersConfig = {}) { if (!menuName) { throw new Error('no menu id provided.') } @@ -199,7 +207,7 @@ export const tide = (axios, site, config) => ({ } } } - const menu = await this.get('menu_link_content/menu_link_content', params) + const menu = await this.get('menu_link_content/menu_link_content', params, '', headersConfig) return this.getAllPaginatedData(menu, false) }, @@ -232,17 +240,17 @@ export const tide = (axios, site, config) => ({ } }, - getPathData: async function (path, params, authToken) { + getPathData: async function (path, params, headersConfig) { let routeParams = { path: path } if (!_.isEmpty(params)) { _.merge(routeParams, params) } - const response = await this.get('route', routeParams, '', authToken) + const response = await this.get('route', routeParams, '', headersConfig) return response }, - getEntityByPathData: async function (pathData, query, authToken) { + getEntityByPathData: async function (pathData, query, headersConfig) { const endpoint = `${pathData.entity_type}/${pathData.bundle}/${pathData.uuid}` let include @@ -305,13 +313,13 @@ export const tide = (axios, site, config) => ({ if (!_.isEmpty(query)) { params = _.merge(query, params) } - const entity = await this.get(endpoint, params, '', authToken) + const entity = await this.get(endpoint, params, '', headersConfig) return entity }, - getPageByPath: async function (path, params, authToken) { + getPageByPath: async function (path, params, headersConfig) { let pageData = null - const response = await this.getPathData(path, params, authToken) + const response = await this.getPathData(path, params, headersConfig) const pathData = jsonapiParse.parse(response).data @@ -320,7 +328,7 @@ export const tide = (axios, site, config) => ({ return pathData } - const entity = await this.getEntityByPathData(pathData, params, authToken) + const entity = await this.getEntityByPathData(pathData, params, headersConfig) if (!entity) { throw new Error('Something wrong. Could not get any entity data from Tide based on API route response.') } @@ -331,7 +339,7 @@ export const tide = (axios, site, config) => ({ return pageData }, - getPreviewPage: async function (contentType, uuid, revisionId, section, params, authToken) { + getPreviewPage: async function (contentType, uuid, revisionId, section, params, headersConfig) { if (revisionId === 'latest') { params.resourceVersion = 'rel:working-copy' } else { @@ -343,7 +351,7 @@ export const tide = (axios, site, config) => ({ bundle: contentType, uuid: uuid } - const entity = await this.getEntityByPathData(pathData, params, authToken) + const entity = await this.getEntityByPathData(pathData, params, headersConfig) const pageData = jsonapiParse.parse(entity).data // Append the site section to page data diff --git a/packages/ripple-nuxt-tide/lib/module.js b/packages/ripple-nuxt-tide/lib/module.js index 7f13a806e..78f60c91e 100644 --- a/packages/ripple-nuxt-tide/lib/module.js +++ b/packages/ripple-nuxt-tide/lib/module.js @@ -67,10 +67,18 @@ const nuxtTide = function (moduleOptions) { this.addServerMiddleware(basicAuth) } - this.addServerMiddleware(require('./server-middleware/logger')) - this.addModule('@dpc-sdp/ripple-nuxt-ui', true) + // Add request id + this.addPlugin({ + src: path.resolve(__dirname, 'templates/request-id.js'), + fileName: './request-id.js' + }) + this.options.router.middleware.push('request-id') + this.addServerMiddleware(require('./server-middleware/request-id')) + // Log all server side requests + this.addServerMiddleware(require('./server-middleware/request-log')) + this.options.head.htmlAttrs = this.options.head.hasOwnProperty('htmlAttrs') ? this.options.head.htmlAttrs : this.options.head.htmlAttrs = { lang: 'en' } this.addModule('@nuxtjs/proxy', true) diff --git a/packages/ripple-nuxt-tide/lib/server-middleware/request-id.js b/packages/ripple-nuxt-tide/lib/server-middleware/request-id.js new file mode 100644 index 000000000..ef4f2c747 --- /dev/null +++ b/packages/ripple-nuxt-tide/lib/server-middleware/request-id.js @@ -0,0 +1,14 @@ +// Log server connection +import { generateId } from './../core/tide-helper' +const url = require('url') + +module.exports = function (req, res, next) { + // req is the Node.js http request object + const reqUrl = decodeURI((url.parse(req.url)).pathname) + if (!reqUrl.includes('/api/v')) { + req.requestId = generateId() + } + // next is a function to call to invoke the next middleware + // Don't forget to call next at the end if your middleware is not an endpoint! + next() +} diff --git a/packages/ripple-nuxt-tide/lib/server-middleware/logger.js b/packages/ripple-nuxt-tide/lib/server-middleware/request-log.js similarity index 56% rename from packages/ripple-nuxt-tide/lib/server-middleware/logger.js rename to packages/ripple-nuxt-tide/lib/server-middleware/request-log.js index eb25a0172..88d62162b 100644 --- a/packages/ripple-nuxt-tide/lib/server-middleware/logger.js +++ b/packages/ripple-nuxt-tide/lib/server-middleware/request-log.js @@ -6,11 +6,12 @@ module.exports = function (req, res, next) { // req is the Node.js http request object const status = res.statusCode const method = req.method.toUpperCase() - const reqUrl = decodeURI((url.parse(req.url)).pathname) - if (!reqUrl.includes('api/v1/')) { - logger.info('Server got request: %s %s %s', status, method, reqUrl, { label: 'Connect' }) + const reqUrl = url.parse(req.url) + const reqPath = decodeURI(reqUrl.pathname) + if (reqPath.includes('/api/v')) { + logger.info('Proxy %s %s to backend, res status code: %s.', method, reqPath, status, { label: 'Connect', requestQuery: reqUrl.query, requestId: req.headers['x-request-id'] }) } else { - logger.info('Proxy %s %s to backend, %s.', method, reqUrl, status, { label: 'Connect' }) + logger.info('Server got request: %s %s %s', status, method, reqPath, { label: 'Connect', requestQuery: reqUrl.query, requestId: req.requestId }) } // next is a function to call to invoke the next middleware // Don't forget to call next at the end if your middleware is not an endpoint! diff --git a/packages/ripple-nuxt-tide/lib/templates/plugin.js b/packages/ripple-nuxt-tide/lib/templates/plugin.js index 1df8c807a..6dc831846 100644 --- a/packages/ripple-nuxt-tide/lib/templates/plugin.js +++ b/packages/ripple-nuxt-tide/lib/templates/plugin.js @@ -21,7 +21,7 @@ export default ({ env, app, req, res, store , route}, inject) => { app.$axios.onRequest(config => { // Log all axios' requests if (process.server) { - logger.info('Making %s request to %s', config.method.toUpperCase(), config.url, {label: 'Axios'}) + logger.info('Making %s request to %s', config.method.toUpperCase(), config.url, {label: 'Axios', requestId: config.headers['X-Request-Id']}) logger.debug('Headers %O', config.headers, {label: 'Axios'}) } }) @@ -92,9 +92,9 @@ export default ({ env, app, req, res, store , route}, inject) => { } }, actions: { - async init ({ commit, dispatch }) { + async init ({ commit, dispatch }, { requestId = null } = {}) { if (process.server) { - await dispatch('setSiteData') + await dispatch('setSiteData', { requestId }) commit('setHost', req.headers.host) // Set protocol @@ -103,7 +103,7 @@ export default ({ env, app, req, res, store , route}, inject) => { // Load site module store. if (config.modules.site === 1) { - await store.dispatch('tideSite/init') + await store.dispatch('tideSite/init', { requestId }) } // Load authenticated content store. if (config.modules.authenticatedContent === 1) { @@ -111,8 +111,9 @@ export default ({ env, app, req, res, store , route}, inject) => { } } }, - async setSiteData ({ commit }) { - const siteData = await app.$tide.getSiteData() + async setSiteData ({ commit }, { requestId = null } = {}) { + const headersConfig = { requestId } + const siteData = await app.$tide.getSiteData(headersConfig) if (siteData instanceof Error) { throw siteData } diff --git a/packages/ripple-nuxt-tide/lib/templates/request-id.js b/packages/ripple-nuxt-tide/lib/templates/request-id.js new file mode 100644 index 000000000..f1562c0be --- /dev/null +++ b/packages/ripple-nuxt-tide/lib/templates/request-id.js @@ -0,0 +1,19 @@ +import { generateId } from '@dpc-sdp/ripple-nuxt-tide/lib/core/tide-helper' +import middleware from './middleware' + +middleware['request-id'] = async (context) => { + const { req, route, isHMR } = context; + + if (isHMR) { + return + } + + // Set request id for all Tide requests within the same route + if (req && req.requestId) { + // Nuxt req is server only variable, we pass the server request id to here. + route.requestId = req.requestId + } else { + // Generate a request id if it's a client side navigation. + route.requestId = generateId() + } +} diff --git a/packages/ripple-nuxt-tide/modules/publication/tide.middleware.js b/packages/ripple-nuxt-tide/modules/publication/tide.middleware.js index 4799b638a..8b340e8e0 100644 --- a/packages/ripple-nuxt-tide/modules/publication/tide.middleware.js +++ b/packages/ripple-nuxt-tide/modules/publication/tide.middleware.js @@ -1,39 +1,43 @@ export default { - publication: async (context, results) => { - if (results.tidePage && ['node--publication', 'node--publication_page'].includes(results.tidePage.type)) { - if (!results.tidePage.sidebarComponents || !Array.isArray(results.tidePage.sidebarComponents)) { - results.tidePage.sidebarComponents = [] + publication: async (context, pageData) => { + if (pageData.tidePage && ['node--publication', 'node--publication_page'].includes(pageData.tidePage.type)) { + if (!pageData.tidePage.sidebarComponents || !Array.isArray(pageData.tidePage.sidebarComponents)) { + pageData.tidePage.sidebarComponents = [] } let downloadLinks = null let publicationTitle = null - if (results.tidePage.type === 'node--publication') { - downloadLinks = context.app.$tideMapping.mappingFilters.fieldNodeDocuments(results.tidePage.field_node_documents) - publicationTitle = results.tidePage.title - } else if (results.tidePage.type === 'node--publication_page') { + if (pageData.tidePage.type === 'node--publication') { + downloadLinks = context.app.$tideMapping.mappingFilters.fieldNodeDocuments(pageData.tidePage.field_node_documents) + publicationTitle = pageData.tidePage.title + } else if (pageData.tidePage.type === 'node--publication_page') { // extra request for parent publication for download links - const parentPublication = await context.app.$tide.getPageByPath(results.tidePage.publication_navigation_root.meta.url, { include: 'field_node_documents.field_media_file' }) + const parentPublication = await context.app.$tide.getPageByPath( + pageData.tidePage.publication_navigation_root.meta.url, + { include: 'field_node_documents.field_media_file' }, + { requestId: pageData.requestId } + ) downloadLinks = context.app.$tideMapping.mappingFilters.fieldNodeDocuments(parentPublication.field_node_documents) publicationTitle = parentPublication.title - if (results.tidePage.breadcrumbs && results.tidePage.breadcrumbs.length > 0) { + if (pageData.tidePage.breadcrumbs && pageData.tidePage.breadcrumbs.length > 0) { const parentPublicationUrl = parentPublication.path.alias.split('/').filter(i => !i.includes('site-') && i !== '')[0] // Add publication root to breadcrumb - if (!results.tidePage.breadcrumbs.includes(crumb => crumb.url === parentPublicationUrl)) { - results.tidePage.breadcrumbs.splice(1, 0, { text: parentPublication.title, url: `/${parentPublicationUrl}` }) + if (!pageData.tidePage.breadcrumbs.includes(crumb => crumb.url === parentPublicationUrl)) { + pageData.tidePage.breadcrumbs.splice(1, 0, { text: parentPublication.title, url: `/${parentPublicationUrl}` }) } } } // add meta to page - if (publicationTitle && typeof results.tidePage.head.meta !== 'undefined') { - results.tidePage.head.meta.push({ + if (publicationTitle && typeof pageData.tidePage.head.meta !== 'undefined') { + pageData.tidePage.head.meta.push({ name: 'publication-title', content: publicationTitle }) } // add download and print to sidebar if (downloadLinks) { - results.tidePage.sidebarComponents.push({ + pageData.tidePage.sidebarComponents.push({ name: 'rpl-publication-download-print', order: 1, data: { @@ -57,9 +61,9 @@ export default { } return elem }) - } else if (results.tidePage.type === 'node--publication' && results.tidePage.publication_children) { + } else if (pageData.tidePage.type === 'node--publication' && pageData.tidePage.publication_children) { // parent page with pages but no chapters - return results.tidePage.publication_children.map(item => { + return pageData.tidePage.publication_children.map(item => { const menuItem = item.meta return { text: menuItem.title, @@ -68,18 +72,18 @@ export default { }) } } - if (results.tidePage.field_show_publication_nav) { + if (pageData.tidePage.field_show_publication_nav) { // Define root publication id and title. let publicationRootId, publicationRootTitle - switch (results.tidePage.type) { + switch (pageData.tidePage.type) { case 'node--publication': - publicationRootId = results.tidePage.id - publicationRootTitle = results.tidePage.title + publicationRootId = pageData.tidePage.id + publicationRootTitle = pageData.tidePage.title break case 'node--publication_page': - if (results.tidePage.publication_navigation_root) { - publicationRootId = results.tidePage.publication_navigation_root.meta.id - publicationRootTitle = results.tidePage.publication_navigation_root.meta.title + if (pageData.tidePage.publication_navigation_root) { + publicationRootId = pageData.tidePage.publication_navigation_root.meta.id + publicationRootTitle = pageData.tidePage.publication_navigation_root.meta.title } break } @@ -92,7 +96,7 @@ export default { // Add site section navigation to sidebar. if (menu) { - results.tidePage.sidebarComponents.push({ + pageData.tidePage.sidebarComponents.push({ name: 'rpl-site-section-navigation', order: 2, data: { @@ -105,7 +109,5 @@ export default { } } } // Check tidePage - - return results } } diff --git a/packages/ripple-nuxt-tide/modules/site/plugin.js b/packages/ripple-nuxt-tide/modules/site/plugin.js index 82462cd4e..40c8fb260 100644 --- a/packages/ripple-nuxt-tide/modules/site/plugin.js +++ b/packages/ripple-nuxt-tide/modules/site/plugin.js @@ -19,9 +19,11 @@ export default ({ app, store }) => { } }, actions: { - async init ({ commit }) { + async init ({ commit }, { requestId = null } = {}) { if (process.server) { - const sitesDomainMap = await app.$tide.getSitesDomainMap() + const headersConfig = { requestId } + // TODO: This request may unnecessary, as we should able to get from other request. + const sitesDomainMap = await app.$tide.getSitesDomainMap(headersConfig) commit('setSitesDomainMap', sitesDomainMap) } } diff --git a/packages/ripple-nuxt-tide/package.json b/packages/ripple-nuxt-tide/package.json index 5f64c37bc..94d0b13b0 100755 --- a/packages/ripple-nuxt-tide/package.json +++ b/packages/ripple-nuxt-tide/package.json @@ -55,6 +55,7 @@ "connect-logger": "^0.0.1", "cookieparser": "^0.1.0", "cookies": "^0.7.3", + "cuid": "^2.1.6", "elastic-builder": "^2.0.2", "elasticsearch": "^15.4.1", "js-cookie": "^2.2.0", diff --git a/yarn.lock b/yarn.lock index becb3b54c..7fe0ec944 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5477,6 +5477,10 @@ cucumber@^4.2.1: util-arity "^1.0.2" verror "^1.9.0" +cuid@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/cuid/-/cuid-2.1.6.tgz#dc3a20b5a7497d36d32c0bf8a2997524c9c796c4" + cuint@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b"