diff --git a/src/components/MonitoringApplicationsPage/MonitoringApplications/AllApplicationsTable.jsx b/src/components/MonitoringApplicationsPage/MonitoringApplications/AllApplicationsTable.jsx new file mode 100644 index 000000000..671734358 --- /dev/null +++ b/src/components/MonitoringApplicationsPage/MonitoringApplications/AllApplicationsTable.jsx @@ -0,0 +1,144 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { useMemo } from 'react' +import { useNavigate, useParams } from 'react-router-dom' +import PropTypes from 'prop-types' + +import ApplicationTableRow from '../../../elements/ApplicationTableRow/ApplicationTableRow' +import NoData from '../../../common/NoData/NoData' +import Table from '../../Table/Table' +import { Loader } from 'igz-controls/components' + +import { MODEL_ENDPOINTS_TAB, MONITORING_APP_PAGE } from '../../../constants' +import { createApplicationContent } from '../../../utils/createApplicationContent' +import { saveAndTransformSearchParams } from 'igz-controls/utils/filter.util' +import { MONITORING_APPLICATIONS_NO_DATA_MESSAGE } from '../MonitoringApplicationsPage.util' +import { getScssVariableValue } from 'igz-controls/utils/common.util' +import { isRowRendered, useVirtualization } from '../../../hooks/useVirtualization.hook' + +import PresentMetricsIcon from 'igz-controls/images/present-metrics-icon.svg?react' + +import '../monitoringApplicationsPage.scss' + +const AllApplicationsTable = ({ applications, loading, error = null }) => { + const params = useParams() + const navigate = useNavigate() + + const applicationsRowHeight = useMemo(() => getScssVariableValue('--applicationRowHeight'), []) + const applicationRowHeightExtended = useMemo( + () => getScssVariableValue('--applicationRowHeightExtended'), + [] + ) + const applicationsHeaderRowHeight = useMemo( + () => getScssVariableValue('--applicationHeaderRowHeight'), + [] + ) + + const applicationsTableContent = useMemo(() => { + return applications.map(contentItem => + createApplicationContent(contentItem, params.projectName) + ) + }, [applications, params.projectName]) + + const applicationsTableHeaders = useMemo( + () => applicationsTableContent[0]?.content ?? [], + [applicationsTableContent] + ) + + const applicationsTableActionsMenu = useMemo( + () => [ + [], + [ + { + id: 'open-metrics', + label: 'Open metrics', + icon: , + onClick: data => + navigate( + `/projects/${params.projectName}/${MONITORING_APP_PAGE}/${data.name}/${MODEL_ENDPOINTS_TAB}${saveAndTransformSearchParams( + window.location.search, + true + )}` + ) + } + ] + ], + [navigate, params.projectName] + ) + + const virtualizationConfig = useVirtualization({ + rowsData: { + content: applicationsTableContent + }, + heightData: { + headerRowHeight: applicationsHeaderRowHeight, + rowHeight: applicationsRowHeight, + rowHeightExtended: applicationRowHeightExtended + } + }) + + return ( +
+
+ All applications +
+ {applications.length === 0 && !loading ? ( + + ) : loading ? ( + + ) : ( + + {applicationsTableContent.map( + (tableItem, index) => + isRowRendered(virtualizationConfig, index) && ( + + ) + )} +
+ )} +
+ ) +} + +AllApplicationsTable.propTypes = { + applications: PropTypes.array.isRequired, + loading: PropTypes.bool.isRequired, + error: PropTypes.object +} + +export default AllApplicationsTable diff --git a/src/components/MonitoringApplicationsPage/MonitoringApplications/MonitoringApplication/MonitoringApplication.jsx b/src/components/MonitoringApplicationsPage/MonitoringApplications/MonitoringApplication/MonitoringApplication.jsx index e93ce64f8..fba71215c 100644 --- a/src/components/MonitoringApplicationsPage/MonitoringApplications/MonitoringApplication/MonitoringApplication.jsx +++ b/src/components/MonitoringApplicationsPage/MonitoringApplications/MonitoringApplication/MonitoringApplication.jsx @@ -40,7 +40,7 @@ import './monitoringApplication.scss' const MonitoringApplication = () => { const dispatch = useDispatch() - const { artifacts } = useSelector(store => store.artifactsStore) + const { artifacts, loading: loadingArtifacts } = useSelector(store => store.artifactsStore) const { monitoringApplication, loading } = useSelector(store => store.monitoringApplicationsStore) const params = useParams() @@ -71,11 +71,17 @@ const MonitoringApplication = () => {
Artifacts
- {artifacts.length === 0 && !loading ? ( + {artifacts.length === 0 && !loadingArtifacts ? ( ) : ( <> - + { const dispatch = useDispatch() const params = useParams() - const navigate = useNavigate() const { monitoringApplications: { applications = [], operatingFunctions = [] }, loading, error } = useSelector(store => store.monitoringApplicationsStore) - const applicationsTableActionsMenu = useMemo( - () => [ - [], - [ - { - id: 'open-metrics', - label: 'Open metrics', - icon: , - onClick: data => - navigate( - `/projects/${params.projectName}/${MONITORING_APP_PAGE}/${data.name}/${MODEL_ENDPOINTS_TAB}${saveAndTransformSearchParams( - window.location.search, - true - )}` - ) - } - ] - ], - [navigate, params.projectName] - ) const operatingFunctionsTable = useMemo( () => generateOperatingFunctionsTable(operatingFunctions, params.projectName), [operatingFunctions, params.projectName] ) - const applicationsTableContent = useMemo(() => { - return applications.map(contentItem => - createApplicationContent(contentItem, params.projectName) - ) - }, [applications, params.projectName]) - - const applicationsTableHeaders = useMemo( - () => applicationsTableContent[0]?.content ?? [], - [applicationsTableContent] - ) useEffect(() => { return () => { @@ -110,44 +73,17 @@ const MonitoringApplications = () => { } /> ) : ( - + )}
-
-
- All applications -
- {applications.length === 0 && !loading ? ( - - ) : loading ? ( - - ) : ( - - {applicationsTableContent.map((tableItem, index) => ( - - ))} -
- )} -
+
) diff --git a/src/components/MonitoringApplicationsPage/monitoringApplicationsPage.scss b/src/components/MonitoringApplicationsPage/monitoringApplicationsPage.scss index db6aeb07b..cddb71497 100644 --- a/src/components/MonitoringApplicationsPage/monitoringApplicationsPage.scss +++ b/src/components/MonitoringApplicationsPage/monitoringApplicationsPage.scss @@ -7,6 +7,32 @@ $applicationRowHeight: variables.$rowHeight; $applicationHeaderRowHeight: variables.$headerRowHeight; $applicationRowHeightExtended: variables.$rowHeightExtended; +:root { + --applicationRowHeight: #{$applicationRowHeight}; + --applicationHeaderRowHeight: #{$applicationHeaderRowHeight}; + --applicationRowHeightExtended: #{$applicationRowHeightExtended}; +} + +.all-applications-table { + max-height: 450px; + + .table__flex { + max-height: calc(100% - 25px); + + .table__content { + height: 100%; + + & > div { + height: 100%; + + table.applications-table { + height: 100%; + } + } + } + } +} + .monitoring-app-content { .monitoring-apps-title { color: colors.$primary; @@ -59,9 +85,9 @@ $applicationRowHeightExtended: variables.$rowHeightExtended; .applications-table { @include mixins.rowsHeight( - $applicationHeaderRowHeight, - $applicationRowHeight, - $applicationRowHeightExtended + $applicationHeaderRowHeight, + $applicationRowHeight, + $applicationRowHeightExtended ); } @@ -92,8 +118,8 @@ $applicationRowHeightExtended: variables.$rowHeightExtended; left: 50%; font-weight: 500; font-size: 13px; - transform: translate(-50%, 0); - } + transform: translate(-50%, 0); + } &.loading { visibility: hidden; diff --git a/src/elements/SectionTable/SectionTable.jsx b/src/elements/SectionTable/SectionTable.jsx index 0836042da..4b9f82300 100644 --- a/src/elements/SectionTable/SectionTable.jsx +++ b/src/elements/SectionTable/SectionTable.jsx @@ -17,180 +17,237 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import React from 'react' +import React, { useMemo, useState } from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' import { Link } from 'react-router-dom' +import { v4 as uuidv4 } from 'uuid' import { TableTypeCell } from 'igz-controls/elements' import { TextTooltipTemplate, Tooltip, Tip, Loader, ReadOnlyChips } from 'igz-controls/components' +import { isRowRendered, useVirtualization } from '../../hooks/useVirtualization.hook' import './SectionTable.scss' -const SectionTable = ({ loading = false, params, table }) => { +const SPACE_FOR_BORDER = 2 +const DEFAULT_ROW_HEIGHT = 41 +const DEFAULT_MAX_TABLE_HEIGHT = 369 + +const SectionTable = ({ + headerHeight = DEFAULT_ROW_HEIGHT, + loading = false, + params, + rowHeight = DEFAULT_ROW_HEIGHT, + table, + maxTableHeight = DEFAULT_MAX_TABLE_HEIGHT +}) => { + const [tableId] = useState(`section-table-${uuidv4()}`) + const [tableBodyId] = useState(`section-table-body-${uuidv4()}`) + + const rowsSizes = useMemo( + () => (table?.body?.length ? new Array(table.body.length).fill(parseInt(rowHeight)) : []), + [rowHeight, table.body.length] + ) + + const heightData = useMemo( + () => ({ + headerRowHeight: headerHeight, + rowHeight: rowHeight, + rowHeightExtended: rowHeight + }), + [headerHeight, rowHeight] + ) + + const tableContainerHeight = useMemo(() => { + return Math.min(headerHeight + rowHeight * table.body.length + SPACE_FOR_BORDER, maxTableHeight) + }, [headerHeight, maxTableHeight, rowHeight, table]) + + const virtualizationConfig = useVirtualization({ + renderTriggerItem: table, + heightData, + rowsSizes, + tableBodyId: tableBodyId, + tableId: tableId + }) + return loading ? ( ) : ( - <> - - - - <> - {table.header.map( - header => - !header.hidden && ( - - ) - )} - - - - - {table.body.map((body, index) => { - const extractedItemName = body['name'].value.startsWith(params.projectName) - ? body['name'].value.slice(params.projectName.length + 1) - : body['name'].value - - return ( - - <> - {Object.keys(body).map((key, index) => { - const tableValueClassName = classnames( - 'section-table__table-cell', - body[key].className, - key === 'name' && 'name-wrapper', - key === 'status' && 'status-cell', - key === 'status' && - !Array.isArray(body[key].value) && - `status_${body?.[key]?.value?.toLowerCase?.()} capitalize` +
+
+
- }> - {header.value} - - {header.tip && } -
+ + + <> + {table.header.map( + header => + !header.hidden && ( + ) + )} + + + + + {table.body.map((body, index) => { + const extractedItemName = body['name'].value.startsWith(params.projectName) + ? body['name'].value.slice(params.projectName.length + 1) + : body['name'].value - return ( - !body[key].hidden && - (key === 'type' ? ( - - ) : ( - + <> + {Object.keys(body).map((key, index) => { + const tableValueClassName = classnames( + 'section-table__table-cell', + body[key].className, + key === 'name' && 'name-wrapper', + key === 'status' && 'status-cell', + key === 'status' && + !Array.isArray(body[key].value) && + `status_${body?.[key]?.value?.toLowerCase?.()} capitalize` + ) + + return ( + !body[key].hidden && + (key === 'type' ? ( + + ) : ( + - )) - ) - })} - - - ) - })} - -
+ }> + {header.value} + + {header.tip && } +
- {key === 'name' ? ( - <> - {body[key].href ? ( - - } - textShow={true} - > - {extractedItemName} - - - ) : body[key].link ? ( - + return ( + isRowRendered(virtualizationConfig, index) && ( +
+ {key === 'name' ? ( + <> + {body[key].href ? ( + + } + textShow={true} + > + {extractedItemName} + + + ) : body[key].link ? ( + + } + > + {body[key].value} + + + ) : ( + } + > + {body[key].value} + + )} + + {body[key].tag ? ( + } + > + {body[key].tag} + + ) : null} + + ) : key === 'labels' ? ( + + ) : key === 'status' ? ( + <> + {Array.isArray(body.status.value) ? ( + body.status.value.map((status, index) => { + return ( + } + > + + + ) + }) + ) : ( + + } + > + {body[key].value} + + )} + + ) : ( + <> } + template={ + + } > {body[key].value} - - ) : ( - } - > - {body[key].value} - - )} - - {body[key].tag ? ( - } - > - {body[key].tag} - - ) : null} - - ) : key === 'labels' ? ( - - ) : key === 'status' ? ( - <> - {Array.isArray(body.status.value) ? ( - body.status.value.map((status, index) => { - return ( + {body[key].status && ( } + key={body[key].status + index} + template={} > - + - ) - }) - ) : ( - - } - > - {body[key].value} - - )} - - ) : ( - <> - - } - > - {body[key].value} - - {body[key].status && ( - } - > - - + )} + )} - - )} -
- + + )) + ) + })} + + + ) + ) + })} + + + + ) } SectionTable.propTypes = { + headerHeight: PropTypes.number, loading: PropTypes.bool, params: PropTypes.object.isRequired, - table: PropTypes.object.isRequired + rowHeight: PropTypes.number, + table: PropTypes.object.isRequired, + maxTableHeight: PropTypes.number } export default React.memo(SectionTable) diff --git a/src/elements/SectionTable/SectionTable.scss b/src/elements/SectionTable/SectionTable.scss index 2da8cf9e5..b87bae1ba 100644 --- a/src/elements/SectionTable/SectionTable.scss +++ b/src/elements/SectionTable/SectionTable.scss @@ -18,72 +18,100 @@ } } -.section-table { - min-width: fit-content; - height: 100%; - border: borders.$dividerBorder; - border-radius: 8px; - - &__table { - &-body { - display: flex; - flex: 1; - flex-direction: column; - } +.section-table__content { + position: relative; + width: 100%; +} - &-row { - display: flex; - flex: 1; - flex-direction: row; - border-bottom: borders.$tertiaryBorder; - } +.section-table__wrapper { + position: absolute; + top: 0; + right: 0; + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + overflow-y: auto; - &-link { + .section-table { + position: relative; + display: flex; + flex: 1; + flex-flow: column nowrap; + width: 100%; + height: 100%; + overflow-y: auto; + border: borders.$dividerBorder; + border-radius: 8px; + will-change: scroll-position; + + thead { + position: sticky; + top: 0; + z-index: 3; min-width: 100%; - max-width: 50px; + background: colors.$white; } - &-cell { - display: flex; - align-items: center; - padding: 8px 5px 8px 0; - color: colors.$primary; - line-height: 24px; - - &.status { - color: colors.$supernova; - text-transform: none; - - &_completed, - &_ready, - &_running { - color: colors.$java; - } + &__table { + &-body { + display: flex; + flex-direction: column; + } + + &-row { + display: flex; + flex-direction: row; + min-width: 100%; + border-bottom: borders.$tertiaryBorder; + } - &-nuclio { - &_ready { - color: colors.$brightTurquoise; + &-link { + min-width: 100%; + max-width: 50px; + } - &.disabled { + &-cell { + display: flex; + align-items: center; + padding: 8px 5px 8px 0; + color: colors.$primary; + line-height: 24px; + + &.status { + color: colors.$supernova; + text-transform: none; + + &_completed, + &_ready, + &_running { + color: colors.$java; + } + + &-nuclio { + &_ready { + color: colors.$brightTurquoise; + + &.disabled { + color: colors.$topaz; + } + } + + &_scaledToZero { color: colors.$topaz; } } - &_scaledToZero { - color: colors.$topaz; + &_failed, + &_error, + &_unhealthy { + color: colors.$amaranth; } - } - - &_failed, - &_error, - &_unhealthy { - color: colors.$amaranth; - } - &_imported { - color: colors.$topaz; + &_imported { + color: colors.$topaz; + } } - } .tooltip-wrapper { min-width: 8px; @@ -93,60 +121,60 @@ margin-left: 5px; } - &.name-wrapper { - display: flex; - flex-wrap: wrap; + &.name-wrapper { + display: flex; + flex-wrap: wrap; + + &.table-cell_with-tag { + gap: 4px; + height: 57px; + } - &.table-cell_with-tag { - gap: 4px; - height: 57px; + .item-name { + width: 100%; + } + + .item-tag { + display: inline; + max-width: 150px; + color: colors.$topaz; + line-height: 16px; + } } - .item-name { - width: 100%; + .table-body__cell { + &_type { + display: flex; + align-items: center; + padding: 0; + } } - .item-tag { - display: inline; - max-width: 150px; - color: colors.$topaz; - line-height: 16px; + &:first-child { + padding-left: 15px; } } - .table-body__cell { - &_type { - display: flex; - align-items: center; - padding: 0; + &-header { + display: flex; + font-size: 14px; + line-height: 24px; + border-bottom: borders.$tertiaryBorder; + + .table-header-item { + color: colors.$topaz; + font-weight: bold; } } - &:first-child { - padding-left: 15px; + .table-body__cell { + align-items: center; + border: none; } - } - - &-header { - display: flex; - flex: 1; - font-size: 14px; - line-height: 24px; - border-bottom: borders.$tertiaryBorder; - .table-header-item { - color: colors.$topaz; - font-weight: bold; + .tooltip__text { + display: initial; } } - - .table-body__cell { - align-items: center; - border: none; - } - - .tooltip__text { - display: initial; - } } }