diff --git a/.storybook/__snapshots__/Welcome.story.storyshot b/.storybook/__snapshots__/Welcome.story.storyshot
index 44edbaf72e..4095e27f37 100644
--- a/.storybook/__snapshots__/Welcome.story.storyshot
+++ b/.storybook/__snapshots__/Welcome.story.storyshot
@@ -3024,6 +3024,18 @@ exports[`Storybook Snapshot tests and console checks Storyshots 0/Getting Starte
useSuiteHeaderData
+
+
+
+ suiteHeaderData
+
+
diff --git a/jest.config.js b/jest.config.js
index 0a76eec30b..501c4af416 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -3,6 +3,7 @@ module.exports = {
'src/components/**/*.js?(x)',
'!src/**/*.story.js?(x)',
'!src/**/hooks/*.js',
+ '!src/components/SuiteHeader/util/suiteHeaderData.js',
],
coveragePathIgnorePatterns: ['/node_modules/', '/lib/', '/coverage/'],
coverageReporters: ['html', 'text-summary', 'lcov'],
diff --git a/src/components/SuiteHeader/SuiteHeader.story.jsx b/src/components/SuiteHeader/SuiteHeader.story.jsx
index f622bf0cfc..e4ec6d89bf 100644
--- a/src/components/SuiteHeader/SuiteHeader.story.jsx
+++ b/src/components/SuiteHeader/SuiteHeader.story.jsx
@@ -7,6 +7,7 @@ import Group from '@carbon/icons-react/lib/group/24';
import SuiteHeader from './SuiteHeader';
import SuiteHeaderI18N from './i18n';
+// import getSuiteHeaderData from './util/suiteHeaderData';
// import useSuiteHeaderData from './hooks/useSuiteHeaderData';
const sideNavLinks = [
@@ -227,6 +228,109 @@ export const HeaderWithSurveyNotification = () => {
);
};
+/* Sample of SuiteHeader usage with data hook
+export const HeaderWithHook = () => {
+ const StatefulExample = () => {
+ const [data] = useSuiteHeaderData({
+ // baseApiUrl: 'http://localhost:3001/internal',
+ domain: 'mydomain.com',
+ isTest: true,
+ surveyConfig: {
+ id: 'suite',
+ delayIntervalDays: 30,
+ frequencyDays: 90,
+ },
+ lang: 'en',
+ });
+ const surveyData = data.showSurvey
+ ? {
+ surveyLink: 'https://www.ibm.com',
+ privacyLink: 'https://www.ibm.com',
+ }
+ : null;
+ return data.username ? (
+
+ ) : null;
+ };
+ return ;
+};
+
+HeaderWithHook.story = {
+ name: 'Header with hook',
+};
+
+export const HeaderWithDataFetching = () => {
+ const StatefulExample = () => {
+ const [data, setData] = useState({
+ username: null,
+ userDisplayName: null,
+ email: null,
+ routes: {
+ profile: null,
+ navigator: null,
+ admin: null,
+ logout: null,
+ about: null,
+ documentation: null,
+ whatsNew: null,
+ requestEnhancement: null,
+ support: null,
+ gettingStarted: null,
+ },
+ applications: [],
+ showSurvey: false,
+ });
+ useEffect(() => {
+ getSuiteHeaderData({
+ // baseApiUrl: 'http://localhost:3001/internal',
+ domain: 'mydomain.com',
+ isTest: true,
+ surveyConfig: {
+ id: 'suite',
+ delayIntervalDays: 30,
+ frequencyDays: 90,
+ },
+ lang: 'en',
+ }).then((suiteHeaderData) => setData(suiteHeaderData));
+ }, []);
+
+ const surveyData = data.showSurvey
+ ? {
+ surveyLink: 'https://www.ibm.com',
+ privacyLink: 'https://www.ibm.com',
+ }
+ : null;
+ return data.username ? (
+
+ ) : null;
+ };
+ return ;
+};
+
+HeaderWithDataFetching.story = {
+ name: 'Header with data fetching',
+};
+
+*/
+
HeaderWithSurveyNotification.story = {
name: 'Header with survey notification',
};
diff --git a/src/components/SuiteHeader/hooks/useSuiteHeaderData.js b/src/components/SuiteHeader/hooks/useSuiteHeaderData.js
index 2f3414a310..d4139f61bc 100644
--- a/src/components/SuiteHeader/hooks/useSuiteHeaderData.js
+++ b/src/components/SuiteHeader/hooks/useSuiteHeaderData.js
@@ -1,168 +1,12 @@
import { useState, useEffect, useCallback } from 'react';
-import moment from 'moment';
-
-import SuiteHeaderI18N from '../i18n';
// eslint-disable-next-line import/extensions
-import testApiData from './suiteHeaderData.fixture.js';
-
-// default route calculation logic
-const calcRoutes = (domain, user, workspaces, applications) => {
- const workspaceId = Object.keys(user.workspaces)[0];
- const getApplicationUrl = (appId) =>
- user.workspaces[workspaceId].applications[appId].href;
- const isAdmin = user.permissions.systemAdmin || user.permissions.userAdmin;
- const routeData = {
- profile: `https://home.${domain}/myaccount`,
- navigator: `https://${workspaceId}.home.${domain}`,
- admin: isAdmin ? `https://admin.${domain}` : null,
- logout: `https://home.${domain}/logout`,
- whatsNew:
- 'https://www.ibm.com/support/knowledgecenter/SSRHPA_current/appsuite/overview/whats_new.html',
- gettingStarted:
- 'https://www.ibm.com/support/knowledgecenter/SSRHPA_current/appsuite/overview/getting_started.html',
- documentation: 'https://www.ibm.com/support/knowledgecenter/SSRHPA_current',
- requestEnhancement: 'https://ibm-watson-iot.ideas.aha.io/',
- support: 'https://www.ibm.com/mysupport',
- about: `https://home.${domain}/about`,
- };
- const appOrdering = ['monitor', 'health', 'predict', 'visualinspection'];
- const workspaceApplications = user.workspaces[workspaceId].applications || {};
- const applicationSyncStates = user.applications || {};
- const appData = Object.keys(workspaceApplications)
- .filter((appId) => workspaceApplications[appId].role !== 'NO_ACCESS')
- .filter((appId) => applicationSyncStates[appId]?.sync?.state === 'SUCCESS')
- .filter(
- (appId) =>
- (applications.find((i) => i.id === appId) || {}).category ===
- 'application'
- )
- .sort(
- (a, b) =>
- appOrdering.findIndex((i) => i === a) -
- appOrdering.findIndex((i) => i === b)
- )
- .map((appId) => ({
- id: appId,
- name:
- (applications.find((i) => i.id === appId) || {}).name ||
- appId.charAt(0).toUpperCase() + appId.slice(1),
- href: getApplicationUrl(appId),
- isExternal: getApplicationUrl(appId).indexOf(domain) >= 0,
- }))
- .sort();
- return [routeData, appData];
-};
-
-// Default survey status calculation logic
-const calcSurveyStatus = async (userId, surveyConfig, apiFct) => {
- let showSurvey = false;
-
- // Check if it is time to show the survey
- const isTimeForSurvey = (surveyData) => {
- // If survey is not enabled, return false
- if (!surveyData.enabled) {
- return false;
- }
- // If lastPromptTimestamp is set and it is greater than initialInteractionTimestamp,
- // it means that at least one survey has already been prompted to the user,
- // so, we check if another survey prompt is due.
- if (
- surveyData.lastPromptTimestamp &&
- moment(surveyData.lastPromptTimestamp).isAfter(
- surveyData.initialInteractionTimestamp
- )
- ) {
- if (
- moment().diff(surveyData.lastPromptTimestamp, 'days') >
- surveyData.frequencyDays
- ) {
- return true;
- }
- }
- // No survey has been prompted yet, so we check if it is time for the first one.
- else if (
- moment().diff(surveyData.initialInteractionTimestamp, 'days') >
- surveyData.delayIntervalDays
- ) {
- return true;
- }
- return false;
- };
-
- const surveyData = await apiFct(
- 'GET',
- `/users/${userId}/surveys/${surveyConfig.id}`
- );
- if (surveyData) {
- // Survey data found, check it some config props need to be updated on the backend
- const updateObject = {};
- ['delayIntervalDays', 'frequencyDays', 'enabled'].forEach((surveyProp) => {
- if (
- surveyConfig[surveyProp] &&
- surveyConfig[surveyProp] !== surveyData[surveyProp]
- ) {
- updateObject[surveyProp] = surveyConfig[surveyProp];
- }
- });
- // If at least one config prop is different than the one in the existing record, update it
- if (Object.keys(updateObject).length > 0) {
- await apiFct(
- 'PUT',
- `/users/${userId}/surveys/${surveyConfig.id}`,
- updateObject
- );
- }
- // Based on survey data and current timestamp, make the proper time comparisons to check if it is time to show a survey
- showSurvey = isTimeForSurvey(surveyData);
- if (showSurvey) {
- // Update lastPromptTimestamp to the current timestamp so that we need to wait another 'frequencyDays' days until the next survey
- await apiFct('PUT', `/users/${userId}/surveys/${surveyConfig.id}`, {
- lastPromptTimestamp: moment.utc().format(),
- });
- }
- } else {
- // Survey record not found, create it
- await apiFct('POST', `/users/${userId}/surveys`, {
- ...surveyConfig,
- delayIntervalDays: surveyConfig.delayIntervalDays ?? 30,
- frequencyDays: surveyConfig.frequencyDays ?? 90,
- enabled: surveyConfig.enabled ?? true,
- initialInteractionTimestamp: moment.utc().format(),
- });
- }
- return showSurvey;
-};
-
-// default i18n calculation logic
-const calcI18N = (i18nData) => ({
- ...i18nData,
- surveyTitle: (solutionName) =>
- i18nData.surveyTitle.replace('{solutionName}', solutionName),
- profileLogoutModalBody: (solutionName, userName) =>
- i18nData.profileLogoutModalBody
- .replace('{solutionName}', solutionName)
- .replace('{userName}', userName),
-});
-
-const defaultFetchApi = async (method, url, body, headers, testResponse) =>
- testResponse ||
- fetch(url, {
- method,
- credentials: 'include',
- headers: headers || {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(body),
- })
- .then((res) => res.json())
- .then((resJson) => {
- // Don't return any data if an error happened (401, 404, 409, etc)
- if (resJson.error || resJson.exception) {
- return null;
- }
- return resJson;
- });
+import getSuiteHeaderData, {
+ calcRoutes,
+ calcSurveyStatus,
+ calcI18N,
+ defaultFetchApi,
+} from '../util/suiteHeaderData';
const useSuiteHeaderData = ({
baseApiUrl,
@@ -194,67 +38,24 @@ const useSuiteHeaderData = ({
gettingStarted: null,
},
applications: [],
+ showSurvey: false,
});
const refreshData = useCallback(async () => {
- const api = (method, path, body, headers) =>
- fetchApi(
- method,
- `${baseApiUrl}${path}`,
- body,
- headers,
- isTest ? testApiData[path] : null
- );
-
try {
setIsLoading(true);
- const profileData = await api('GET', '/profile');
- const appsData = await api('GET', '/applications');
- const eamData = await api('GET', '/config/eam');
- const i18nData = await api('GET', `/i18n/header/${isTest ? 'en' : lang}`);
-
- // Routes
- const [routes, applications] = calculateRoutes(
+ const suiteHeaderData = await getSuiteHeaderData({
+ baseApiUrl,
domain,
- profileData.user,
- profileData.workspaces,
- appsData
- );
-
- // Survey
- const showSurvey = surveyConfig?.id
- ? await calculateSurveyStatus(
- profileData.user.username,
- surveyConfig,
- api
- )
- : false;
-
- // i18n
- const i18n = i18nData ? calculateI18N(i18nData) : SuiteHeaderI18N.en;
-
- setData({
- username: profileData.user.username,
- userDisplayName: profileData.user.displayName,
- email: profileData.user.email,
- routes,
- applications: [
- ...(eamData?.url
- ? [
- {
- id: 'eam',
- name: 'Manage',
- href: eamData.url,
- isExternal: true,
- },
- ]
- : []),
- ...applications,
- ],
- i18n,
- showSurvey,
+ lang,
+ calculateRoutes,
+ calculateSurveyStatus,
+ calculateI18N,
+ fetchApi,
+ surveyConfig,
+ isTest,
});
- setIsLoading(false);
+ setData(suiteHeaderData);
} catch (err) {
setError(err);
setIsLoading(false);
diff --git a/src/components/SuiteHeader/hooks/suiteHeaderData.fixture.js b/src/components/SuiteHeader/util/suiteHeaderData.fixture.js
similarity index 100%
rename from src/components/SuiteHeader/hooks/suiteHeaderData.fixture.js
rename to src/components/SuiteHeader/util/suiteHeaderData.fixture.js
diff --git a/src/components/SuiteHeader/util/suiteHeaderData.js b/src/components/SuiteHeader/util/suiteHeaderData.js
new file mode 100644
index 0000000000..d01c0e2fe1
--- /dev/null
+++ b/src/components/SuiteHeader/util/suiteHeaderData.js
@@ -0,0 +1,236 @@
+import moment from 'moment';
+
+import SuiteHeaderI18N from '../i18n';
+
+// eslint-disable-next-line import/extensions
+import testApiData from './suiteHeaderData.fixture.js';
+
+// default route calculation logic
+export const calcRoutes = (domain, user, workspaces, applications) => {
+ const workspaceId = Object.keys(user.workspaces)[0];
+ const getApplicationUrl = (appId) =>
+ user.workspaces[workspaceId].applications[appId].href;
+ const isAdmin = user.permissions.systemAdmin || user.permissions.userAdmin;
+ const routeData = {
+ profile: `https://home.${domain}/myaccount`,
+ navigator: `https://${workspaceId}.home.${domain}`,
+ admin: isAdmin ? `https://admin.${domain}` : null,
+ logout: `https://home.${domain}/logout`,
+ whatsNew:
+ 'https://www.ibm.com/support/knowledgecenter/SSRHPA_current/appsuite/overview/whats_new.html',
+ gettingStarted:
+ 'https://www.ibm.com/support/knowledgecenter/SSRHPA_current/appsuite/overview/getting_started.html',
+ documentation: 'https://www.ibm.com/support/knowledgecenter/SSRHPA_current',
+ requestEnhancement: 'https://ibm-watson-iot.ideas.aha.io/',
+ support: 'https://www.ibm.com/mysupport',
+ about: `https://home.${domain}/about`,
+ };
+ const appOrdering = ['monitor', 'health', 'predict', 'visualinspection'];
+ const workspaceApplications = user.workspaces[workspaceId].applications || {};
+ const applicationSyncStates = user.applications || {};
+ const appData = Object.keys(workspaceApplications)
+ .filter((appId) => workspaceApplications[appId].role !== 'NO_ACCESS')
+ .filter((appId) => applicationSyncStates[appId]?.sync?.state === 'SUCCESS')
+ .filter(
+ (appId) =>
+ (applications.find((i) => i.id === appId) || {}).category ===
+ 'application'
+ )
+ .sort(
+ (a, b) =>
+ appOrdering.findIndex((i) => i === a) -
+ appOrdering.findIndex((i) => i === b)
+ )
+ .map((appId) => ({
+ id: appId,
+ name:
+ (applications.find((i) => i.id === appId) || {}).name ||
+ appId.charAt(0).toUpperCase() + appId.slice(1),
+ href: getApplicationUrl(appId),
+ isExternal: getApplicationUrl(appId).indexOf(domain) >= 0,
+ }))
+ .sort();
+ return [routeData, appData];
+};
+
+// Default survey status calculation logic
+export const calcSurveyStatus = async (userId, surveyConfig, apiFct) => {
+ let showSurvey = false;
+
+ // Check if it is time to show the survey
+ const isTimeForSurvey = (surveyData) => {
+ // If survey is not enabled, return false
+ if (!surveyData.enabled) {
+ return false;
+ }
+ // If lastPromptTimestamp is set and it is greater than initialInteractionTimestamp,
+ // it means that at least one survey has already been prompted to the user,
+ // so, we check if another survey prompt is due.
+ if (
+ surveyData.lastPromptTimestamp &&
+ moment(surveyData.lastPromptTimestamp).isAfter(
+ surveyData.initialInteractionTimestamp
+ )
+ ) {
+ if (
+ moment().diff(surveyData.lastPromptTimestamp, 'days') >
+ surveyData.frequencyDays
+ ) {
+ return true;
+ }
+ }
+ // No survey has been prompted yet, so we check if it is time for the first one.
+ else if (
+ moment().diff(surveyData.initialInteractionTimestamp, 'days') >
+ surveyData.delayIntervalDays
+ ) {
+ return true;
+ }
+ return false;
+ };
+
+ const surveyData = await apiFct(
+ 'GET',
+ `/users/${userId}/surveys/${surveyConfig.id}`
+ );
+ if (surveyData) {
+ // Survey data found, check it some config props need to be updated on the backend
+ const updateObject = {};
+ ['delayIntervalDays', 'frequencyDays', 'enabled'].forEach((surveyProp) => {
+ if (
+ surveyConfig[surveyProp] &&
+ surveyConfig[surveyProp] !== surveyData[surveyProp]
+ ) {
+ updateObject[surveyProp] = surveyConfig[surveyProp];
+ }
+ });
+ // If at least one config prop is different than the one in the existing record, update it
+ if (Object.keys(updateObject).length > 0) {
+ await apiFct(
+ 'PUT',
+ `/users/${userId}/surveys/${surveyConfig.id}`,
+ updateObject
+ );
+ }
+ // Based on survey data and current timestamp, make the proper time comparisons to check if it is time to show a survey
+ showSurvey = isTimeForSurvey(surveyData);
+ if (showSurvey) {
+ // Update lastPromptTimestamp to the current timestamp so that we need to wait another 'frequencyDays' days until the next survey
+ await apiFct('PUT', `/users/${userId}/surveys/${surveyConfig.id}`, {
+ lastPromptTimestamp: moment.utc().format(),
+ });
+ }
+ } else {
+ // Survey record not found, create it
+ await apiFct('POST', `/users/${userId}/surveys`, {
+ ...surveyConfig,
+ delayIntervalDays: surveyConfig.delayIntervalDays ?? 30,
+ frequencyDays: surveyConfig.frequencyDays ?? 90,
+ enabled: surveyConfig.enabled ?? true,
+ initialInteractionTimestamp: moment.utc().format(),
+ });
+ }
+ return showSurvey;
+};
+
+// default i18n calculation logic
+export const calcI18N = (i18nData) => ({
+ ...i18nData,
+ surveyTitle: (solutionName) =>
+ i18nData.surveyTitle.replace('{solutionName}', solutionName),
+ profileLogoutModalBody: (solutionName, userName) =>
+ i18nData.profileLogoutModalBody
+ .replace('{solutionName}', solutionName)
+ .replace('{userName}', userName),
+});
+
+export const defaultFetchApi = async (
+ method,
+ url,
+ body,
+ headers,
+ testResponse
+) =>
+ testResponse ||
+ fetch(url, {
+ method,
+ credentials: 'include',
+ headers: headers || {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ })
+ .then((res) => res.json())
+ .then((resJson) => {
+ // Don't return any data if an error happened (401, 404, 409, etc)
+ if (resJson.error || resJson.exception) {
+ return null;
+ }
+ return resJson;
+ });
+
+const getSuiteHeaderData = async ({
+ baseApiUrl,
+ domain,
+ lang = 'en',
+ calculateRoutes = calcRoutes,
+ calculateSurveyStatus = calcSurveyStatus,
+ calculateI18N = calcI18N,
+ fetchApi = defaultFetchApi,
+ surveyConfig = null,
+ isTest = false,
+}) => {
+ const api = (method, path, body, headers) =>
+ fetchApi(
+ method,
+ `${baseApiUrl}${path}`,
+ body,
+ headers,
+ isTest ? testApiData[path] : null
+ );
+
+ const profileData = await api('GET', '/profile');
+ const appsData = await api('GET', '/applications');
+ const eamData = await api('GET', '/config/eam');
+ const i18nData = await api('GET', `/i18n/header/${isTest ? 'en' : lang}`);
+
+ // Routes
+ const [routes, applications] = calculateRoutes(
+ domain,
+ profileData.user,
+ profileData.workspaces,
+ appsData
+ );
+
+ // Survey
+ const showSurvey = surveyConfig?.id
+ ? await calculateSurveyStatus(profileData.user.username, surveyConfig, api)
+ : false;
+
+ // i18n
+ const i18n = i18nData ? calculateI18N(i18nData) : SuiteHeaderI18N.en;
+
+ return {
+ username: profileData.user.username,
+ userDisplayName: profileData.user.displayName,
+ email: profileData.user.email,
+ routes,
+ applications: [
+ ...(eamData?.url
+ ? [
+ {
+ id: 'eam',
+ name: 'Manage',
+ href: eamData.url,
+ isExternal: true,
+ },
+ ]
+ : []),
+ ...applications,
+ ],
+ i18n,
+ showSurvey,
+ };
+};
+
+export default getSuiteHeaderData;
diff --git a/src/index.js b/src/index.js
index b3c810f435..a6f8b6b59a 100755
--- a/src/index.js
+++ b/src/index.js
@@ -59,6 +59,7 @@ export SuiteHeaderAppSwitcher from './components/SuiteHeader/SuiteHeaderAppSwitc
export SuiteHeaderLogoutModal from './components/SuiteHeader/SuiteHeaderLogoutModal/SuiteHeaderLogoutModal';
export SuiteHeaderI18N from './components/SuiteHeader/i18n';
export useSuiteHeaderData from './components/SuiteHeader/hooks/useSuiteHeaderData';
+export suiteHeaderData from './components/SuiteHeader/util/suiteHeaderData';
// Dashboard
export Dashboard from './components/Dashboard/Dashboard';
diff --git a/src/utils/__tests__/__snapshots__/publicAPI.test.js.snap b/src/utils/__tests__/__snapshots__/publicAPI.test.js.snap
index d66d0b3eed..b52404c173 100644
--- a/src/utils/__tests__/__snapshots__/publicAPI.test.js.snap
+++ b/src/utils/__tests__/__snapshots__/publicAPI.test.js.snap
@@ -7122,6 +7122,7 @@ Map {
},
},
"useSuiteHeaderData" => Object {},
+ "suiteHeaderData" => Object {},
"Dashboard" => Object {
"defaultProps": Object {
"actions": Array [],