From e2a191d45f8add107f74b28d109337f6ccf0a859 Mon Sep 17 00:00:00 2001 From: Austin McGee <947888+amcgee@users.noreply.github.com> Date: Fri, 15 May 2020 14:42:16 +0200 Subject: [PATCH 1/5] fix: correctly select and use user locale and application title text --- i18n/en.pot | 6 +- src/HeaderBar.js | 43 +++++----- src/HeaderBar/Apps.js | 10 +-- src/HeaderBar/Logo.js | 52 ++++++------ src/HeaderBar/Notifications.js | 56 +++++++------ src/HeaderBar/Profile/ProfileHeader.js | 51 ++++++------ src/HeaderBar/Profile/ProfileMenu.js | 109 ++++++++++++------------- src/__demo__/HeaderBar.stories.js | 92 +++++++++++++++------ 8 files changed, 225 insertions(+), 194 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index f73e0f6..e983a46 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2020-02-18T13:19:29.126Z\n" -"PO-Revision-Date: 2020-02-18T13:19:29.126Z\n" +"POT-Creation-Date: 2020-05-15T11:30:14.721Z\n" +"PO-Revision-Date: 2020-05-15T11:30:14.721Z\n" msgid "Search apps" msgstr "" @@ -29,5 +29,5 @@ msgstr "" msgid "Logout" msgstr "" -msgid "Something went wrong with loading the children." +msgid "Could not load children" msgstr "" diff --git a/src/HeaderBar.js b/src/HeaderBar.js index ecd0940..71580d8 100755 --- a/src/HeaderBar.js +++ b/src/HeaderBar.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React, { useMemo } from 'react' import propTypes from '@dhis2/prop-types' import { colors } from '@dhis2/ui-core' @@ -6,19 +6,18 @@ import { colors } from '@dhis2/ui-core' import Apps from './HeaderBar/Apps.js' import Profile from './HeaderBar/Profile.js' -import { useDataQuery } from '@dhis2/app-runtime' +import { useDataQuery, useConfig } from '@dhis2/app-runtime' import { Logo } from './HeaderBar/Logo.js' import { Title } from './HeaderBar/Title.js' import { Notifications } from './HeaderBar/Notifications.js' -import './locales' -import i18n from '@dhis2/d2-i18n' +import i18n from './locales' const query = { - systemInfo: { - resource: 'system/info', + title: { + resource: 'systemSettings/applicationTitle', }, user: { resource: 'me', @@ -32,24 +31,27 @@ const query = { } export const HeaderBar = ({ appName, className }) => { + const baseUrl = useConfig().baseUrl const { loading, error, data } = useDataQuery(query) - useEffect(() => { + const apps = useMemo(() => { const getPath = path => path.startsWith('http:') || path.startsWith('https:') ? path - : `${data.systemInfo.contextPath}/api/${path}` + : `${baseUrl}api/${path}` - if (!loading && !error) - data.apps.modules.forEach(app => { - app.icon = getPath(app.icon) - app.defaultAction = getPath(app.defaultAction) - }) + return data?.apps.modules.map(app => ({ + ...app, + icon: getPath(app.icon), + defaultAction: getPath(app.defaultAction), + })) }, [data]) if (!loading && !error) { // TODO: This will run every render which is probably wrong! Also, setting the global locale shouldn't be done in the headerbar const locale = data.user.settings.keyUiLocale || 'en' + console.log(locale) + i18n.setDefaultNamespace('default') i18n.changeLanguage(locale) } @@ -57,10 +59,10 @@ export const HeaderBar = ({ appName, className }) => {
{!loading && !error && ( <> - + <div className="right-control-spacer" /> <Notifications @@ -68,16 +70,9 @@ export const HeaderBar = ({ appName, className }) => { data.notifications.unreadInterpretations } messages={data.notifications.unreadMessageConversations} - contextPath={data.systemInfo.contextPath} - /> - <Apps - apps={data.apps.modules} - contextPath={data.systemInfo.contextPath} - /> - <Profile - user={data.user} - contextPath={data.systemInfo.contextPath} /> + <Apps apps={apps} /> + <Profile user={data.user} /> </> )} diff --git a/src/HeaderBar/Apps.js b/src/HeaderBar/Apps.js index 6385430..3b90103 100755 --- a/src/HeaderBar/Apps.js +++ b/src/HeaderBar/Apps.js @@ -1,5 +1,6 @@ import React from 'react' import propTypes from '@dhis2/prop-types' +import { useConfig } from '@dhis2/app-runtime' import i18n from '@dhis2/d2-i18n' import { Card, InputField, colors, theme } from '@dhis2/ui-core' @@ -51,7 +52,9 @@ TrailIcon.propTypes = { onClick: propTypes.func, } -function Search({ value, onChange, onIconClick, contextPath }) { +function Search({ value, onChange, onIconClick }) { + const baseUrl = useConfig().baseUrl + return ( <div> <span> @@ -66,7 +69,7 @@ function Search({ value, onChange, onIconClick, contextPath }) { </span> <span> - <a href={`${contextPath}/dhis-web-menu-management`}> + <a href={`${baseUrl}dhis-web-menu-management`}> <Settings className={settingsIcon.className} /> </a> </span> @@ -99,7 +102,6 @@ Search.defaultProps = { } Search.propTypes = { - contextPath: propTypes.string.isRequired, value: propTypes.string.isRequired, onChange: propTypes.func.isRequired, onIconClick: propTypes.func, @@ -244,7 +246,6 @@ export default class Apps extends React.Component { value={this.state.filter} onChange={this.onChange} onIconClick={this.onIconClick} - contextPath={this.props.contextPath} /> <List apps={apps} filter={this.state.filter} /> </Card> @@ -291,5 +292,4 @@ export default class Apps extends React.Component { Apps.propTypes = { apps: propTypes.array.isRequired, - contextPath: propTypes.string.isRequired, } diff --git a/src/HeaderBar/Logo.js b/src/HeaderBar/Logo.js index ed0c41f..2a6e726 100755 --- a/src/HeaderBar/Logo.js +++ b/src/HeaderBar/Logo.js @@ -1,34 +1,34 @@ import React from 'react' -import propTypes from '@dhis2/prop-types' +import { useConfig } from '@dhis2/app-runtime' import { LogoImage } from './LogoImage.js' -export const Logo = ({ baseUrl }) => ( - <div data-test="headerbar-logo"> - <a href={baseUrl}> - <LogoImage /> - </a> +export const Logo = () => { + const baseUrl = useConfig().baseUrl - <style jsx>{` - div { - box-sizing: border-box; - min-width: 49px; - max-height: 48px; - margin: 0 12px 0 0; - border-right: 1px solid rgba(32, 32, 32, 0.15); - } + return ( + <div data-test="headerbar-logo"> + <a href={baseUrl}> + <LogoImage /> + </a> - a, - a:hover, - a:focus, - a:active, - a:visited { - user-select: none; - } - `}</style> - </div> -) + <style jsx>{` + div { + box-sizing: border-box; + min-width: 49px; + max-height: 48px; + margin: 0 12px 0 0; + border-right: 1px solid rgba(32, 32, 32, 0.15); + } -Logo.propTypes = { - baseUrl: propTypes.string.isRequired, + a, + a:hover, + a:focus, + a:active, + a:visited { + user-select: none; + } + `}</style> + </div> + ) } diff --git a/src/HeaderBar/Notifications.js b/src/HeaderBar/Notifications.js index dc1b571..a079988 100755 --- a/src/HeaderBar/Notifications.js +++ b/src/HeaderBar/Notifications.js @@ -1,37 +1,41 @@ import React from 'react' import propTypes from '@dhis2/prop-types' +import { useConfig } from '@dhis2/app-runtime' import { NotificationIcon } from './NotificationIcon.js' -export const Notifications = ({ interpretations, messages, contextPath }) => ( - <div data-test="headerbar-notifications"> - <NotificationIcon - count={interpretations} - href={`${contextPath}/dhis-web-interpretation`} - kind="message" - dataTestId="headerbar-interpretations" - /> +export const Notifications = ({ interpretations, messages }) => { + const baseUrl = useConfig().baseUrl - <NotificationIcon - message="email" - count={messages} - href={`${contextPath}/dhis-web-messaging`} - kind="interpretation" - dataTestId="headerbar-messages" - /> + return ( + <div data-test="headerbar-notifications"> + <NotificationIcon + count={interpretations} + href={`${baseUrl}/dhis-web-interpretation`} + kind="message" + dataTestId="headerbar-interpretations" + /> - <style jsx>{` - div { - user-select: none; - display: flex; - flex-direction: row; - align-items: center; - } - `}</style> - </div> -) + <NotificationIcon + message="email" + count={messages} + href={`${baseUrl}/dhis-web-messaging`} + kind="interpretation" + dataTestId="headerbar-messages" + /> + + <style jsx>{` + div { + user-select: none; + display: flex; + flex-direction: row; + align-items: center; + } + `}</style> + </div> + ) +} Notifications.propTypes = { - contextPath: propTypes.string, interpretations: propTypes.number, messages: propTypes.number, } diff --git a/src/HeaderBar/Profile/ProfileHeader.js b/src/HeaderBar/Profile/ProfileHeader.js index 4075ea8..8515b44 100755 --- a/src/HeaderBar/Profile/ProfileHeader.js +++ b/src/HeaderBar/Profile/ProfileHeader.js @@ -1,5 +1,6 @@ import React from 'react' import propTypes from '@dhis2/prop-types' +import { useConfig } from '@dhis2/app-runtime' import i18n from '@dhis2/d2-i18n' @@ -40,36 +41,36 @@ ProfileEmail.propTypes = { children: propTypes.string, } -const ProfileEdit = ({ children, contextPath }) => ( - <a - href={`${contextPath}/dhis-web-user-profile/#/profile`} - data-test="headerbar-profile-edit-profile-link" - > - {children} +const ProfileEdit = ({ children }) => { + const baseUrl = useConfig().baseUrl + return ( + <a + href={`${baseUrl}/dhis-web-user-profile/#/profile`} + data-test="headerbar-profile-edit-profile-link" + > + {children} - <style jsx>{` - a { - color: rgba(0, 0, 0, 0.87); - font-size: 12px; - line-height: 14px; - text-decoration: underline; - cursor: pointer; - } - `}</style> - </a> -) + <style jsx>{` + a { + color: rgba(0, 0, 0, 0.87); + font-size: 12px; + line-height: 14px; + text-decoration: underline; + cursor: pointer; + } + `}</style> + </a> + ) +} ProfileEdit.propTypes = { children: propTypes.string, - contextPath: propTypes.string, } -const ProfileDetails = ({ name, email, contextPath }) => ( +const ProfileDetails = ({ name, email }) => ( <div> <ProfileName>{name}</ProfileName> <ProfileEmail>{email}</ProfileEmail> - <ProfileEdit contextPath={contextPath}> - {i18n.t('Edit profile')} - </ProfileEdit> + <ProfileEdit>{i18n.t('Edit profile')}</ProfileEdit> <style jsx>{` div { @@ -84,16 +85,15 @@ const ProfileDetails = ({ name, email, contextPath }) => ( </div> ) ProfileDetails.propTypes = { - contextPath: propTypes.string, email: propTypes.string, name: propTypes.string, } -export const ProfileHeader = ({ name, email, img, contextPath }) => ( +export const ProfileHeader = ({ name, email, img }) => ( <div> {img ? <ImageIcon src={img} /> : <TextIcon name={name} />} - <ProfileDetails name={name} email={email} contextPath={contextPath} /> + <ProfileDetails name={name} email={email} /> <style jsx>{` div { @@ -107,7 +107,6 @@ export const ProfileHeader = ({ name, email, img, contextPath }) => ( ) ProfileHeader.propTypes = { - contextPath: propTypes.string.isRequired, email: propTypes.string, img: propTypes.string, name: propTypes.string, diff --git a/src/HeaderBar/Profile/ProfileMenu.js b/src/HeaderBar/Profile/ProfileMenu.js index afeea4e..135de43 100755 --- a/src/HeaderBar/Profile/ProfileMenu.js +++ b/src/HeaderBar/Profile/ProfileMenu.js @@ -1,6 +1,7 @@ import React from 'react' import propTypes from '@dhis2/prop-types' import css from 'styled-jsx/css' +import { useConfig } from '@dhis2/app-runtime' import i18n from '@dhis2/d2-i18n' import { Card, Divider, MenuItem, colors } from '@dhis2/ui-core' @@ -22,18 +23,18 @@ const iconStyle = css.resolve` } ` -const list = [ +const getMenuList = () => [ { icon: <Settings className={iconStyle.className} />, label: i18n.t('Settings'), value: 'settings', - link: `/dhis-web-user-profile/#/settings`, + link: `dhis-web-user-profile/#/settings`, }, { icon: <Account className={iconStyle.className} />, label: i18n.t('Account'), value: 'account', - link: `/dhis-web-user-profile/#/account`, + link: `dhis-web-user-profile/#/account`, }, { icon: <Help className={iconStyle.className} />, @@ -47,77 +48,72 @@ const list = [ icon: <Info className={iconStyle.className} />, label: i18n.t('About DHIS2'), value: 'about', - link: `/dhis-web-user-profile/#/aboutPage`, + link: `dhis-web-user-profile/#/aboutPage`, }, { icon: <Exit className={iconStyle.className} />, label: i18n.t('Logout'), value: 'logout', - link: `/dhis-web-commons-security/logout.action`, + link: `dhis-web-commons-security/logout.action`, }, ] -const ProfileContents = ({ name, email, avatar, contextPath }) => ( - <Card> - <div> - <ProfileHeader - name={name} - email={email} - img={avatar} - contextPath={contextPath} - /> - <Divider margin="13px 0 7px 0" /> - <ul data-test="headerbar-profile-menu"> - {list.map(({ label, value, icon, link, nobase }) => ( - <MenuItem - href={nobase ? link : `${contextPath}${link}`} - key={`h-mi-${value}`} - label={label} - value={value} - icon={icon} - /> - ))} - </ul> - </div> +const ProfileContents = ({ name, email, avatar }) => { + const baseUrl = useConfig().baseUrl - {iconStyle.styles} - <style jsx>{` - div { - width: 100%; - padding: 0; - } + return ( + <Card> + <div> + <ProfileHeader name={name} email={email} img={avatar} /> + <Divider margin="13px 0 7px 0" /> + <ul data-test="headerbar-profile-menu"> + {getMenuList().map( + ({ label, value, icon, link, nobase }) => ( + <MenuItem + href={nobase ? link : `${baseUrl}${link}`} + key={`h-mi-${value}`} + label={label} + value={value} + icon={icon} + /> + ) + )} + </ul> + </div> - ul { - padding: 0; - margin: 0; - } + {iconStyle.styles} + <style jsx>{` + div { + width: 100%; + padding: 0; + } - a, - a:hover, - a:focus, - a:active, - a:visited { - text-decoration: none; - display: block; - } - `}</style> - </Card> -) + ul { + padding: 0; + margin: 0; + } + + a, + a:hover, + a:focus, + a:active, + a:visited { + text-decoration: none; + display: block; + } + `}</style> + </Card> + ) +} ProfileContents.propTypes = { avatar: propTypes.element, - contextPath: propTypes.string, email: propTypes.string, name: propTypes.string, } -export const ProfileMenu = ({ avatar, name, email, contextPath }) => ( +export const ProfileMenu = ({ avatar, name, email }) => ( <div data-test="headerbar-profile-menu"> - <ProfileContents - name={name} - email={email} - avatar={avatar} - contextPath={contextPath} - /> + <ProfileContents name={name} email={email} avatar={avatar} /> <style jsx>{` div { z-index: 10000; @@ -132,7 +128,6 @@ export const ProfileMenu = ({ avatar, name, email, contextPath }) => ( ) ProfileMenu.propTypes = { avatar: propTypes.element, - contextPath: propTypes.string, email: propTypes.string, name: propTypes.string, } diff --git a/src/__demo__/HeaderBar.stories.js b/src/__demo__/HeaderBar.stories.js index 69fd8ba..0c959c8 100644 --- a/src/__demo__/HeaderBar.stories.js +++ b/src/__demo__/HeaderBar.stories.js @@ -1,14 +1,18 @@ import React from 'react' import { storiesOf } from '@storybook/react' -import { CustomDataProvider } from '@dhis2/app-runtime' +import { CustomDataProvider, Provider } from '@dhis2/app-runtime' //import { Provider } from '@dhis2/app-runtime' import { HeaderBar } from '../index.js' +const mockConfig = { + baseUrl: 'https://debug.dhis2.org/dev/', + apiVersion: 33, +} + const customData = { - 'system/info': { - systemName: 'Foobar', - contextPath: 'https://play.dhis2.org/2.32.0', + 'systemSettings/applicationTitle': { + applicationTitle: 'Foobar', }, me: { name: 'John Doe', @@ -76,31 +80,31 @@ const customData = { description: '', }, { - name: 'dhis-web-importexport', - namespace: '/dhis-web-importexport', - defaultAction: '../dhis-web-importexport/index.action', + name: 'dhis-web-import-export', + namespace: '/dhis-web-import-export', + defaultAction: '../dhis-web-import-export/index.action', displayName: 'Import/Export', - icon: '../icons/dhis-web-importexport.png', + icon: '../icons/dhis-web-import-export.png', description: '', }, { name: 'WHO Metadata browser', namespace: 'WHO Metadata browser', defaultAction: - 'https://play.dhis2.org/2.32.0/api/apps/WHO-Metadata-browser/index.html', - displayName: '', + 'https://debug.dhis2.org/dev/api/apps/WHO-Metadata-browser/index.html', + displayName: 'WHO Metadata browser', icon: - 'https://play.dhis2.org/2.32.0/api/apps/WHO-Metadata-browser/icons/medicine-48.png', + 'https://debug.dhis2.org/dev/api/apps/WHO-Metadata-browser/icons/medicine-48.png', description: '', }, { name: 'Dashboard Classic', namespace: 'Dashboard Classic', defaultAction: - 'https://play.dhis2.org/2.32.0/api/apps/Dashboard-Classic/index.html', - displayName: '', + 'https://debug.dhis2.org/dev/api/apps/Dashboard-Classic/index.html', + displayName: 'Dashboard Classic', icon: - 'https://play.dhis2.org/2.32.0/api/apps/Dashboard-Classic/icon.png', + 'https://debug.dhis2.org/dev/api/apps/Dashboard-Classic/icon.png', description: 'DHIS2 Legacy Dashboard App', }, ], @@ -111,7 +115,7 @@ const customData = { }, } -const customLogo = { +const customLogoData = { ...customData, 'staticContent/logo_banner': { images: { @@ -120,26 +124,60 @@ const customLogo = { }, } +const customLocaleData = { + ...customData, + 'systemSettings/applicationTitle': { + applicationTitle: 'Le Gros Foobar', + }, + me: { + ...customData.me, + settings: { + keyUiLocale: 'fr', + }, + }, + 'action::menu/getModules': { + modules: customData['action::menu/getModules'].modules.map(mod => ({ + ...mod, + displayName: `Le ${mod.displayName}`, + })), + }, +} + storiesOf('HeaderBar', module) .add('Default', () => ( - <CustomDataProvider data={customData}> - <HeaderBar appName="Example!" /> - </CustomDataProvider> + <Provider config={mockConfig}> + <CustomDataProvider data={customData}> + <HeaderBar appName="Example!" /> + </CustomDataProvider> + </Provider> )) .add('Custom Logo (wide dimension)', () => ( - <CustomDataProvider data={customLogo}> - <HeaderBar appName="Example!" /> - </CustomDataProvider> + <Provider config={mockConfig}> + <CustomDataProvider data={customLogoData}> + <HeaderBar appName="Example!" /> + </CustomDataProvider> + </Provider> + )) + .add('Non-english user locale', () => ( + <Provider config={mockConfig}> + <CustomDataProvider data={customLocaleData}> + <HeaderBar appName="Exemple!" /> + </CustomDataProvider> + </Provider> )) .add('Loading...', () => ( - <CustomDataProvider options={{ loadForever: true }}> - <HeaderBar appName="Example!" /> - </CustomDataProvider> + <Provider config={mockConfig}> + <CustomDataProvider options={{ loadForever: true }}> + <HeaderBar appName="Example!" /> + </CustomDataProvider> + </Provider> )) .add('Error!', () => ( - <CustomDataProvider data={{}}> - <HeaderBar appName="Example!" /> - </CustomDataProvider> + <Provider config={mockConfig}> + <CustomDataProvider data={{}}> + <HeaderBar appName="Example!" /> + </CustomDataProvider> + </Provider> )) /* From 32cdbfb044d667a68de0827f8c7ab55f24e09e65 Mon Sep 17 00:00:00 2001 From: Austin McGee <947888+amcgee@users.noreply.github.com> Date: Fri, 15 May 2020 14:59:58 +0200 Subject: [PATCH 2/5] chore: remove console.log --- src/HeaderBar.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/HeaderBar.js b/src/HeaderBar.js index 71580d8..6c3f7d0 100755 --- a/src/HeaderBar.js +++ b/src/HeaderBar.js @@ -50,7 +50,6 @@ export const HeaderBar = ({ appName, className }) => { if (!loading && !error) { // TODO: This will run every render which is probably wrong! Also, setting the global locale shouldn't be done in the headerbar const locale = data.user.settings.keyUiLocale || 'en' - console.log(locale) i18n.setDefaultNamespace('default') i18n.changeLanguage(locale) } From b924ffb326e5de55ca7226ec4755f4cd01108d10 Mon Sep 17 00:00:00 2001 From: Austin McGee <947888+amcgee@users.noreply.github.com> Date: Fri, 15 May 2020 15:30:37 +0200 Subject: [PATCH 3/5] chore: fix cypress tests --- .../fixtures/HeaderBar/applicationTitle.json | 3 +++ cypress/fixtures/HeaderBar/systemInfo.json | 4 --- .../HeaderBar_contains_logo.js | 7 +++-- ...The_HeaderBar_displays_the_custom_title.js | 8 +++--- cypress/integration/HeaderBar/common/index.js | 26 ++++++++++--------- src/HeaderBar/Profile/ProfileHeader.js | 2 +- src/__e2e__/HeaderBar.stories.js | 11 +++++--- 7 files changed, 33 insertions(+), 28 deletions(-) create mode 100644 cypress/fixtures/HeaderBar/applicationTitle.json delete mode 100644 cypress/fixtures/HeaderBar/systemInfo.json diff --git a/cypress/fixtures/HeaderBar/applicationTitle.json b/cypress/fixtures/HeaderBar/applicationTitle.json new file mode 100644 index 0000000..3fe4e07 --- /dev/null +++ b/cypress/fixtures/HeaderBar/applicationTitle.json @@ -0,0 +1,3 @@ +{ + "applicationTitle": "Foobar" +} diff --git a/cypress/fixtures/HeaderBar/systemInfo.json b/cypress/fixtures/HeaderBar/systemInfo.json deleted file mode 100644 index 08e51bd..0000000 --- a/cypress/fixtures/HeaderBar/systemInfo.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "systemName": "Foobar", - "contextPath": "https://play.dhis2.org/2.32.0" -} diff --git a/cypress/integration/HeaderBar/The_HeaderBar_should_contain_a_logo_that_links_to_the_homepage/HeaderBar_contains_logo.js b/cypress/integration/HeaderBar/The_HeaderBar_should_contain_a_logo_that_links_to_the_homepage/HeaderBar_contains_logo.js index ad86c86..17502d5 100644 --- a/cypress/integration/HeaderBar/The_HeaderBar_should_contain_a_logo_that_links_to_the_homepage/HeaderBar_contains_logo.js +++ b/cypress/integration/HeaderBar/The_HeaderBar_should_contain_a_logo_that_links_to_the_homepage/HeaderBar_contains_logo.js @@ -1,14 +1,13 @@ import '../common/index.js' import { Then } from 'cypress-cucumber-preprocessor/steps' +import { baseUrl } from '../common/index.js' Then('the HeaderBar should display the dhis2 logo', () => { cy.get('[data-test="headerbar-logo"]').should('be.visible') }) Then('the logo should link to the homepage', () => { - cy.get('@systemInfoFixture').then(({ contextPath }) => { - cy.get('[data-test="headerbar-logo"] a').then($a => { - expect($a.attr('href')).to.equal(contextPath) - }) + cy.get('[data-test="headerbar-logo"] a').then($a => { + expect($a.attr('href')).to.equal(baseUrl) }) }) diff --git a/cypress/integration/HeaderBar/The_HeaderBar_should_display_the_title_provided_by_the_backend_and_the_app/The_HeaderBar_displays_the_custom_title.js b/cypress/integration/HeaderBar/The_HeaderBar_should_display_the_title_provided_by_the_backend_and_the_app/The_HeaderBar_displays_the_custom_title.js index b18ef51..0c546a7 100644 --- a/cypress/integration/HeaderBar/The_HeaderBar_should_display_the_title_provided_by_the_backend_and_the_app/The_HeaderBar_displays_the_custom_title.js +++ b/cypress/integration/HeaderBar/The_HeaderBar_should_display_the_title_provided_by_the_backend_and_the_app/The_HeaderBar_displays_the_custom_title.js @@ -3,13 +3,13 @@ import { Then, Given } from 'cypress-cucumber-preprocessor/steps' Given( 'the custom title is {string} and the app title is "Example!"', - systemName => { - cy.fixture('HeaderBar/systemInfo') + applicationTitle => { + cy.fixture('HeaderBar/applicationTitle') .then(response => ({ ...response, - systemName, + applicationTitle, })) - .as('systemInfoFixture') + .as('applicationTitleFixture') } ) diff --git a/cypress/integration/HeaderBar/common/index.js b/cypress/integration/HeaderBar/common/index.js index b98f991..4d93466 100644 --- a/cypress/integration/HeaderBar/common/index.js +++ b/cypress/integration/HeaderBar/common/index.js @@ -1,29 +1,31 @@ import { Before, Given } from 'cypress-cucumber-preprocessor/steps' +export const baseUrl = 'https://domain.tld/' + /** * Will be executed before any `Given` statement, * so these can be overriden by using a different fixture, e. g: * * Given('foo bar baz', () => { - * cy.fixture('HeaderBar/systemInfoBarbaz').as('systemInfoFixture') + * cy.fixture('HeaderBar/applicationTitleBarbaz').as('applicationTitleFixture') * }) * * or * * Given('foo bar baz', () => { - * cy.fixture('HeaderBar/systemInfo') + * cy.fixture('HeaderBar/me') * then(response => ({ * ...response, * foo: { * ...response.foo, * bar: 'baz' * } - * })).as('systemInfoFixture') + * })).as('meFixture') * }) * */ Before(() => { - cy.fixture('HeaderBar/systemInfo').as('systemInfoFixture') + cy.fixture('HeaderBar/applicationTitle').as('applicationTitleFixture') cy.fixture('HeaderBar/me').as('meFixture') cy.fixture('HeaderBar/getModules').as('modulesFixture') cy.fixture('HeaderBar/dashboard').as('dashboardFixture') @@ -33,37 +35,37 @@ Before(() => { Given('the HeaderBar loads without an error', () => { cy.server() - cy.get('@systemInfoFixture').then(fx => { + cy.get('@applicationTitleFixture').then(fx => { cy.route({ - url: 'https://domain.tld/api/system/info', + url: `${baseUrl}api/systemSettings/applicationTitle`, response: fx, - }).as('systemInfo') + }).as('applicationTitle') }) cy.get('@meFixture').then(fx => { cy.route({ - url: 'https://domain.tld/api/me', + url: `${baseUrl}api/me`, response: fx, - }).as('systemInfo') + }).as('me') }) cy.get('@modulesFixture').then(fx => { cy.route({ - url: 'https://domain.tld/dhis-web-commons/menu/getModules.action', + url: `${baseUrl}dhis-web-commons/menu/getModules.action`, response: fx, }).as('modules') }) cy.get('@dashboardFixture').then(fx => { cy.route({ - url: 'https://domain.tld/api/me/dashboard', + url: `${baseUrl}api/me/dashboard`, response: fx, }).as('dashboard') }) cy.get('@logoFixture').then(fx => { cy.route({ - url: 'https://domain.tld/api/staticContent/logo_banner', + url: `${baseUrl}api/staticContent/logo_banner`, response: fx, }).as('logo_banner') }) diff --git a/src/HeaderBar/Profile/ProfileHeader.js b/src/HeaderBar/Profile/ProfileHeader.js index 8515b44..46dc83e 100755 --- a/src/HeaderBar/Profile/ProfileHeader.js +++ b/src/HeaderBar/Profile/ProfileHeader.js @@ -45,7 +45,7 @@ const ProfileEdit = ({ children }) => { const baseUrl = useConfig().baseUrl return ( <a - href={`${baseUrl}/dhis-web-user-profile/#/profile`} + href={`${baseUrl}dhis-web-user-profile/#/profile`} data-test="headerbar-profile-edit-profile-link" > {children} diff --git a/src/__e2e__/HeaderBar.stories.js b/src/__e2e__/HeaderBar.stories.js index 0ffcbe3..a290028 100644 --- a/src/__e2e__/HeaderBar.stories.js +++ b/src/__e2e__/HeaderBar.stories.js @@ -1,10 +1,15 @@ import React from 'react' import { storiesOf } from '@storybook/react' import { HeaderBar } from '../index.js' -import { DataProvider } from '@dhis2/app-runtime' +import { Provider } from '@dhis2/app-runtime' storiesOf('HeaderBarTesting', module).add('Default', () => ( - <DataProvider baseUrl="https://domain.tld" apiVersion=""> + <Provider + config={{ + baseUrl: 'https://domain.tld/', + apiVersion: '', + }} + > <HeaderBar appName="Example!" /> - </DataProvider> + </Provider> )) From 728c5621f161892e4dfd4ea130d82b6794935e67 Mon Sep 17 00:00:00 2001 From: Austin McGee <947888+amcgee@users.noreply.github.com> Date: Fri, 15 May 2020 15:55:05 +0200 Subject: [PATCH 4/5] chore: use joinPath instead of assuming a trailing slash --- src/HeaderBar.js | 3 ++- src/HeaderBar/Apps.js | 3 ++- src/HeaderBar/Notifications.js | 5 +++-- src/HeaderBar/Profile/ProfileHeader.js | 3 ++- src/HeaderBar/Profile/ProfileMenu.js | 3 ++- src/HeaderBar/joinPath.js | 4 ++++ 6 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 src/HeaderBar/joinPath.js diff --git a/src/HeaderBar.js b/src/HeaderBar.js index 6c3f7d0..38ebe95 100755 --- a/src/HeaderBar.js +++ b/src/HeaderBar.js @@ -14,6 +14,7 @@ import { Title } from './HeaderBar/Title.js' import { Notifications } from './HeaderBar/Notifications.js' import i18n from './locales' +import { joinPath } from './HeaderBar/joinPath.js' const query = { title: { @@ -38,7 +39,7 @@ export const HeaderBar = ({ appName, className }) => { const getPath = path => path.startsWith('http:') || path.startsWith('https:') ? path - : `${baseUrl}api/${path}` + : joinPath(baseUrl, 'api', path) return data?.apps.modules.map(app => ({ ...app, diff --git a/src/HeaderBar/Apps.js b/src/HeaderBar/Apps.js index 3b90103..cfd49e3 100755 --- a/src/HeaderBar/Apps.js +++ b/src/HeaderBar/Apps.js @@ -10,6 +10,7 @@ import { Apps as AppsIcon } from '../icons/Apps.js' import { Cancel } from '../icons/Cancel.js' import css from 'styled-jsx/css' +import { joinPath } from './joinPath.js' const appIcon = css.resolve` svg { @@ -69,7 +70,7 @@ function Search({ value, onChange, onIconClick }) { </span> <span> - <a href={`${baseUrl}dhis-web-menu-management`}> + <a href={joinPath(baseUrl, 'dhis-web-menu-management')}> <Settings className={settingsIcon.className} /> </a> </span> diff --git a/src/HeaderBar/Notifications.js b/src/HeaderBar/Notifications.js index a079988..97138e1 100755 --- a/src/HeaderBar/Notifications.js +++ b/src/HeaderBar/Notifications.js @@ -3,6 +3,7 @@ import propTypes from '@dhis2/prop-types' import { useConfig } from '@dhis2/app-runtime' import { NotificationIcon } from './NotificationIcon.js' +import { joinPath } from './joinPath.js' export const Notifications = ({ interpretations, messages }) => { const baseUrl = useConfig().baseUrl @@ -11,7 +12,7 @@ export const Notifications = ({ interpretations, messages }) => { <div data-test="headerbar-notifications"> <NotificationIcon count={interpretations} - href={`${baseUrl}/dhis-web-interpretation`} + href={joinPath(baseUrl, 'dhis-web-interpretation')} kind="message" dataTestId="headerbar-interpretations" /> @@ -19,7 +20,7 @@ export const Notifications = ({ interpretations, messages }) => { <NotificationIcon message="email" count={messages} - href={`${baseUrl}/dhis-web-messaging`} + href={joinPath(baseUrl, 'dhis-web-messaging')} kind="interpretation" dataTestId="headerbar-messages" /> diff --git a/src/HeaderBar/Profile/ProfileHeader.js b/src/HeaderBar/Profile/ProfileHeader.js index 46dc83e..76da9fe 100755 --- a/src/HeaderBar/Profile/ProfileHeader.js +++ b/src/HeaderBar/Profile/ProfileHeader.js @@ -6,6 +6,7 @@ import i18n from '@dhis2/d2-i18n' import { TextIcon } from '../TextIcon.js' import { ImageIcon } from '../ImageIcon.js' +import { joinPath } from '../joinPath.js' const ProfileName = ({ children }) => ( <div data-test="headerbar-profile-username"> @@ -45,7 +46,7 @@ const ProfileEdit = ({ children }) => { const baseUrl = useConfig().baseUrl return ( <a - href={`${baseUrl}dhis-web-user-profile/#/profile`} + href={joinPath(baseUrl, 'dhis-web-user-profile/#/profile')} data-test="headerbar-profile-edit-profile-link" > {children} diff --git a/src/HeaderBar/Profile/ProfileMenu.js b/src/HeaderBar/Profile/ProfileMenu.js index 135de43..90f4fbb 100755 --- a/src/HeaderBar/Profile/ProfileMenu.js +++ b/src/HeaderBar/Profile/ProfileMenu.js @@ -13,6 +13,7 @@ import { Exit } from '../../icons/Exit.js' import { Account } from '../../icons/Account.js' import { ProfileHeader } from './ProfileHeader.js' +import { joinPath } from '../joinPath.js' const iconStyle = css.resolve` svg { @@ -70,7 +71,7 @@ const ProfileContents = ({ name, email, avatar }) => { {getMenuList().map( ({ label, value, icon, link, nobase }) => ( <MenuItem - href={nobase ? link : `${baseUrl}${link}`} + href={nobase ? link : joinPath(baseUrl, link)} key={`h-mi-${value}`} label={label} value={value} diff --git a/src/HeaderBar/joinPath.js b/src/HeaderBar/joinPath.js new file mode 100644 index 0000000..9d34993 --- /dev/null +++ b/src/HeaderBar/joinPath.js @@ -0,0 +1,4 @@ +export const joinPath = (...parts) => { + const realParts = parts.filter(part => !!part) + return realParts.map(part => part.replace(/^\/+|\/+$/g, '')).join('/') +} From ab5375cea3877911dacbf85f3bb9d9201dc91fb5 Mon Sep 17 00:00:00 2001 From: Austin McGee <947888+amcgee@users.noreply.github.com> Date: Fri, 15 May 2020 16:04:04 +0200 Subject: [PATCH 5/5] chore: clean up baseUrl references, convert missed contextPath --- .../HeaderBar_contains_logo.js | 1 - src/HeaderBar.js | 4 ++-- src/HeaderBar/Apps.js | 2 +- src/HeaderBar/Logo.js | 2 +- src/HeaderBar/Notifications.js | 2 +- src/HeaderBar/Profile.js | 18 +++++++++--------- src/HeaderBar/Profile/ProfileHeader.js | 2 +- src/HeaderBar/Profile/ProfileMenu.js | 2 +- 8 files changed, 16 insertions(+), 17 deletions(-) diff --git a/cypress/integration/HeaderBar/The_HeaderBar_should_contain_a_logo_that_links_to_the_homepage/HeaderBar_contains_logo.js b/cypress/integration/HeaderBar/The_HeaderBar_should_contain_a_logo_that_links_to_the_homepage/HeaderBar_contains_logo.js index 17502d5..3e92021 100644 --- a/cypress/integration/HeaderBar/The_HeaderBar_should_contain_a_logo_that_links_to_the_homepage/HeaderBar_contains_logo.js +++ b/cypress/integration/HeaderBar/The_HeaderBar_should_contain_a_logo_that_links_to_the_homepage/HeaderBar_contains_logo.js @@ -1,4 +1,3 @@ -import '../common/index.js' import { Then } from 'cypress-cucumber-preprocessor/steps' import { baseUrl } from '../common/index.js' diff --git a/src/HeaderBar.js b/src/HeaderBar.js index 38ebe95..25d84fd 100755 --- a/src/HeaderBar.js +++ b/src/HeaderBar.js @@ -32,7 +32,7 @@ const query = { } export const HeaderBar = ({ appName, className }) => { - const baseUrl = useConfig().baseUrl + const { baseUrl } = useConfig() const { loading, error, data } = useDataQuery(query) const apps = useMemo(() => { @@ -72,7 +72,7 @@ export const HeaderBar = ({ appName, className }) => { messages={data.notifications.unreadMessageConversations} /> <Apps apps={apps} /> - <Profile user={data.user} /> + <Profile user={data.user} baseUrl={baseUrl} /> </> )} diff --git a/src/HeaderBar/Apps.js b/src/HeaderBar/Apps.js index cfd49e3..5cf3d0c 100755 --- a/src/HeaderBar/Apps.js +++ b/src/HeaderBar/Apps.js @@ -54,7 +54,7 @@ TrailIcon.propTypes = { } function Search({ value, onChange, onIconClick }) { - const baseUrl = useConfig().baseUrl + const { baseUrl } = useConfig() return ( <div> diff --git a/src/HeaderBar/Logo.js b/src/HeaderBar/Logo.js index 2a6e726..b61872b 100755 --- a/src/HeaderBar/Logo.js +++ b/src/HeaderBar/Logo.js @@ -4,7 +4,7 @@ import { useConfig } from '@dhis2/app-runtime' import { LogoImage } from './LogoImage.js' export const Logo = () => { - const baseUrl = useConfig().baseUrl + const { baseUrl } = useConfig() return ( <div data-test="headerbar-logo"> diff --git a/src/HeaderBar/Notifications.js b/src/HeaderBar/Notifications.js index 97138e1..deb4b4d 100755 --- a/src/HeaderBar/Notifications.js +++ b/src/HeaderBar/Notifications.js @@ -6,7 +6,7 @@ import { NotificationIcon } from './NotificationIcon.js' import { joinPath } from './joinPath.js' export const Notifications = ({ interpretations, messages }) => { - const baseUrl = useConfig().baseUrl + const { baseUrl } = useConfig() return ( <div data-test="headerbar-notifications"> diff --git a/src/HeaderBar/Profile.js b/src/HeaderBar/Profile.js index f484d00..5911eb4 100755 --- a/src/HeaderBar/Profile.js +++ b/src/HeaderBar/Profile.js @@ -5,13 +5,14 @@ import { ProfileMenu } from './Profile/ProfileMenu.js' import { TextIcon } from './TextIcon.js' import { ImageIcon } from './ImageIcon.js' +import { joinPath } from './joinPath.js' -function avatarPath(avatar, contextPath) { +function avatarPath(avatar, baseUrl) { if (!avatar) { return null } - return `${contextPath}/api/fileResources/${avatar.id}/data` + return joinPath(baseUrl, 'api/fileResources', avatar.id, 'data') } export default class Profile extends React.Component { @@ -35,8 +36,8 @@ export default class Profile extends React.Component { onToggle = () => this.setState({ show: !this.state.show }) - userIcon(me, contextPath) { - const avatar = avatarPath(me.avatar, contextPath) + userIcon(me, baseUrl) { + const avatar = avatarPath(me.avatar, baseUrl) if (avatar) { return ( @@ -58,21 +59,20 @@ export default class Profile extends React.Component { } render() { - const { user, contextPath } = this.props + const { user, baseUrl } = this.props return ( <div ref={c => (this.elContainer = c)} data-test="headerbar-profile" > - {this.userIcon(user, contextPath)} + {this.userIcon(user, baseUrl)} {this.state.show ? ( <ProfileMenu - avatar={avatarPath(user.avatar, contextPath)} + avatar={avatarPath(user.avatar, baseUrl)} name={user.name} email={user.email} - contextPath={contextPath} /> ) : null} @@ -90,6 +90,6 @@ export default class Profile extends React.Component { } Profile.propTypes = { - contextPath: propTypes.string.isRequired, + baseUrl: propTypes.string.isRequired, user: propTypes.object.isRequired, } diff --git a/src/HeaderBar/Profile/ProfileHeader.js b/src/HeaderBar/Profile/ProfileHeader.js index 76da9fe..6782e15 100755 --- a/src/HeaderBar/Profile/ProfileHeader.js +++ b/src/HeaderBar/Profile/ProfileHeader.js @@ -43,7 +43,7 @@ ProfileEmail.propTypes = { } const ProfileEdit = ({ children }) => { - const baseUrl = useConfig().baseUrl + const { baseUrl } = useConfig() return ( <a href={joinPath(baseUrl, 'dhis-web-user-profile/#/profile')} diff --git a/src/HeaderBar/Profile/ProfileMenu.js b/src/HeaderBar/Profile/ProfileMenu.js index 90f4fbb..75f3da8 100755 --- a/src/HeaderBar/Profile/ProfileMenu.js +++ b/src/HeaderBar/Profile/ProfileMenu.js @@ -60,7 +60,7 @@ const getMenuList = () => [ ] const ProfileContents = ({ name, email, avatar }) => { - const baseUrl = useConfig().baseUrl + const { baseUrl } = useConfig() return ( <Card>