Skip to content

Commit

Permalink
Merge pull request #1757 from carbon-design-system/suite-header-util
Browse files Browse the repository at this point in the history
feat(suiteheader) Separating data fetching logic in a util function.
  • Loading branch information
kodiakhq[bot] authored Nov 17, 2020
2 parents c41709f + 5464362 commit fc4adf7
Show file tree
Hide file tree
Showing 8 changed files with 372 additions and 216 deletions.
12 changes: 12 additions & 0 deletions .storybook/__snapshots__/Welcome.story.storyshot
Original file line number Diff line number Diff line change
Expand Up @@ -3024,6 +3024,18 @@ exports[`Storybook Snapshot tests and console checks Storyshots 0/Getting Starte
useSuiteHeaderData
</div>
</div>
<div
className="bx--structured-list-row"
>
<div
className="bx--structured-list-td"
/>
<div
className="bx--structured-list-td"
>
suiteHeaderData
</div>
</div>
<div
className="bx--structured-list-row"
>
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
104 changes: 104 additions & 0 deletions src/components/SuiteHeader/SuiteHeader.story.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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 ? (
<SuiteHeader
suiteName="Application Suite"
appName="Application Name"
userDisplayName={data.userDisplayName}
username={data.username}
routes={data.routes}
applications={data.applications}
i18n={data.i18n}
surveyData={surveyData}
/>
) : null;
};
return <StatefulExample />;
};
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 ? (
<SuiteHeader
suiteName="Application Suite"
appName="Application Name"
userDisplayName={data.userDisplayName}
username={data.username}
routes={data.routes}
applications={data.applications}
i18n={data.i18n}
surveyData={surveyData}
/>
) : null;
};
return <StatefulExample />;
};
HeaderWithDataFetching.story = {
name: 'Header with data fetching',
};
*/

HeaderWithSurveyNotification.story = {
name: 'Header with survey notification',
};
233 changes: 17 additions & 216 deletions src/components/SuiteHeader/hooks/useSuiteHeaderData.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit fc4adf7

Please sign in to comment.