Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Libraries
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'

// Components
import DashboardCard from 'src/dashboards/components/dashboard_index/DashboardCard'
import AssetLimitAlert from 'src/cloud/components/AssetLimitAlert'

// Types
import {AppState, Dashboard} from 'src/types'
import {LimitStatus} from 'src/cloud/actions/limits'

// Utils
import {extractDashboardLimits} from 'src/cloud/utils/limits'
import {isFlagEnabled} from 'src/shared/utils/featureFlag'
import {CLOUD} from 'src/shared/constants'

let getPinnedItems
if (CLOUD) {
getPinnedItems = require('src/shared/contexts/pinneditems').getPinnedItems
}

interface StateProps {
limitStatus: LimitStatus['status']
}

interface OwnProps {
dashboards: Dashboard[]
onFilterChange: (searchTerm: string) => void
}

class DashboardCards extends PureComponent<OwnProps & StateProps> {
private _isMounted = true

state = {
pinnedItems: [],
}

public componentDidMount() {
if (isFlagEnabled('pinnedItems') && CLOUD) {
getPinnedItems()
.then(res => {
if (this._isMounted) {
this.setState(prev => ({...prev, pinnedItems: res}))
}
})
.catch(err => {
console.error(err)
})
}
}

public componentWillUnmount() {
this._isMounted = false
}

public render() {
const {dashboards, onFilterChange} = this.props

const {pinnedItems} = this.state

return (
<div>
<div className="dashboards-card-grid">
{dashboards.map(({id, name, description, labels, meta}) => (
<DashboardCard
key={id}
id={id}
name={name}
labels={labels}
updatedAt={meta.updatedAt}
description={description}
onFilterChange={onFilterChange}
isPinned={
!!pinnedItems.find(item => item?.metadata.dashboardID === id)
}
/>
))}
<AssetLimitAlert
className="dashboards--asset-alert"
resourceName="dashboards"
limitStatus={this.props.limitStatus}
/>
</div>
</div>
)
}
}

const mstp = (state: AppState) => {
return {
limitStatus: extractDashboardLimits(state),
}
}

export default connect(mstp)(DashboardCards)
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Libraries
import React, {Component, createRef, RefObject} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import {withRouter, RouteComponentProps} from 'react-router-dom'
import memoizeOne from 'memoize-one'

// Components
import DashboardsTableEmpty from 'src/dashboards/components/dashboard_index/DashboardsTableEmpty'
import DashboardCardsPaginated from 'src/dashboards/components/dashboard_index/DashboardCardsPaginated'
import {ResourceList} from '@influxdata/clockface'

// Actions
import {retainRangesDashTimeV1 as retainRangesDashTimeV1Action} from 'src/dashboards/actions/ranges'
import {checkDashboardLimits as checkDashboardLimitsAction} from 'src/cloud/actions/limits'
import {createDashboard} from 'src/dashboards/actions/thunks'

// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'

// Types
import {Dashboard, AppState, Pageable, RemoteDataState} from 'src/types'
import {Sort} from '@influxdata/clockface'
import {SortTypes, getSortedResources} from 'src/shared/utils/sort'
import {DashboardSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'

// Utils
import {PaginationNav} from '@influxdata/clockface'

interface OwnProps {
onFilterChange: (searchTerm: string) => void
searchTerm: string
filterComponent?: JSX.Element
sortDirection: Sort
sortType: SortTypes
sortKey: DashboardSortKey
pageHeight: number
pageWidth: number
dashboards: Dashboard[]
totalDashboards: number
}

type ReduxProps = ConnectedProps<typeof connector>
type Props = ReduxProps & OwnProps & RouteComponentProps<{orgID: string}>

const DEFAULT_PAGINATION_CONTROL_HEIGHT = 62

@ErrorHandling
class DashboardsIndexContents extends Component<Props> implements Pageable {
private paginationRef: RefObject<HTMLDivElement>
public currentPage: number = 1
public rowsPerPage: number = 12
public totalPages: number

private memGetSortedResources = memoizeOne<typeof getSortedResources>(
getSortedResources
)
constructor(props) {
super(props)
this.paginationRef = createRef<HTMLDivElement>()
}

public componentDidMount() {
const {dashboards} = this.props

const dashboardIDs = dashboards.map(d => d.id)
this.props.retainRangesDashTimeV1(dashboardIDs)
this.props.checkDashboardLimits()

const params = new URLSearchParams(window.location.search)
const urlPageNumber = parseInt(params.get('page'), 10)

const passedInPageIsValid =
urlPageNumber && urlPageNumber <= this.totalPages && urlPageNumber > 0
if (passedInPageIsValid) {
this.currentPage = urlPageNumber
}
}

public componentDidUpdate() {
// if the user filters the list while on a page that is
// outside the new filtered list put them on the last page of the new list
if (this.currentPage > this.totalPages) {
this.paginate(this.totalPages)
}
}

public paginate = page => {
this.currentPage = page
const url = new URL(location.href)
url.searchParams.set('page', page)
history.replaceState(null, '', url.toString())
this.forceUpdate()
}

public renderDashboardCards() {
const sortedDashboards = this.memGetSortedResources(
this.props.dashboards,
this.props.sortKey,
this.props.sortDirection,
this.props.sortType
)

const startIndex = this.rowsPerPage * Math.max(this.currentPage - 1, 0)
const endIndex = Math.min(
startIndex + this.rowsPerPage,
this.props.totalDashboards
)
const rows = []
for (let i = startIndex; i < endIndex; i++) {
const dashboard = sortedDashboards[i]
if (dashboard) {
rows.push(dashboard)
}
}
return rows
}

public render() {
const {
searchTerm,
status,
dashboards,
onFilterChange,
onCreateDashboard,
} = this.props

const heightWithPagination =
this.paginationRef?.current?.clientHeight ||
DEFAULT_PAGINATION_CONTROL_HEIGHT
const height = this.props.pageHeight - heightWithPagination

this.totalPages = Math.max(
Math.ceil(dashboards.length / this.rowsPerPage),
1
)

if (status === RemoteDataState.Done && !dashboards.length) {
return (
<DashboardsTableEmpty
searchTerm={searchTerm}
onCreateDashboard={onCreateDashboard}
summonImportFromTemplateOverlay={this.summonImportFromTemplateOverlay}
summonImportOverlay={this.summonImportOverlay}
/>
)
}

return (
<>
<ResourceList style={{width: this.props.pageWidth}}>
<ResourceList.Body
style={{maxHeight: height, minHeight: height, overflow: 'auto'}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, this overflow: 'auto' avoids the bug I keep seeming to stumble over.

emptyState={null}
>
<DashboardCardsPaginated
dashboards={this.renderDashboardCards()}
onFilterChange={onFilterChange}
/>
</ResourceList.Body>
</ResourceList>
<PaginationNav.PaginationNav
ref={this.paginationRef}
style={{width: this.props.pageWidth}}
totalPages={this.totalPages}
currentPage={this.currentPage}
pageRangeOffset={1}
onChange={this.paginate}
/>
</>
)
}

private summonImportOverlay = (): void => {
const {
history,
match: {
params: {orgID},
},
} = this.props
history.push(`/orgs/${orgID}/dashboards-list/import`)
}

private summonImportFromTemplateOverlay = (): void => {
const {
history,
match: {
params: {orgID},
},
} = this.props
history.push(`/orgs/${orgID}/dashboards-list/import/template`)
}
}

const mstp = (state: AppState) => {
return {
status: state.resources.dashboards.status,
}
}

const mdtp = {
retainRangesDashTimeV1: retainRangesDashTimeV1Action,
checkDashboardLimits: checkDashboardLimitsAction,
onCreateDashboard: createDashboard as any,
}

const connector = connect(mstp, mdtp)

export default connector(withRouter(DashboardsIndexContents))
Loading