|
| 1 | +// Libraries |
| 2 | +import React, {Component, createRef, RefObject} from 'react' |
| 3 | +import {connect, ConnectedProps} from 'react-redux' |
| 4 | +import {withRouter, RouteComponentProps} from 'react-router-dom' |
| 5 | +import memoizeOne from 'memoize-one' |
| 6 | + |
| 7 | +// Components |
| 8 | +import DashboardsTableEmpty from 'src/dashboards/components/dashboard_index/DashboardsTableEmpty' |
| 9 | +import DashboardCardsPaginated from 'src/dashboards/components/dashboard_index/DashboardCardsPaginated' |
| 10 | +import {ResourceList} from '@influxdata/clockface' |
| 11 | + |
| 12 | +// Actions |
| 13 | +import {retainRangesDashTimeV1 as retainRangesDashTimeV1Action} from 'src/dashboards/actions/ranges' |
| 14 | +import {checkDashboardLimits as checkDashboardLimitsAction} from 'src/cloud/actions/limits' |
| 15 | +import {createDashboard} from 'src/dashboards/actions/thunks' |
| 16 | + |
| 17 | +// Decorators |
| 18 | +import {ErrorHandling} from 'src/shared/decorators/errors' |
| 19 | + |
| 20 | +// Types |
| 21 | +import {Dashboard, AppState, Pageable, RemoteDataState} from 'src/types' |
| 22 | +import {Sort} from '@influxdata/clockface' |
| 23 | +import {SortTypes, getSortedResources} from 'src/shared/utils/sort' |
| 24 | +import {DashboardSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems' |
| 25 | + |
| 26 | +// Utils |
| 27 | +import {PaginationNav} from '@influxdata/clockface' |
| 28 | + |
| 29 | +interface OwnProps { |
| 30 | + onFilterChange: (searchTerm: string) => void |
| 31 | + searchTerm: string |
| 32 | + filterComponent?: JSX.Element |
| 33 | + sortDirection: Sort |
| 34 | + sortType: SortTypes |
| 35 | + sortKey: DashboardSortKey |
| 36 | + pageHeight: number |
| 37 | + pageWidth: number |
| 38 | + dashboards: Dashboard[] |
| 39 | + totalDashboards: number |
| 40 | +} |
| 41 | + |
| 42 | +type ReduxProps = ConnectedProps<typeof connector> |
| 43 | +type Props = ReduxProps & OwnProps & RouteComponentProps<{orgID: string}> |
| 44 | + |
| 45 | +const DEFAULT_PAGINATION_CONTROL_HEIGHT = 62 |
| 46 | + |
| 47 | +@ErrorHandling |
| 48 | +class DashboardsIndexContents extends Component<Props> implements Pageable { |
| 49 | + private paginationRef: RefObject<HTMLDivElement> |
| 50 | + public currentPage: number = 1 |
| 51 | + public rowsPerPage: number = 12 |
| 52 | + public totalPages: number |
| 53 | + |
| 54 | + private memGetSortedResources = memoizeOne<typeof getSortedResources>( |
| 55 | + getSortedResources |
| 56 | + ) |
| 57 | + constructor(props) { |
| 58 | + super(props) |
| 59 | + this.paginationRef = createRef<HTMLDivElement>() |
| 60 | + } |
| 61 | + |
| 62 | + public componentDidMount() { |
| 63 | + const {dashboards} = this.props |
| 64 | + |
| 65 | + const dashboardIDs = dashboards.map(d => d.id) |
| 66 | + this.props.retainRangesDashTimeV1(dashboardIDs) |
| 67 | + this.props.checkDashboardLimits() |
| 68 | + |
| 69 | + const params = new URLSearchParams(window.location.search) |
| 70 | + const urlPageNumber = parseInt(params.get('page'), 10) |
| 71 | + |
| 72 | + const passedInPageIsValid = |
| 73 | + urlPageNumber && urlPageNumber <= this.totalPages && urlPageNumber > 0 |
| 74 | + if (passedInPageIsValid) { |
| 75 | + this.currentPage = urlPageNumber |
| 76 | + } |
| 77 | + } |
| 78 | + |
| 79 | + public componentDidUpdate() { |
| 80 | + // if the user filters the list while on a page that is |
| 81 | + // outside the new filtered list put them on the last page of the new list |
| 82 | + if (this.currentPage > this.totalPages) { |
| 83 | + this.paginate(this.totalPages) |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + public paginate = page => { |
| 88 | + this.currentPage = page |
| 89 | + const url = new URL(location.href) |
| 90 | + url.searchParams.set('page', page) |
| 91 | + history.replaceState(null, '', url.toString()) |
| 92 | + this.forceUpdate() |
| 93 | + } |
| 94 | + |
| 95 | + public renderDashboardCards() { |
| 96 | + const sortedDashboards = this.memGetSortedResources( |
| 97 | + this.props.dashboards, |
| 98 | + this.props.sortKey, |
| 99 | + this.props.sortDirection, |
| 100 | + this.props.sortType |
| 101 | + ) |
| 102 | + |
| 103 | + const startIndex = this.rowsPerPage * Math.max(this.currentPage - 1, 0) |
| 104 | + const endIndex = Math.min( |
| 105 | + startIndex + this.rowsPerPage, |
| 106 | + this.props.totalDashboards |
| 107 | + ) |
| 108 | + const rows = [] |
| 109 | + for (let i = startIndex; i < endIndex; i++) { |
| 110 | + const dashboard = sortedDashboards[i] |
| 111 | + if (dashboard) { |
| 112 | + rows.push(dashboard) |
| 113 | + } |
| 114 | + } |
| 115 | + return rows |
| 116 | + } |
| 117 | + |
| 118 | + public render() { |
| 119 | + const { |
| 120 | + searchTerm, |
| 121 | + status, |
| 122 | + dashboards, |
| 123 | + onFilterChange, |
| 124 | + onCreateDashboard, |
| 125 | + } = this.props |
| 126 | + |
| 127 | + const heightWithPagination = |
| 128 | + this.paginationRef?.current?.clientHeight || |
| 129 | + DEFAULT_PAGINATION_CONTROL_HEIGHT |
| 130 | + const height = this.props.pageHeight - heightWithPagination |
| 131 | + |
| 132 | + this.totalPages = Math.max( |
| 133 | + Math.ceil(dashboards.length / this.rowsPerPage), |
| 134 | + 1 |
| 135 | + ) |
| 136 | + |
| 137 | + if (status === RemoteDataState.Done && !dashboards.length) { |
| 138 | + return ( |
| 139 | + <DashboardsTableEmpty |
| 140 | + searchTerm={searchTerm} |
| 141 | + onCreateDashboard={onCreateDashboard} |
| 142 | + summonImportFromTemplateOverlay={this.summonImportFromTemplateOverlay} |
| 143 | + summonImportOverlay={this.summonImportOverlay} |
| 144 | + /> |
| 145 | + ) |
| 146 | + } |
| 147 | + |
| 148 | + return ( |
| 149 | + <> |
| 150 | + <ResourceList style={{width: this.props.pageWidth}}> |
| 151 | + <ResourceList.Body |
| 152 | + style={{maxHeight: height, minHeight: height, overflow: 'auto'}} |
| 153 | + emptyState={null} |
| 154 | + > |
| 155 | + <DashboardCardsPaginated |
| 156 | + dashboards={this.renderDashboardCards()} |
| 157 | + onFilterChange={onFilterChange} |
| 158 | + /> |
| 159 | + </ResourceList.Body> |
| 160 | + </ResourceList> |
| 161 | + <PaginationNav.PaginationNav |
| 162 | + ref={this.paginationRef} |
| 163 | + style={{width: this.props.pageWidth}} |
| 164 | + totalPages={this.totalPages} |
| 165 | + currentPage={this.currentPage} |
| 166 | + pageRangeOffset={1} |
| 167 | + onChange={this.paginate} |
| 168 | + /> |
| 169 | + </> |
| 170 | + ) |
| 171 | + } |
| 172 | + |
| 173 | + private summonImportOverlay = (): void => { |
| 174 | + const { |
| 175 | + history, |
| 176 | + match: { |
| 177 | + params: {orgID}, |
| 178 | + }, |
| 179 | + } = this.props |
| 180 | + history.push(`/orgs/${orgID}/dashboards-list/import`) |
| 181 | + } |
| 182 | + |
| 183 | + private summonImportFromTemplateOverlay = (): void => { |
| 184 | + const { |
| 185 | + history, |
| 186 | + match: { |
| 187 | + params: {orgID}, |
| 188 | + }, |
| 189 | + } = this.props |
| 190 | + history.push(`/orgs/${orgID}/dashboards-list/import/template`) |
| 191 | + } |
| 192 | +} |
| 193 | + |
| 194 | +const mstp = (state: AppState) => { |
| 195 | + return { |
| 196 | + status: state.resources.dashboards.status, |
| 197 | + } |
| 198 | +} |
| 199 | + |
| 200 | +const mdtp = { |
| 201 | + retainRangesDashTimeV1: retainRangesDashTimeV1Action, |
| 202 | + checkDashboardLimits: checkDashboardLimitsAction, |
| 203 | + onCreateDashboard: createDashboard as any, |
| 204 | +} |
| 205 | + |
| 206 | +const connector = connect(mstp, mdtp) |
| 207 | + |
| 208 | +export default connector(withRouter(DashboardsIndexContents)) |
0 commit comments