diff --git a/blueocean-core-js/src/js/components/ContentPageHeader.jsx b/blueocean-core-js/src/js/components/ContentPageHeader.jsx index 4528fd30e8f..b8a298f9272 100644 --- a/blueocean-core-js/src/js/components/ContentPageHeader.jsx +++ b/blueocean-core-js/src/js/components/ContentPageHeader.jsx @@ -9,27 +9,47 @@ import { import { BlueLogo } from './BlueLogo'; // Wrap an array of elements in a parent element without requiring a bunch "key" props +// FIXME: This should be strengthened a little, and promoted to JDL with some tests export function _wrap(children, elementOrComponent = 'div', props = {}) { if (!children) { return null; } + const childArray = Array.isArray(children) ? children : [children]; return React.createElement(elementOrComponent, props, ...childArray); } -export const ContentPageHeader = props => { +export const SiteHeader = props => { const topNavLinks = _wrap(props.topNavLinks, 'nav'); const userComponents = _wrap(props.userComponents, 'div', { className: 'ContentPageHeader-user' }); - const pageTabLinks = _wrap(props.pageTabLinks, PageTabs); return ( - +
{ topNavLinks } { userComponents } + + ); +}; + +SiteHeader.propTypes = { + topNavLinks: PropTypes.node, + userComponents: PropTypes.node, + children: PropTypes.node, +}; + +export const ContentPageHeader = props => { + const pageTabLinks = _wrap( + props.pageTabLinks, + PageTabs, + { base: props.pageTabBase } + ); + + return ( +
{ props.children } @@ -41,10 +61,9 @@ export const ContentPageHeader = props => { }; ContentPageHeader.propTypes = { - topNavLinks: PropTypes.node, - userComponents: PropTypes.node, pageTabLinks: PropTypes.node, children: PropTypes.node, + pageTabBase: PropTypes.string, }; export default ContentPageHeader; diff --git a/blueocean-core-js/src/js/components/stories/ContentPageHeaderStories.js b/blueocean-core-js/src/js/components/stories/ContentPageHeaderStories.js index a2f9584e4e9..b84c2a817a2 100644 --- a/blueocean-core-js/src/js/components/stories/ContentPageHeaderStories.js +++ b/blueocean-core-js/src/js/components/stories/ContentPageHeaderStories.js @@ -4,6 +4,7 @@ import React, { Component, PropTypes } from 'react'; import { storiesOf } from '@kadira/storybook'; import { BlueLogo } from '../BlueLogo'; import { ContentPageHeader } from '../ContentPageHeader'; +import { SiteHeader } from '../ContentPageHeader'; import { ResultPageHeader } from '../ResultPageHeader'; import { LiveStatusIndicator, WeatherIcon } from '@jenkins-cd/design-language'; @@ -46,11 +47,12 @@ function pageHeaderDashboard() { ]; return ( - -

Dashboard

-
+
+ + +

Dashboard

+
+
); } @@ -75,13 +77,13 @@ function pageHeaderPipeline() { ]; return ( - - -

Lorem / Ipsum / Pipelineum

-
+
+ + + +

Lorem / Ipsum / Pipelineum

+
+
); } diff --git a/blueocean-core-js/src/js/components/stories/index.js b/blueocean-core-js/src/js/components/stories/index.js index 018b130fe64..066a6ee5acf 100644 --- a/blueocean-core-js/src/js/components/stories/index.js +++ b/blueocean-core-js/src/js/components/stories/index.js @@ -6,4 +6,4 @@ ext.store.init({ }, }); -require('./ContentPageHeaderStories'); // TODO: Split this into two +require('./ContentPageHeaderStories'); diff --git a/blueocean-core-js/src/js/index.js b/blueocean-core-js/src/js/index.js index 51d8d72c824..2067acf106d 100644 --- a/blueocean-core-js/src/js/index.js +++ b/blueocean-core-js/src/js/index.js @@ -51,7 +51,7 @@ export { BlueLogo, BlueOceanIcon, } from './components/BlueLogo'; -export { ContentPageHeader } from './components/ContentPageHeader'; +export { ContentPageHeader, SiteHeader } from './components/ContentPageHeader'; export { ResultPageHeader } from './components/ResultPageHeader'; // Create and export the SSE connection that will be shared by other diff --git a/blueocean-dashboard/src/main/js/components/PipelinePage.jsx b/blueocean-dashboard/src/main/js/components/PipelinePage.jsx index 6d96d055fe8..c4fb0e53d85 100644 --- a/blueocean-dashboard/src/main/js/components/PipelinePage.jsx +++ b/blueocean-dashboard/src/main/js/components/PipelinePage.jsx @@ -10,7 +10,7 @@ import { TabLink, WeatherIcon, } from '@jenkins-cd/design-language'; -import { i18nTranslator, NotFound, User, Paths } from '@jenkins-cd/blueocean-core-js'; +import { i18nTranslator, NotFound, User, Paths, ContentPageHeader } from '@jenkins-cd/blueocean-core-js'; import { Icon } from '@jenkins-cd/react-material-icons'; import PageLoading from './PageLoading'; import { buildOrganizationUrl, buildPipelineUrl, buildClassicConfigUrl } from '../util/UrlUtils'; @@ -74,40 +74,43 @@ export class PipelinePage extends Component { setTitle(`${organization} / ${name}`); const baseUrl = buildPipelineUrl(organization, fullName); - return ( - - - {!isReady && } - {!isReady && - - <h1><Link to={orgUrl}>{organization}</Link> - <span> / </span></h1> - } - {isReady && - - <WeatherIcon score={pipeline.weatherScore} size="large" /> - <h1> - <Link to={orgUrl} query={location.query}>{organization}</Link> - <span> / </span> + + const pageTabLinks = [ + <TabLink to="/activity">{ translate('pipelinedetail.common.tab.activity', { defaultValue: 'Activity' }) }</TabLink>, + <TabLink to="/branches">{ translate('pipelinedetail.common.tab.branches', { defaultValue: 'Branches' }) }</TabLink>, + <TabLink to="/pr">{ translate('pipelinedetail.common.tab.pullrequests', { defaultValue: 'Pull Requests' }) }</TabLink>, + ]; + + const pageHeader = isReady ? ( + <ContentPageHeader pageTabBase={baseUrl} pageTabLinks={pageTabLinks}> + <WeatherIcon score={pipeline.weatherScore} /> + <h1> + <Link to={orgUrl} query={location.query}>{organization}</Link> + <span> / </span> <Link to={activityUrl} query={location.query}> <ExpandablePath path={fullDisplayName} hideFirst className="dark-theme" iconSize={20} /> </Link> - </h1> - <Extensions.Renderer - extensionPoint="jenkins.pipeline.detail.header.action" - store={this.context.store} - pipeline={pipeline} - /> - {classicConfigLink(pipeline)} - - } - - - { translate('pipelinedetail.common.tab.activity', { defaultValue: 'Activity' }) } - { translate('pipelinedetail.common.tab.branches', { defaultValue: 'Branches' }) } - { translate('pipelinedetail.common.tab.pullrequests', { defaultValue: 'Pull Requests' }) } - - + + + {classicConfigLink(pipeline)} + + ) : ( + +

+ {organization} + / +

+
+ ); + + return ( + + { pageHeader } + {!isReady && } {isReady && React.cloneElement(this.props.children, { pipeline, setTitle, t: translate, locale: translate.lng })} ); diff --git a/blueocean-dashboard/src/main/js/components/Pipelines.jsx b/blueocean-dashboard/src/main/js/components/Pipelines.jsx index 7c3fd032072..4b16133c1a7 100644 --- a/blueocean-dashboard/src/main/js/components/Pipelines.jsx +++ b/blueocean-dashboard/src/main/js/components/Pipelines.jsx @@ -1,14 +1,13 @@ - -import React, { Component, PropTypes } from 'react'; -import { Link } from 'react-router'; -import { Page, PageHeader, Table, Title } from '@jenkins-cd/design-language'; -import { i18nTranslator } from '@jenkins-cd/blueocean-core-js'; +import React, {Component, PropTypes} from 'react'; +import {Link} from 'react-router'; +import {Page, PageHeader, Table, Title} from '@jenkins-cd/design-language'; +import {i18nTranslator, ContentPageHeader} from '@jenkins-cd/blueocean-core-js'; import Extensions from '@jenkins-cd/js-extensions'; import CreatePipelineLink from './CreatePipelineLink'; import PipelineRowItem from './PipelineRowItem'; import PageLoading from './PageLoading'; -import { observer } from 'mobx-react'; +import {observer} from 'mobx-react'; const translate = i18nTranslator('blueocean-dashboard'); @@ -40,73 +39,72 @@ export class Pipelines extends Component { render() { const pipelines = this.pager.data; - const { organization, location = {} } = this.context.params; + const {organization, location = {}} = this.context.params; const orgLink = organization ? {organization} : ''; const headers = [ - { label: translate('home.pipelineslist.header.name', { defaultValue: 'Name' }), className: 'name-col' }, - translate('home.pipelineslist.header.health', { defaultValue: 'Health' }), - translate('home.pipelineslist.header.branches', { defaultValue: 'Branches' }), - translate('home.pipelineslist.header.pullrequests', { defaultValue: 'PR' }), - { label: '', className: 'actions-col' }, + {label: translate('home.pipelineslist.header.name', {defaultValue: 'Name'}), className: 'name-col'}, + translate('home.pipelineslist.header.health', {defaultValue: 'Health'}), + translate('home.pipelineslist.header.branches', {defaultValue: 'Branches'}), + translate('home.pipelineslist.header.pullrequests', {defaultValue: 'PR'}), + {label: '', className: 'actions-col'}, ]; return ( - - {!pipelines || this.pager.pending && } - + <ContentPageHeader> + <div className="u-flex-grow"> <h1> - <Link - to="/" - query={location.query} - > - { translate('home.header.dashboard', { defaultValue: 'Dashboard' }) } + <Link to="/" query={location.query}> + { translate('home.header.dashboard', {defaultValue: 'Dashboard'}) } </Link> { organization && ' / ' } { organization && orgLink } </h1> - <Extensions.Renderer extensionPoint="jenkins.pipeline.create.action"> - <CreatePipelineLink /> - </Extensions.Renderer> - - +
+ + + + + + {!pipelines || this.pager.pending && } +
{ /* TODO: need to adjust Extensions to make store available */ } { pipelines && - pipelines.map(pipeline => { - const key = pipeline._links.self.href; - return ( - - ); - }) + pipelines.map(pipeline => { + const key = pipeline._links.self.href; + return ( + + ); + }) }
{ pipelines && - + }
@@ -114,7 +112,7 @@ export class Pipelines extends Component { } } -const { func, object } = PropTypes; +const {func, object} = PropTypes; Pipelines.contextTypes = { config: object, diff --git a/blueocean-web/src/main/js/main.jsx b/blueocean-web/src/main/js/main.jsx index 1a78659a6b5..7d55bbb278e 100644 --- a/blueocean-web/src/main/js/main.jsx +++ b/blueocean-web/src/main/js/main.jsx @@ -2,7 +2,7 @@ import React, { Component, PropTypes } from 'react'; import { render } from 'react-dom'; import { Router, Route, Link, useRouterHistory, IndexRedirect } from 'react-router'; import { createHistory } from 'history'; -import { i18nTranslator, AppConfig, Security, UrlConfig, Utils, sseService, locationService, NotFound } from '@jenkins-cd/blueocean-core-js'; +import { i18nTranslator, AppConfig, Security, UrlConfig, Utils, sseService, locationService, NotFound, SiteHeader } from '@jenkins-cd/blueocean-core-js'; import Extensions from '@jenkins-cd/js-extensions'; import { Provider, configureStore, combineReducers} from './redux'; @@ -66,23 +66,27 @@ class App extends Component { render() { const { location } = this.context; - var pipeCaption = translate('pipelines', { + + const pipeCaption = translate('pipelines', { defaultValue: 'Pipelines', }); + + const topNavLinks = [ + {pipeCaption}, + , + ]; + + const userComponents = [ +
+ { loginOrLogout(translate) } +
+ ]; + return (
-
-
- - -
- { loginOrLogout(translate) } -
-
-
+ + +
{this.props.children /* Set by react-router */ }