Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core/nav): Components for new navigation categories (#8150)
* feat(core/nav): Introduce new nav item component * feat(core/nav): UI router wrapper for nav category * feat(core/nav): css * feat(core/nav): Upgrade @spninaker/mocks * feat(core/nav): Upgrade @spninaker/mocks * feat(core/nav): Write unit tests * feat(core/nav): Sync import format * feat(core/nav): Refector NavRoute to function copmponent * feat(core/nav): useDataSrouce hook and rebase * fix tests * Update tests * update imports * feat(core/nav): Propogate active styles * Update test * Use uirouter hook * Move css to styleguide classes * Move css to styleguide classes
- Loading branch information
1 parent
895272c
commit 21488bc
Showing
4 changed files
with
243 additions
and
0 deletions.
There are no files selected for viewing
138 changes: 138 additions & 0 deletions
138
app/scripts/modules/core/src/application/nav/NavCategory.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import React from 'react'; | ||
import { mount } from 'enzyme'; | ||
import { BehaviorSubject } from 'rxjs'; | ||
|
||
import { mockEntityTags, mockServerGroupDataSourceConfig, mockPipelineDataSourceConfig } from '@spinnaker/mocks'; | ||
import { Application, ApplicationModelBuilder } from '../../application'; | ||
import { ApplicationDataSource, IDataSourceConfig } from '../service/applicationDataSource'; | ||
import { IEntityTags, IServerGroup, IPipeline } from '../../domain'; | ||
import { NavCategory } from './NavCategory'; | ||
|
||
describe('NavCategory', () => { | ||
const buildApp = <T,>(config: IDataSourceConfig<T>): Application => | ||
ApplicationModelBuilder.createApplicationForTests('testapp', config); | ||
|
||
it('should render a datasources icon', () => { | ||
const app = buildApp<IServerGroup>(mockServerGroupDataSourceConfig); | ||
const category = app.getDataSource('serverGroups'); | ||
category.iconName = 'spMenuClusters'; | ||
|
||
const wrapper = mount(<NavCategory app={app} category={category} isActive={false} />); | ||
const nodes = wrapper.children(); | ||
const icon = nodes.childAt(1).children(); | ||
expect(icon.find('svg').length).toEqual(1); | ||
}); | ||
|
||
it('should render a placeholder when there is icon', () => { | ||
const app = buildApp<IServerGroup>(mockServerGroupDataSourceConfig); | ||
const category = app.getDataSource('serverGroups'); | ||
|
||
const wrapper = mount(<NavCategory app={app} category={category} isActive={false} />); | ||
const nodes = wrapper.children(); | ||
const icon = nodes.childAt(1).children(); | ||
expect(icon.find('svg').length).toEqual(0); | ||
}); | ||
|
||
it('should render running tasks badge', () => { | ||
const app = buildApp<IPipeline>(mockPipelineDataSourceConfig); | ||
const category = app.getDataSource('executions'); | ||
app.dataSources.push({ ...category, key: 'runningExecutions' } as ApplicationDataSource<IPipeline>); | ||
app.getDataSource(category.badge).status$ = new BehaviorSubject({ | ||
status: 'FETCHED', | ||
loaded: true, | ||
lastRefresh: 0, | ||
error: null, | ||
data: [mockPipelineDataSourceConfig, mockPipelineDataSourceConfig], | ||
}); | ||
|
||
const wrapper = mount(<NavCategory app={app} category={category} isActive={false} />); | ||
const nodes = wrapper.children(); | ||
expect(nodes.find('.badge-running-count').length).toBe(1); | ||
expect(nodes.find('.badge-none').length).toBe(0); | ||
|
||
const text = nodes.childAt(0).getDOMNode(); | ||
expect(text.textContent).toBe('2'); | ||
}); | ||
|
||
it('should not render running tasks badge if there are none', () => { | ||
const app = buildApp<IPipeline>(mockPipelineDataSourceConfig); | ||
const category = app.getDataSource('executions'); | ||
app.dataSources.push({ ...category, key: 'runningExecutions' } as ApplicationDataSource<IPipeline>); | ||
|
||
const wrapper = mount(<NavCategory app={app} category={category} isActive={false} />); | ||
const nodes = wrapper.children(); | ||
expect(nodes.find('.badge-running-count').length).toBe(0); | ||
expect(nodes.find('.badge-none').length).toBe(1); | ||
|
||
const text = nodes.childAt(0).getDOMNode(); | ||
expect(text.textContent).toBe(''); | ||
}); | ||
|
||
it('subscribes to runningCount updates', () => { | ||
const app = buildApp<IPipeline>(mockPipelineDataSourceConfig); | ||
const category = app.getDataSource('executions'); | ||
app.dataSources.push({ ...category, key: 'runningExecutions' } as ApplicationDataSource<IPipeline>); | ||
|
||
const wrapper = mount(<NavCategory app={app} category={category} isActive={false} />); | ||
const nodes = wrapper.children(); | ||
expect(nodes.find('.badge-running-count').length).toBe(0); | ||
expect(nodes.find('.badge-none').length).toBe(1); | ||
|
||
const text = nodes.childAt(0).getDOMNode(); | ||
expect(text.textContent).toBe(''); | ||
|
||
const updatedApp = buildApp<IPipeline>(mockPipelineDataSourceConfig); | ||
updatedApp.dataSources.push({ | ||
...category, | ||
key: 'runningExecutions', | ||
} as ApplicationDataSource<IPipeline>); | ||
updatedApp.getDataSource(category.badge).status$ = new BehaviorSubject({ | ||
status: 'FETCHED', | ||
loaded: true, | ||
lastRefresh: 0, | ||
error: null, | ||
data: [mockPipelineDataSourceConfig, mockPipelineDataSourceConfig], | ||
}); | ||
|
||
wrapper.setProps({ | ||
app: updatedApp, | ||
category, | ||
isActive: false, | ||
}); | ||
wrapper.update(); | ||
|
||
const newNodes = wrapper.children(); | ||
expect(newNodes.find('.badge-running-count').length).toBe(1); | ||
expect(newNodes.find('.badge-none').length).toBe(0); | ||
|
||
const newText = nodes.childAt(0).getDOMNode(); | ||
expect(newText.textContent).toBe('2'); | ||
}); | ||
|
||
it('should subscribe to alert updates', () => { | ||
const app = buildApp<IServerGroup>(mockServerGroupDataSourceConfig); | ||
const category = app.getDataSource('serverGroups'); | ||
const wrapper = mount(<NavCategory app={app} category={category} isActive={false} />); | ||
const nodes = wrapper.children(); | ||
const tags: IEntityTags[] = nodes.find('DataSourceNotifications').prop('tags'); | ||
expect(tags.length).toEqual(0); | ||
|
||
const newCategory = { | ||
...category, | ||
alerts: [mockEntityTags], | ||
entityTags: [mockEntityTags], | ||
}; | ||
|
||
wrapper.setProps({ | ||
app, | ||
category: newCategory, | ||
isActive: false, | ||
}); | ||
|
||
const newTags: IEntityTags[] = wrapper | ||
.children() | ||
.find('DataSourceNotifications') | ||
.prop('tags'); | ||
expect(newTags.length).toEqual(1); | ||
}); | ||
}); |
40 changes: 40 additions & 0 deletions
40
app/scripts/modules/core/src/application/nav/NavCategory.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import React from 'react'; | ||
|
||
import { DataSourceNotifications } from 'core/entityTag/notifications/DataSourceNotifications'; | ||
import { Icon, useDataSource } from '../../presentation'; | ||
|
||
import { ApplicationDataSource } from '../service/applicationDataSource'; | ||
import { Application } from '../application.model'; | ||
import { IEntityTags } from '../../domain'; | ||
|
||
export interface INavCategoryProps { | ||
category: ApplicationDataSource; | ||
isActive: boolean; | ||
app: Application; | ||
} | ||
|
||
export const NavCategory = ({ app, category, isActive }: INavCategoryProps) => { | ||
const { alerts, badge, iconName, key, label } = category; | ||
|
||
const { data: badgeData } = useDataSource(app.getDataSource(badge || key)); | ||
const runningCount = badge ? badgeData.length : 0; | ||
|
||
// useDataSource is enough to update alerts when needed | ||
useDataSource(category); | ||
const tags: IEntityTags[] = alerts || []; | ||
|
||
const badgeClassNames = runningCount ? 'badge-running-count' : 'badge-none'; | ||
|
||
return ( | ||
<div className="nav-category flex-container-h middle sp-padding-s-yaxis'"> | ||
<div className={badgeClassNames}>{runningCount > 0 ? runningCount : ''}</div> | ||
<div className="nav-item"> | ||
{iconName && ( | ||
<Icon className="nav-icon" name={iconName} size="extraSmall" color={isActive ? 'primary' : 'accent'} /> | ||
)} | ||
</div> | ||
<div className="nav-item">{' ' + category.label}</div> | ||
<DataSourceNotifications tags={tags} application={app} tabName={label} /> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import React from 'react'; | ||
import { useSrefActive } from '@uirouter/react'; | ||
|
||
import { NavCategory } from './NavCategory'; | ||
import { ApplicationDataSource } from '../service/applicationDataSource'; | ||
import { Application } from '../../application'; | ||
|
||
export interface INavRouteProps { | ||
category: ApplicationDataSource; | ||
isActive: boolean; | ||
app: Application; | ||
} | ||
|
||
export const NavRoute = ({ app, category, isActive }: INavRouteProps) => { | ||
const sref = useSrefActive(category.sref, null, 'active'); | ||
return ( | ||
<a {...sref}> | ||
<NavCategory app={app} category={category} isActive={isActive} /> | ||
</a> | ||
); | ||
}; |
44 changes: 44 additions & 0 deletions
44
app/scripts/modules/core/src/application/nav/verticalNav.less
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
.nav-category { | ||
color: var(--color-accent); | ||
padding-left: 40px; | ||
|
||
.badge-none { | ||
min-width: 18px; | ||
margin-right: 8px; | ||
} | ||
|
||
.badge-running-count { | ||
min-width: 18px; | ||
margin-right: 8px; | ||
border-radius: 3px; | ||
color: var(--color-white); | ||
background-color: var(--color-primary); | ||
padding: 3px; | ||
line-height: 14px; | ||
text-align: center; | ||
@media (max-width: 1400px) { | ||
font-size: 10px; | ||
} | ||
} | ||
|
||
.nav-item { | ||
min-width: 16px; | ||
margin-right: 8px; | ||
line-height: 10px; | ||
} | ||
} | ||
|
||
a { | ||
text-decoration: none; | ||
} | ||
|
||
.active { | ||
.nav-category { | ||
background: var(--color-accessory-light); | ||
color: var(--color-primary); | ||
} | ||
|
||
.nav-icon { | ||
color: var(--color-primary); | ||
} | ||
} |