diff --git a/src/common/TabsSlider/TabsSlider.js b/src/common/TabsSlider/TabsSlider.js
index dc3b30202..a12fa3cc5 100644
--- a/src/common/TabsSlider/TabsSlider.js
+++ b/src/common/TabsSlider/TabsSlider.js
@@ -191,7 +191,7 @@ const TabsSlider = ({
className={tabClassName}
data-tab={tab.id}
to={generateUrlFromRouterPath(
- `${window.location.pathname?.replace(/^$|([^/]+$)/, tab.id)}${location.search ?? ''}`
+ `${window.location.pathname?.replace(/^$|([^/]+$)/, tab.id)}${location.search ?? ''}${tab.query ?? ''}`
)}
onClick={() => onSelectTab(tab)}
key={tab.id}
diff --git a/src/components/ActionBar/ActionBar.js b/src/components/ActionBar/ActionBar.js
index a8f0fe58b..5f3860bde 100644
--- a/src/components/ActionBar/ActionBar.js
+++ b/src/components/ActionBar/ActionBar.js
@@ -365,6 +365,7 @@ const ActionBar = ({
onChange={(dates, isPredefined, optionId) =>
handleDateChange(dates, isPredefined, optionId, input, formState)
}
+ timeFrameLimit={filtersConfig[DATES_FILTER].timeFrameLimit}
type="date-range-time"
withLabels
/>
diff --git a/src/components/Alerts/Alerts.js b/src/components/Alerts/Alerts.js
index 63acec3d6..b49d2f27e 100644
--- a/src/components/Alerts/Alerts.js
+++ b/src/components/Alerts/Alerts.js
@@ -25,8 +25,11 @@ import AlertsView from './AlertsView'
import { ALERTS_PAGE } from '../../constants'
import { createAlertRowData } from '../../utils/createAlertsContent'
-import { getAlertsFiltersConfig, parseAlertsQueryParamsCallback } from './alerts.util'
-import { generatePageData } from './alerts.util'
+import {
+ getAlertsFiltersConfig,
+ generatePageData,
+ parseAlertsQueryParamsCallback
+} from './alerts.util'
import { getJobLogs } from '../../utils/getJobLogs.util'
import projectsAction from '../../actions/projects'
import { useAlertsPageData } from '../../hooks/useAlertsPageData'
@@ -34,11 +37,11 @@ import { useFiltersFromSearchParams } from '../../hooks/useFiltersFromSearchPara
const Alerts = () => {
const [selectedAlert, setSelectedAlert] = useState({})
- const { id: projectId } = useParams()
const [, setProjectsRequestErrorMessage] = useState('')
const alertsStore = useSelector(state => state.alertsStore)
const filtersStore = useSelector(store => store.filtersStore)
const dispatch = useDispatch()
+ const { id: projectId } = useParams()
const params = useParams()
const isCrossProjects = useMemo(() => projectId === '*', [projectId])
@@ -93,7 +96,7 @@ const Alerts = () => {
)
const pageData = useMemo(
- () => generatePageData(handleFetchJobLogs, selectedAlert),
+ () => generatePageData(selectedAlert, handleFetchJobLogs),
[handleFetchJobLogs, selectedAlert]
)
diff --git a/src/components/Alerts/AlertsFilters.js b/src/components/Alerts/AlertsFilters.js
index aae4bc896..8bab9f6ff 100644
--- a/src/components/Alerts/AlertsFilters.js
+++ b/src/components/Alerts/AlertsFilters.js
@@ -50,7 +50,7 @@ import {
SEVERITY
} from '../../constants'
-const AlertsFilters = ({ isCrossProjects }) => {
+const AlertsFilters = ({ isAlertsPage, isCrossProjects }) => {
const form = useForm()
const {
values: { [ENTITY_TYPE]: entityType }
@@ -94,13 +94,15 @@ const AlertsFilters = ({ isCrossProjects }) => {
)}
-
-
-
+ {isAlertsPage && (
+
+
+
+ )}
{(entityType === FILTER_ALL_ITEMS || entityType === MODEL_MONITORING_APPLICATION) && (
diff --git a/src/components/Alerts/AlertsView.js b/src/components/Alerts/AlertsView.js
index 7804b476c..cc5ae9d50 100644
--- a/src/components/Alerts/AlertsView.js
+++ b/src/components/Alerts/AlertsView.js
@@ -17,6 +17,7 @@ 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 classNames from 'classnames'
import PropTypes from 'prop-types'
import ActionBar from '../ActionBar/ActionBar'
@@ -32,9 +33,12 @@ import { ALERTS_FILTERS, ALERTS_PAGE } from '../../constants'
import { getNoDataMessage } from '../../utils/getNoDataMessage'
import { getCloseDetailsAlertLink } from '../../utils/link-helper.util'
+import './alerts.scss'
+
const AlertsView = ({
alertsFiltersConfig,
alertsStore,
+ isAlertsPage = true,
filters,
filtersStore,
handleCancel,
@@ -46,16 +50,22 @@ const AlertsView = ({
requestErrorMessage,
selectedAlert,
setSearchParams,
- tableContent
+ selectedRowData,
+ tableContent,
+ toggleRow
}) => {
+ const content = classNames('content', !isAlertsPage && 'alerts-table__content')
+
return (
<>
-
-
-
-
-
+ {isAlertsPage && (
+
+
+
+ )}
+
+
{alertsStore.loading ? (
@@ -88,27 +98,36 @@ const AlertsView = ({
<>
getCloseDetailsAlertLink()} //TODO: the getCloseDetailsLink will be updated with ML-8368
+ getCloseDetailsLink={() => getCloseDetailsAlertLink()}
pageData={pageData}
retryRequest={handleRefreshWithFilters}
- selectedItem={selectedAlert}
+ selectedItem={isAlertsPage ? selectedAlert : {}}
tableClassName="alerts-table"
handleCancel={handleCancel}
hideActionsMenu
tableHeaders={tableContent[0]?.content ?? []}
withActionMenu={false}
>
- {tableContent.map((tableItem, index) => (
- {}}
- rowIndex={index}
- rowItem={tableItem}
- actionsMenu={[]}
- selectedItem={selectedAlert}
- />
- ))}
+ {tableContent.map((tableItem, index) => {
+ const isRowSelected = tableItem?.data?.id === selectedAlert?.id && !isAlertsPage
+ const selectedRowClassName = `${isRowSelected ? 'alert-row__cell--expanded-selected-cell' : ''} `
+ return (
+ {}}
+ filters={filters}
+ isRowSelected={isRowSelected}
+ rowIndex={index}
+ rowItem={tableItem}
+ actionsMenu={[]}
+ toggleRow={toggleRow}
+ selectedItem={selectedAlert}
+ selectedRowData={selectedRowData}
+ />
+ )
+ })}
{
+export const getAlertsFiltersConfig = (timeFrameLimit = false) => {
return {
[NAME_FILTER]: { label: 'Alert Name:', initialValue: '' },
[DATES_FILTER]: {
label: 'Start time:',
- initialValue: getDatePickerFilterValue(datePickerPastOptions, PAST_24_HOUR_DATE_OPTION)
+ initialValue: getDatePickerFilterValue(datePickerPastOptions, PAST_24_HOUR_DATE_OPTION),
+ timeFrameLimit: timeFrameLimit ? TIME_FRAME_LIMITS.MONTH : Infinity
},
[PROJECTS_FILTER]: { label: 'Project:', initialValue: FILTER_ALL_ITEMS, isModal: true },
[ENTITY_TYPE]: { label: 'Entity Type:', initialValue: FILTER_ALL_ITEMS, isModal: true },
@@ -86,7 +88,7 @@ export const parseAlertsQueryParamsCallback = (paramName, paramValue) => {
return paramValue
}
-export const generatePageData = (handleFetchJobLogs, selectedAlert) => {
+export const generatePageData = (selectedAlert, handleFetchJobLogs = () => {}) => {
return {
page: ALERTS_PAGE,
details: {
@@ -109,8 +111,8 @@ export const allProjectsOption = [
export const filterAlertsEntityTypeOptions = [
{ label: upperFirst(FILTER_ALL_ITEMS), id: FILTER_ALL_ITEMS },
{ label: upperFirst(JOB), id: JOB_KIND_JOB },
- { label: upperFirst(ENDPOINT), id: 'model-endpoint-result' },
- { label: upperFirst(APPLICATION), id: 'model-monitoring-application' }
+ { label: upperFirst(ENDPOINT), id: MODEL_ENDPOINT_RESULT },
+ { label: upperFirst(APPLICATION), id: MODEL_MONITORING_APPLICATION }
]
export const filterAlertsSeverityOptions = [
diff --git a/src/components/DetailsAlerts/DetailsAlerts.js b/src/components/DetailsAlerts/DetailsAlerts.js
index 881dbd832..935f9a1e2 100644
--- a/src/components/DetailsAlerts/DetailsAlerts.js
+++ b/src/components/DetailsAlerts/DetailsAlerts.js
@@ -17,16 +17,86 @@ 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, { useCallback, useMemo, useState } from 'react'
+import { useSelector } from 'react-redux'
-import React from 'react'
+import AlertsView from '../Alerts/AlertsView'
+
+import { createAlertRowData } from '../../utils/createAlertsContent'
+import {
+ generatePageData,
+ getAlertsFiltersConfig,
+ parseAlertsQueryParamsCallback
+} from '../../components/Alerts/alerts.util'
+import { useAlertsPageData } from '../../hooks/useAlertsPageData'
+import { useFiltersFromSearchParams } from '../../hooks/useFiltersFromSearchParams.hook'
+
+const DetailsAlerts = () => {
+ const [selectedAlert, setSelectedAlert] = useState({})
+ const alertsStore = useSelector(state => state.alertsStore)
+ const filtersStore = useSelector(store => store.filtersStore)
+
+ const alertsFiltersConfig = useMemo(() => getAlertsFiltersConfig(true), [])
+
+ const alertsFilters = useFiltersFromSearchParams(
+ alertsFiltersConfig,
+ parseAlertsQueryParamsCallback
+ )
+
+ const {
+ handleRefreshAlerts,
+ paginatedAlerts,
+ paginationConfigAlertsRef,
+ requestErrorMessage,
+ refreshAlerts,
+ setAlerts,
+ setSearchParams
+ } = useAlertsPageData(alertsFilters, false)
+
+ const handleRefreshWithFilters = useCallback(
+ filters => {
+ setAlerts([])
+
+ return refreshAlerts(filters)
+ },
+ [refreshAlerts, setAlerts]
+ )
+
+ const tableContent = useMemo(() => {
+ return paginatedAlerts.map(alert => createAlertRowData(alert, false, true))
+ }, [paginatedAlerts])
+
+ const pageData = useMemo(() => generatePageData(selectedAlert), [selectedAlert])
+
+ const toggleRow = useCallback(
+ (e, item) => {
+ setSelectedAlert(prev => {
+ const selectedAlert = tableContent.find(({ data }) => data?.id === item?.id)
+ return prev?.id !== item.id ? selectedAlert?.data || {} : {}
+ })
+ },
+ [tableContent]
+ )
-const DetailsAlerts = ({ selectedItem }) => {
return (
-
-
Alerts
-
This tab shows alerts for the selected item.
-
+
)
}
-
-export default DetailsAlerts
+export default React.memo(DetailsAlerts)
diff --git a/src/components/DetailsDrillDownAlert/DetailsAlertsMetrics.js b/src/components/DetailsDrillDownAlert/DetailsAlertsMetrics.js
index c90891f37..a0bee1bd8 100644
--- a/src/components/DetailsDrillDownAlert/DetailsAlertsMetrics.js
+++ b/src/components/DetailsDrillDownAlert/DetailsAlertsMetrics.js
@@ -34,6 +34,7 @@ import modelEndpointsActions from '../../actions/modelEndpoints'
import { groupMetricByApplication } from '../../elements/MetricsSelector/metricsSelector.util'
import {
+ CUSTOM_RANGE_DATE_OPTION,
datePickerPastOptions,
PAST_24_HOUR_DATE_OPTION,
TIME_FRAME_LIMITS
@@ -41,13 +42,13 @@ import {
import { ReactComponent as MetricsIcon } from 'igz-controls/images/metrics-icon.svg'
-const DetailsAlertsMetrics = ({ selectedItem }) => {
+const DetailsAlertsMetrics = ({ selectedItem, filters, isAlertsPage = true }) => {
const [metrics, setMetrics] = useState([])
const [requestErrorMessage, setRequestErrorMessage] = useState('')
const metricsContainerRef = useRef(null)
const metricsValuesAbortController = useRef(new AbortController())
const prevSelectedEndPointNameRef = useRef('')
- const [metricOptionsAreLoaded, setMetricOptionsAreLoaded] = useState(false)
+
const detailsStore = useSelector(store => store.detailsStore)
const dispatch = useDispatch()
@@ -82,12 +83,6 @@ const DetailsAlertsMetrics = ({ selectedItem }) => {
handleChangeDates(past24hoursOption.handler(), true, PAST_24_HOUR_DATE_OPTION)
}, [handleChangeDates])
- useEffect(() => {
- dispatch(
- modelEndpointsActions.fetchModelEndpointMetrics(selectedItem.project, selectedItem.uid)
- ).then(() => setMetricOptionsAreLoaded(true))
- }, [dispatch, selectedItem.project, selectedItem.uid])
-
const fetchData = useCallback(
(params, projectName, uid) => {
metricsValuesAbortController.current = new AbortController()
@@ -111,36 +106,27 @@ const DetailsAlertsMetrics = ({ selectedItem }) => {
prevSelectedEndPointNameRef.current = selectedItem.uid
return
}
+ const params = { name: [selectedItem.fullName] }
- if (
- metricOptionsAreLoaded &&
- selectedItem?.uid &&
- detailsStore.metricsOptions.all.length > 0 &&
- detailsStore.metricsOptions.selectedByEndpoint[selectedItem?.uid]
- ) {
- const params = { name: [selectedItem.fullName] }
-
- if (detailsStore.dates.value[0] && detailsStore.dates.value[1]) {
- params.start = detailsStore.dates.value[0].getTime()
- params.end = detailsStore.dates.value[1].getTime()
- }
+ if (isAlertsPage && detailsStore.dates.value[0] && detailsStore.dates.value[1]) {
+ params.start = detailsStore.dates.value[0].getTime()
+ params.end = detailsStore.dates.value[1].getTime()
+ }
- fetchData(params, selectedItem.project, selectedItem.uid).then()
- } else {
- setMetrics([])
+ if (!isAlertsPage) {
+ if (filters?.dates?.initialSelectedOptionId === CUSTOM_RANGE_DATE_OPTION) {
+ params.start = filters?.dates.value[0].getTime()
+ params.end = filters?.dates.value[1].getTime()
+ } else {
+ params.start = filters?.dates.value[0].getTime()
+ params.end = Date.now()
+ }
}
- }, [
- selectedItem,
- metricOptionsAreLoaded,
- detailsStore.metricsOptions,
- detailsStore.dates.value,
- fetchData,
- setMetrics
- ])
+ fetchData(params, selectedItem.project, selectedItem.uid).then()
+ }, [isAlertsPage, filters, selectedItem, detailsStore.dates.value, fetchData])
useEffect(() => {
fetchMetrics()
-
return () => {
metricsValuesAbortController.current?.abort(REQUEST_CANCELED)
setMetrics([])
@@ -148,20 +134,22 @@ const DetailsAlertsMetrics = ({ selectedItem }) => {
}, [fetchMetrics, setMetrics])
return (
-
-
-
-
+
+ {isAlertsPage && (
+
+
+
+ )}
{generatedMetrics.length === 0 ? (
!detailsStore.loadingCounter ? (
@@ -175,10 +163,10 @@ const DetailsAlertsMetrics = ({ selectedItem }) => {
)
) : null
) : (
-
+
{generatedMetrics.map(([applicationName, applicationMetrics]) => (
- {applicationName}
+ {isAlertsPage && {applicationName}
}
{applicationMetrics.map(metric =>
!metric.data || isEmpty(metric.points) ? (
@@ -195,6 +183,7 @@ const DetailsAlertsMetrics = ({ selectedItem }) => {
}
DetailsAlertsMetrics.propTypes = {
+ isAlertsPage: PropTypes.bool,
selectedItem: PropTypes.object.isRequired
}
diff --git a/src/components/ModelsPage/ModelEndpoints/modelEndpoints.util.js b/src/components/ModelsPage/ModelEndpoints/modelEndpoints.util.js
index 443e7b9e3..a6e367126 100644
--- a/src/components/ModelsPage/ModelEndpoints/modelEndpoints.util.js
+++ b/src/components/ModelsPage/ModelEndpoints/modelEndpoints.util.js
@@ -59,7 +59,8 @@ const detailsMenu = [
{
label: 'alerts',
id: 'alerts',
- icon:
+ icon: ,
+ query: '?entity-type=model-endpoint-result' //TODO: temp solution for query params
}
]
diff --git a/src/elements/AlertsTableRow/AlertsTableRow.js b/src/elements/AlertsTableRow/AlertsTableRow.js
index 51a1bf731..d477657d5 100644
--- a/src/elements/AlertsTableRow/AlertsTableRow.js
+++ b/src/elements/AlertsTableRow/AlertsTableRow.js
@@ -17,11 +17,12 @@ 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 { useMemo, useRef } from 'react'
+import React, { useMemo, useRef } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { useParams } from 'react-router-dom'
+import DetailsAlertsMetrics from '../../components/DetailsDrillDownAlert/DetailsAlertsMetrics'
import TableCell from '../TableCell/TableCell'
import { ALERTS_PAGE, DETAILS_OVERVIEW_TAB } from '../../constants'
@@ -29,11 +30,16 @@ import { getIdentifierMethod } from '../../utils/getUniqueIdentifier'
import './AlertsTableRow.scss'
-// TODO: rowIsExpanded logic will be part of ML-8516
-const AlertsTableRow = ({ handleExpandRow, handleSelectItem, rowItem, selectedItem }) => {
+const AlertsTableRow = ({
+ className,
+ isRowSelected,
+ filters,
+ rowItem,
+ selectedItem,
+ toggleRow
+}) => {
const parent = useRef()
const params = useParams()
-
const getIdentifier = useMemo(() => getIdentifierMethod(ALERTS_PAGE), [])
const rowClassNames = classnames(
'alert-row',
@@ -44,38 +50,50 @@ const AlertsTableRow = ({ handleExpandRow, handleSelectItem, rowItem, selectedIt
getIdentifier(selectedItem, true) === rowItem?.data?.ui?.identifierUnique &&
'table-row_active'
)
-
return (
-
- <>
- {rowItem.content.map((value, index) => {
- return (
+ <>
+
+ {rowItem.content.map(
+ (value, index) =>
!value.hidden && (
-
+
+
+
)
- )
- })}
- >
-
+ )}
+
+ {isRowSelected && (
+
+
+
+ |
+
+ )}
+ >
)
}
AlertsTableRow.propTypes = {
+ className: PropTypes.string,
handleSelectItem: PropTypes.func.isRequired,
+ isRowSelected: PropTypes.bool,
mainRowItemsCount: PropTypes.number,
rowIndex: PropTypes.number.isRequired,
rowItem: PropTypes.shape({}).isRequired,
diff --git a/src/elements/AlertsTableRow/AlertsTableRow.scss b/src/elements/AlertsTableRow/AlertsTableRow.scss
index 2d575840c..4e8802c66 100644
--- a/src/elements/AlertsTableRow/AlertsTableRow.scss
+++ b/src/elements/AlertsTableRow/AlertsTableRow.scss
@@ -2,50 +2,20 @@
@import '~igz-controls/scss/borders';
.alert-row {
- .table-body__cell.alert-row-notification-cell {
- min-width: 150px;
- }
+ flex-wrap: wrap;
+ border: 1px solid #e2e7ff;
- &__item-info-notification {
- display: flex;
- gap: 5px;
+ > * {
+ border-bottom-color: white !important;
}
- &__details-alert-icon-cell {
- display: flex;
- gap: 8px;
- align-items: center;
-
- svg {
- width: 20px;
- height: 20px;
- }
- }
-
- &__details-alert-header {
- display: flex;
- align-self: center;
- min-width: 160px;
- font-weight: 500;
- font-size: 15px;
- line-height: 18px;
- }
-
- &__details-alert-logs {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
-
- &__popup-header {
- margin: 0;
- padding-bottom: 12px;
- font-size: 18px;
+ .table-body__cell.alert-row-notification-cell {
+ min-width: 150px;
}
&__item-info-notification {
- flex-wrap: wrap;
display: flex;
+ flex-wrap: wrap;
gap: 5px;
}
@@ -95,6 +65,19 @@
}
}
}
+
+ td:first-child.alert-row__cell--expanded-selected-cell {
+ svg {
+ transform: rotate(90deg);
+ }
+ }
+
+ &__expanded-row {
+ display: flex;
+ flex-direction: column;
+ justify-items: center;
+ margin-top: 8px;
+ }
}
.notifications-item {
diff --git a/src/hooks/useAlertsPageData.js b/src/hooks/useAlertsPageData.js
index 158c46e67..0621588bd 100644
--- a/src/hooks/useAlertsPageData.js
+++ b/src/hooks/useAlertsPageData.js
@@ -25,7 +25,7 @@ import { BE_PAGE, BE_PAGE_SIZE, FILTER_ALL_ITEMS, PROJECTS_FILTER } from '../con
import { usePagination } from './usePagination.hook'
import { fetchAlerts } from '../reducers/alertsReducer'
-export const useAlertsPageData = filters => {
+export const useAlertsPageData = (filters, isAlertsPage) => {
const [alerts, setAlerts] = useState([])
const [requestErrorMessage, setRequestErrorMessage] = useState('')
@@ -38,10 +38,11 @@ export const useAlertsPageData = filters => {
const refreshAlerts = useCallback(
filters => {
setAlerts([])
-
abortControllerRef.current = new AbortController()
- const projectName =
- params.projectName || filters?.[PROJECTS_FILTER]?.toLowerCase?.() !== FILTER_ALL_ITEMS
+
+ const projectName = !isAlertsPage
+ ? params.projectName || params.id
+ : filters?.[PROJECTS_FILTER]?.toLowerCase?.() !== FILTER_ALL_ITEMS
? filters?.[PROJECTS_FILTER]?.toLowerCase?.()
: params.id
@@ -71,7 +72,7 @@ export const useAlertsPageData = filters => {
paginationConfigAlertsRef.current.paginationResponse = response.pagination
})
},
- [dispatch, params.id, params.projectName]
+ [dispatch, isAlertsPage, params.id, params.projectName]
)
const [handleRefreshAlerts, paginatedAlerts, , setSearchParams] = usePagination({
diff --git a/src/utils/createAlertsContent.js b/src/utils/createAlertsContent.js
index f32db7336..7a32be2c3 100644
--- a/src/utils/createAlertsContent.js
+++ b/src/utils/createAlertsContent.js
@@ -192,14 +192,14 @@ const getNotificationData = notifications =>
}
})
-export const createAlertRowData = ({ ...alert }, isCrossProjects) => {
+export const createAlertRowData = ({ ...alert }, isCrossProjects, showExpandButton = false) => {
const { name } = alert
const getLink = alert => {
const queryString = window.location.search
const { alertName, entity_kind: entityType, entity_id, id: alertId, job, project, uid } = alert
- if (entityType === MODEL_ENDPOINT_RESULT) {
+ if (entityType === MODEL_ENDPOINT_RESULT) {
const [endpointId, , , name] = entity_id.split('.')
return `/projects/*/alerts/${project}/${alertName}/${alertId}/${name}/${endpointId}/${DETAILS_ALERT_APPLICATION}${queryString}`
}
@@ -268,9 +268,10 @@ export const createAlertRowData = ({ ...alert }, isCrossProjects) => {
headerLabel: 'Alert Name',
value: name,
className: 'table-cell-name',
- getLink: () => getLink(alert),
+ getLink: () => (!showExpandButton ? getLink(alert) : ''),
tooltip: name,
- type: 'link'
+ type: 'link',
+ showExpandButton
},
{
id: `projectName.${alert.id}`,