{
{(entityType === FILTER_ALL_ITEMS || entityType === APPLICATION) && (
-
+
handleInputChange(value, ENTITY_ID)} name={ENTITY_ID} />
)}
{entityType === JOB && (
-
+
handleInputChange(value, JOB_NAME)} name={JOB_NAME} />
)}
@@ -132,4 +148,4 @@ const ProjectsAlertsFilters = () => {
)
}
-export default ProjectsAlertsFilters
+export default AlertsFilters
diff --git a/src/components/Alerts/AlertsView.js b/src/components/Alerts/AlertsView.js
new file mode 100644
index 0000000000..4722681f75
--- /dev/null
+++ b/src/components/Alerts/AlertsView.js
@@ -0,0 +1,128 @@
+/*
+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 PropTypes from 'prop-types'
+
+import ActionBar from '../ActionBar/ActionBar'
+import AlertsTableRow from '../../elements/AlertsTableRow/AlertsTableRow'
+import Breadcrumbs from '../../common/Breadcrumbs/Breadcrumbs'
+import Loader from '../../common/Loader/Loader'
+import NoData from '../../common/NoData/NoData'
+import AlertsFilters from './AlertsFilters'
+import Table from '../Table/Table'
+
+import { getNoDataMessage } from '../../utils/getNoDataMessage'
+import { ALERTS_FILTERS, ALERTS_PAGE } from '../../constants'
+import { VIRTUALIZATION_CONFIG } from '../../types'
+import { isRowRendered } from '../../hooks/useVirtualization.hook'
+
+const AlertsView = ({
+ actionsMenu,
+ alertsFiltersConfig,
+ alertsStore,
+ filters,
+ filtersStore,
+ pageData,
+ refreshAlertsCallback,
+ requestErrorMessage,
+ selectedAlert,
+ setSearchParams,
+ tableContent,
+ virtualizationConfig
+}) => {
+ return (
+ <>
+
+
+
+
+
+
+
+ {alertsStore.loading ? (
+
+ ) : tableContent.length === 0 ? (
+
+ ) : (
+
+ {tableContent.map(
+ (tableItem, index) =>
+ isRowRendered(virtualizationConfig, index) && (
+ {}}
+ rowIndex={index}
+ rowItem={tableItem}
+ actionsMenu={[]}
+ selectedItem={selectedAlert}
+ />
+ )
+ )}
+
+ )}
+
+
+
+ >
+ )
+}
+
+AlertsView.propTypes = {
+ alertsFiltersConfig: PropTypes.object.isRequired,
+ filters: PropTypes.object.isRequired,
+ filtersStore: PropTypes.object.isRequired,
+ refreshAlertsCallback: PropTypes.func.isRequired,
+ requestErrorMessage: PropTypes.string.isRequired,
+ setSearchParams: PropTypes.func.isRequired,
+ tableContent: PropTypes.arrayOf(PropTypes.object).isRequired,
+ virtualizationConfig: VIRTUALIZATION_CONFIG.isRequired
+}
+export default AlertsView
diff --git a/src/components/Alerts/alerts.scss b/src/components/Alerts/alerts.scss
new file mode 100644
index 0000000000..eb9b553a95
--- /dev/null
+++ b/src/components/Alerts/alerts.scss
@@ -0,0 +1,16 @@
+@import '~igz-controls/scss/variables';
+@import 'src/scss/mixins';
+
+$alertsRowHeight: $rowHeight;
+$alertsHeaderRowHeight: $headerRowHeight;
+$alertsRowHeightExtended: $rowHeightExtended;
+
+.alerts-table {
+ @include rowsHeight($alertsHeaderRowHeight, $alertsRowHeight, $alertsRowHeightExtended);
+}
+
+:export {
+ alertsRowHeight: $alertsRowHeight;
+ alertsHeaderRowHeight: $alertsHeaderRowHeight;
+ alertsRowHeightExtended: $alertsRowHeightExtended;
+}
diff --git a/src/components/ProjectsAlerts/alerts.util.js b/src/components/Alerts/alerts.util.js
similarity index 86%
rename from src/components/ProjectsAlerts/alerts.util.js
rename to src/components/Alerts/alerts.util.js
index 3f4a87d27b..6b6bba376f 100644
--- a/src/components/ProjectsAlerts/alerts.util.js
+++ b/src/components/Alerts/alerts.util.js
@@ -30,7 +30,7 @@ import {
JOB_KIND_JOB,
JOB_NAME,
NAME_FILTER,
- PROJECT_FILTER,
+ PROJECTS_FILTER,
SEVERITY
} from '../../constants'
import {
@@ -41,16 +41,16 @@ import {
export const getAlertsFiltersConfig = () => {
return {
- [NAME_FILTER]: { label: 'Alert name', initialValue: '' },
+ [NAME_FILTER]: { label: 'Alert Name:', initialValue: '' },
[DATES_FILTER]: {
label: 'Start time:',
initialValue: getDatePickerFilterValue(datePickerPastOptions, PAST_24_HOUR_DATE_OPTION)
},
- [PROJECT_FILTER]: { label: 'Project:', initialValue: FILTER_ALL_ITEMS, isModal: true },
+ [PROJECTS_FILTER]: { label: 'Project:', initialValue: FILTER_ALL_ITEMS, isModal: true },
[ENTITY_TYPE]: { label: 'Entity Type', initialValue: FILTER_ALL_ITEMS, isModal: true },
[ENTITY_ID]: { label: 'Entity ID:', initialValue: '', isModal: true },
- [JOB_NAME]: { label: 'Job name:', initialValue: '', isModal: true },
- [ENDPOINT_APPLICATION]: { label: 'Endpoint:', initialValue: '', isModal: true },
+ [JOB_NAME]: { label: 'Job Name:', initialValue: '', isModal: true },
+ [ENDPOINT_APPLICATION]: { label: 'Application Name:', initialValue: '', isModal: true },
[ENDPOINT_RESULT]: { label: 'Result:', initialValue: '', isModal: true },
[SEVERITY]: { label: 'Severity:', initialValue: [FILTER_ALL_ITEMS], isModal: true },
[EVENT_TYPE]: { label: 'Event Type:', initialValue: FILTER_ALL_ITEMS, isModal: true }
@@ -65,6 +65,15 @@ export const parseAlertsQueryParamsCallback = (paramName, paramValue) => {
return filteredStatuses?.length ? filteredStatuses : null
}
+
+ if (paramName === ENTITY_TYPE) {
+ return filterAlertsEntityTypeOptions.find(type => type.id === paramValue)?.id
+ }
+
+ if (paramName === EVENT_TYPE) {
+ return filterAlertsEventTypeOptions.find(type => type.id === paramValue)?.id
+ }
+
return paramValue
}
diff --git a/src/components/ProjectsAlerts/ProjectsAlertsView.js b/src/components/ProjectsAlerts/ProjectsAlertsView.js
deleted file mode 100644
index fd0c787711..0000000000
--- a/src/components/ProjectsAlerts/ProjectsAlertsView.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
-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 Breadcrumbs from '../../common/Breadcrumbs/Breadcrumbs'
-import ActionBar from '../ActionBar/ActionBar'
-import ProjectsAlertsFilters from './ProjectsAlertsFilters'
-
-import { ALERTS_FILTERS, ALERTS_PAGE } from '../../constants'
-
-import PropTypes from 'prop-types'
-
-const ProjectAlertsView = ({ alertsFiltersConfig, filters, refreshAlertsCallback }) => {
- return (
- <>
-
- >
- )
-}
-
-ProjectAlertsView.propTypes = {
- alertsFiltersConfig: PropTypes.object.isRequired,
- filters: PropTypes.object.isRequired,
- refreshAlertsCallback: PropTypes.func.isRequired
-}
-export default ProjectAlertsView
diff --git a/src/components/Table/table.scss b/src/components/Table/table.scss
index d55267ef06..5a204f9d26 100644
--- a/src/components/Table/table.scss
+++ b/src/components/Table/table.scss
@@ -99,6 +99,12 @@
flex: 1;
max-width: 150px;
}
+
+ &-notification {
+ .notification-fail svg > * {
+ fill: $silver;
+ }
+ }
}
&:has(.actions-menu__container-active) {
@@ -109,16 +115,16 @@
&.table {
&__scrolled {
.table-cell-name {
- &:after {
- content: '';
+ &::after {
position: absolute;
top: 0;
- bottom: 0;
right: 0;
+ bottom: 0;
width: 5px;
background-color: inherit;
border-right: $secondaryBorder;
- box-shadow: 2px 0px 2px -1px rgba($black, 0.2);
+ box-shadow: 2px 0 2px -1px rgba($black, 0.2);
+ content: '';
}
}
@@ -197,10 +203,10 @@
&_hidden {
font-size: 0;
- * {
+
+ * {
visibility: hidden;
}
-
.chip {
visibility: hidden;
@@ -230,9 +236,9 @@
a {
position: relative;
+ width: 100%;
margin: 0;
text-decoration: none;
- width: 100%;
span {
display: block;
@@ -266,6 +272,11 @@
.chip_short {
max-width: 100px;
}
+
+ .severity-cell {
+ display: flex;
+ gap: 4px;
+ }
}
}
}
diff --git a/src/constants.js b/src/constants.js
index b5b8184b21..0e06766451 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -96,6 +96,12 @@ export const JOBS_MONITORING_SCHEDULED_TAB = 'scheduled'
export const ALERTS_PAGE = 'ALERTS'
export const ALERTS_FILTERS = 'alerts'
+export const MODEL_ENDPOINT_RESULT = 'model-endpoint-result'
+export const MODEL_MONITORING_APPLICATION = 'model-monitoring-application'
+export const SEVERITY_LOW = 'low'
+export const SEVERITY_MEDIUM = 'medium'
+export const SEVERITY_HIGH = 'high'
+export const SEVERITY_CRITICAL = 'critical'
export const MODELS_PAGE = 'MODELS'
export const MODELS_TAB = 'models'
@@ -508,6 +514,7 @@ export const LABELS_FILTER = 'labels'
export const NAME_FILTER = 'name'
export const DATES_FILTER = 'dates'
export const PROJECT_FILTER = 'project'
+export const PROJECTS_FILTER = 'projects-list'
export const TYPE_FILTER = 'type'
export const SHOW_UNTAGGED_FILTER = 'showUntagged'
export const SORT_BY = 'sortBy'
diff --git a/src/elements/AlertsTableRow/AlertsTableRow.js b/src/elements/AlertsTableRow/AlertsTableRow.js
new file mode 100644
index 0000000000..5527bc74e3
--- /dev/null
+++ b/src/elements/AlertsTableRow/AlertsTableRow.js
@@ -0,0 +1,74 @@
+/*
+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 { useRef } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import { useParams } from 'react-router-dom'
+
+import TableCell from '../TableCell/TableCell'
+
+import { DETAILS_OVERVIEW_TAB } from '../../constants'
+
+// TODO: rowIsExpanded logic will be part of ML-8516
+// TODO: selected row logic will be part of ML-8104
+const AlertsTableRow = ({ handleExpandRow, handleSelectItem, rowItem, selectedItem }) => {
+ const parent = useRef()
+ const params = useParams()
+
+ const rowClassNames = classnames('table-row', 'table-body-row', 'parent-row')
+
+ return (
+
+ <>
+ {rowItem.content.map((value, index) => {
+ return (
+ !value.hidden && (
+
+ )
+ )
+ })}
+ >
+
+ )
+}
+
+AlertsTableRow.propTypes = {
+ handleSelectItem: PropTypes.func.isRequired,
+ mainRowItemsCount: PropTypes.number,
+ rowIndex: PropTypes.number.isRequired,
+ rowItem: PropTypes.shape({}).isRequired,
+ selectedItem: PropTypes.shape({}).isRequired
+}
+
+export default AlertsTableRow
diff --git a/src/reducers/alertsReducer.js b/src/reducers/alertsReducer.js
new file mode 100644
index 0000000000..e8db44ac21
--- /dev/null
+++ b/src/reducers/alertsReducer.js
@@ -0,0 +1,109 @@
+/*
+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 { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
+
+import alertsApi from '../api/alerts-api'
+import { defaultPendingHandler } from './redux.util'
+import { parseAlerts } from '../utils/parseAlert'
+import { largeResponseCatchHandler } from '../utils/largeResponseCatchHandler'
+
+const initialState = {
+ alerts: [],
+ error: null,
+ loading: false
+}
+
+export const fetchAlert = createAsyncThunk(
+ 'fetchAlert',
+ ({ project, filters, config }, thunkAPI) => {
+ return alertsApi
+ .getAlert(project, filters, config)
+ .then(({ data }) => {
+ return parseAlerts(data.alerts)
+ })
+ .catch(error => {
+ largeResponseCatchHandler(
+ error,
+ 'Failed to fetch alerts',
+ thunkAPI.dispatch,
+ config?.ui?.setRequestErrorMessage
+ )
+ })
+ }
+)
+export const fetchAlerts = createAsyncThunk(
+ 'fetchAlerts',
+ ({ project, filters, config }, thunkAPI) => {
+ return alertsApi
+ .getAlerts(project, filters, config)
+ .then(({ data }) => {
+ return parseAlerts(data.alerts)
+ })
+ .catch(error => {
+ largeResponseCatchHandler(
+ error,
+ 'Failed to fetch alerts',
+ thunkAPI.dispatch,
+ config?.ui?.setRequestErrorMessage
+ )
+ })
+ }
+)
+
+const alertsSlice = createSlice({
+ name: 'alertsStore',
+ initialState,
+ reducers: {
+ removeAlerts(state) {
+ state.alerts = initialState.alerts
+ state.error = null
+ state.loading = false
+ }
+ },
+ extraReducers: builder => {
+ builder
+ .addCase(fetchAlert.pending, defaultPendingHandler)
+ .addCase(fetchAlert.fulfilled, (state, action) => {
+ state.alerts = action.payload
+ state.loading = false
+ })
+ .addCase(fetchAlert.rejected, (state, action) => {
+ state.alerts = []
+ state.error = action.payload
+ state.loading = false
+ })
+ .addCase(fetchAlerts.pending, state => {
+ state.loading = true
+ state.error = null
+ })
+ .addCase(fetchAlerts.fulfilled, (state, action) => {
+ state.loading = false
+ state.alerts = action.payload
+ })
+ .addCase(fetchAlerts.rejected, (state, action) => {
+ state.loading = false
+ state.error = action.payload
+ })
+ }
+})
+
+export const { removeAlerts } = alertsSlice.actions
+
+export default alertsSlice.reducer
diff --git a/src/store/toolkitStore.js b/src/store/toolkitStore.js
index abbc5df990..137061bc99 100644
--- a/src/store/toolkitStore.js
+++ b/src/store/toolkitStore.js
@@ -19,6 +19,7 @@ such restriction.
*/
import { configureStore } from '@reduxjs/toolkit'
+import alertsStore from '../reducers/alertsReducer'
import appStore from '../reducers/appReducer'
import artifactsStore from '../reducers/artifactsReducer'
import detailsStore from '../reducers/detailsReducer'
@@ -36,6 +37,7 @@ import workflowsStore from '../reducers/workflowReducer'
const toolkitStore = configureStore({
reducer: {
+ alertsStore,
appStore,
artifactsStore,
detailsStore,
diff --git a/src/utils/createAlertsContent.js b/src/utils/createAlertsContent.js
new file mode 100644
index 0000000000..e9abb189d6
--- /dev/null
+++ b/src/utils/createAlertsContent.js
@@ -0,0 +1,228 @@
+/*
+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 classNames from 'classnames'
+import { upperFirst } from 'lodash'
+import { formatDatetime } from './datetime'
+
+import { ReactComponent as Application } from 'igz-controls/images/entity-type-application.svg'
+import { ReactComponent as Endpoint } from 'igz-controls/images/entity-type-endpoint.svg'
+import { ReactComponent as Critical } from 'igz-controls/images/severity-critical.svg'
+import { ReactComponent as Email } from 'igz-controls/images/email-icon.svg'
+import { ReactComponent as Git } from 'igz-controls/images/git-icon.svg'
+import { ReactComponent as High } from 'igz-controls/images/severity-high.svg'
+import { ReactComponent as Job } from 'igz-controls/images/entity-type-job.svg'
+import { ReactComponent as Low } from 'igz-controls/images/severity-low.svg'
+import { ReactComponent as Normal } from 'igz-controls/images/severity-normal.svg'
+import { ReactComponent as Slack } from 'igz-controls/images/slack-icon-colored.svg'
+import { ReactComponent as Webhook } from 'igz-controls/images/webhook-icon.svg'
+
+import {
+ APPLICATION,
+ ENDPOINT,
+ JOB,
+ MODEL_ENDPOINT_RESULT,
+ MODEL_MONITORING_APPLICATION,
+ SEVERITY,
+ SEVERITY_CRITICAL,
+ SEVERITY_HIGH,
+ SEVERITY_LOW,
+ SEVERITY_MEDIUM
+} from '../constants'
+
+const getEntityTypeData = entityType => {
+ switch (entityType) {
+ case MODEL_ENDPOINT_RESULT:
+ return {
+ value: ,
+ tooltip: upperFirst(ENDPOINT)
+ }
+ case MODEL_MONITORING_APPLICATION:
+ return {
+ value: ,
+ tooltip: upperFirst(APPLICATION)
+ }
+ case JOB:
+ return {
+ value: ,
+ tooltip: upperFirst(JOB)
+ }
+ default:
+ return {
+ value:
+ }
+ }
+}
+
+const getSeverityData = severity => {
+ switch (severity) {
+ case SEVERITY_LOW:
+ return {
+ value: (
+
+
+ {upperFirst(SEVERITY_LOW)}
+
+ ),
+ tooltip: upperFirst(SEVERITY_LOW)
+ }
+ case SEVERITY_MEDIUM:
+ return {
+ value: (
+
+
+ {upperFirst(SEVERITY_MEDIUM)}
+
+ ),
+ tooltip: upperFirst(SEVERITY_MEDIUM)
+ }
+ case SEVERITY_HIGH:
+ return {
+ value: (
+
+
+ {upperFirst(SEVERITY_HIGH)}
+
+ ),
+ tooltip: upperFirst(SEVERITY_HIGH)
+ }
+ case SEVERITY_CRITICAL:
+ return {
+ value: (
+
+
+ {upperFirst(SEVERITY_CRITICAL)}
+
+ ),
+ tooltip: upperFirst(SEVERITY_CRITICAL)
+ }
+ default:
+ return {
+ value:
+ }
+ }
+}
+
+const alertsNotifications = {
+ webhook: ,
+ git: ,
+ slack: ,
+ email:
+}
+
+const getNotificationData = notifications =>
+ notifications.map(notification => {
+ const tableCellClassName = classNames('table-cell-notification__content', {
+ 'notification-fail': notification.err !== ''
+ })
+
+ return {
+ icon: {alertsNotifications[notification.kind]}
,
+ tooltip: upperFirst(notification.kind)
+ }
+ })
+
+export const createAlertRowData = ({ name, ...alert }) => {
+ alert.id = alert.id.slice(-6) // Use the last 6 characters of the database ID as the alert ID
+
+ return {
+ data: {
+ ...alert
+ },
+ content: [
+ {
+ id: `alertName.${alert.id}`,
+ headerId: 'alertName',
+ headerLabel: 'Alert Name',
+ value: name,
+ className: 'table-cell-1',
+ getLink: () => {}, //TODO: Implement in ML-8368
+ showStatus: true,
+ tooltip: name,
+ type: 'link'
+ },
+ {
+ id: `projectName.${alert.id}`,
+ headerId: 'projectName',
+ headerLabel: 'Project name',
+ value: alert.project,
+ className: 'table-cell-1'
+ },
+ {
+ id: `eventType.${alert.id}`,
+ headerId: 'eventType',
+ headerLabel: 'Event Type',
+ value: alert.event_kind?.split('-')?.join(' '),
+ className: 'table-cell-1'
+ },
+ {
+ id: `entityId.${alert.id}`,
+ headerId: 'entityId',
+ headerLabel: 'Entity ID',
+ value: alert.entity_id,
+ className: 'table-cell-1'
+ },
+ {
+ id: `entityType.${alert.id}`,
+ headerId: 'entityType',
+ headerLabel: 'Entity Type',
+ value: getEntityTypeData(alert.entity_kind).value,
+ className: 'table-cell-small',
+ tooltip: getEntityTypeData(alert.entity_kind).tooltip
+ },
+ {
+ id: `timestamp.${alert.id}`,
+ headerId: 'timestamp',
+ headerLabel: 'Timestamp',
+ value: formatDatetime(alert.activation_time, '-'),
+ className: 'table-cell-1'
+ },
+ {
+ id: `severity.${alert.id}`,
+ headerId: SEVERITY,
+ headerLabel: upperFirst(SEVERITY),
+ value: getSeverityData(alert.severity).value,
+ tooltip: getSeverityData(alert.severity).tooltip,
+ className: 'table-cell-1'
+ },
+ {
+ id: `criteriaCount.${alert.id}`,
+ headerId: 'criteriaCount',
+ headerLabel: 'Trigger criteria count',
+ value: alert.criteria?.count,
+ className: 'table-cell-1'
+ },
+ {
+ id: `criteriaTime.${alert.id}`,
+ headerId: 'criteriaTime',
+ headerLabel: 'Trigger criteria time period',
+ value: alert.criteria?.period,
+ className: 'table-cell-1'
+ },
+ {
+ id: `notifications.${alert.id}`,
+ headerId: 'notifications',
+ headerLabel: 'Notifications',
+ value: getNotificationData(alert.notifications),
+ className: 'table-cell-small table-cell-notification',
+ type: 'icons'
+ }
+ ]
+ }
+}
diff --git a/src/utils/parseAlert.js b/src/utils/parseAlert.js
new file mode 100644
index 0000000000..aa112a6178
--- /dev/null
+++ b/src/utils/parseAlert.js
@@ -0,0 +1,31 @@
+/*
+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.
+*/
+export const parseAlerts = alerts => {
+ return alerts.map(alert => {
+ return {
+ ...alert,
+ ui: {
+ ...alert,
+ identifier: `${alert.id.slice(-6)}`,
+ identifierUnique: `${alert.name}.${alert.id.slice(-6)}`
+ }
+ }
+ })
+}
diff --git a/tests/mockServer/data/alerts.json b/tests/mockServer/data/alerts.json
new file mode 100644
index 0000000000..c882070214
--- /dev/null
+++ b/tests/mockServer/data/alerts.json
@@ -0,0 +1,468 @@
+{
+ "activations": [
+ {
+ "id": "a1b2c3d4-5662c7a4470ef28ed1df450e80d37624de7e1749",
+ "name": "alert_1",
+ "project": "workflow-proj",
+ "activation_time": "2024-10-23T10:15:30Z",
+ "notifications": [
+ {
+ "kind": "email",
+ "err": ""
+ },
+ {
+ "kind": "webhook",
+ "err": ""
+ },
+ {
+ "kind": "git",
+ "err": ""
+ },
+ {
+ "kind": "slack",
+ "err": ""
+ }
+ ],
+ "entity_id": "5662c7a4470ef28ed1df450e80d37624de7e1749.histogram-data-drift.general_drift",
+ "entity_kind": "model-endpoint-result",
+ "criteria": {
+ "count": 3,
+ "period": "30m"
+ },
+ "event_kind": "data-drift-detected",
+ "severity": "medium",
+ "number_of_events": 15
+ },
+ {
+ "id": "b2c3d4e5-6772c8b5581ef39fd2df561f91e47635f8f2e263",
+ "name": "alert_2",
+ "project": "tutorial-admin",
+ "activation_time": "2024-10-24T12:20:30Z",
+ "notifications": [
+ {
+ "kind": "email",
+ "err": "fail"
+ },
+ {
+ "kind": "webhook",
+ "err": "fail"
+ },
+ {
+ "kind": "git",
+ "err": "fail"
+ },
+ {
+ "kind": "slack",
+ "err": "fail"
+ }
+ ],
+ "entity_id": "trainer-train.5a8037bd46cf192ce7e94b25f06a8d13",
+ "entity_kind": "job",
+ "criteria": {
+ "count": 10,
+ "period": "2h"
+ },
+ "event_kind": "concept-drift-suspected",
+ "severity": "low",
+ "number_of_events": 7
+ },
+ {
+ "id": "c3d4e5f6-7883d9c6692ef40fe3df672a02f58746f9f3e374",
+ "name": "alert_3",
+ "project": "workflow-proj",
+ "activation_time": "2024-10-25T14:45:00Z",
+ "notifications": [
+ {
+ "kind": "webhook",
+ "err": ""
+ }
+ ],
+ "entity_id": "application-1",
+ "entity_kind": "model-monitoring-application",
+ "criteria": {
+ "count": 2,
+ "period": "1h"
+ },
+ "event_kind": "mm-app-anomaly-detected",
+ "severity": "high",
+ "number_of_events": 12
+ },
+ {
+ "id": "d4e5f6g7-8994ea1a780f0b1fg4df783b13f69857a0d4e485",
+ "name": "alert_4",
+ "project": "analysis-hub",
+ "activation_time": "2024-10-26T08:10:20Z",
+ "notifications": [
+ {
+ "kind": "email",
+ "err": ""
+ },
+ {
+ "kind": "git",
+ "err": "Authentication failed"
+ }
+ ],
+ "entity_id": "d5e6f4b3970ef28ed1df450e80d37624de7e1749.feature-importance.general_drift",
+ "entity_kind": "model-endpoint-result",
+ "criteria": {
+ "count": 8,
+ "period": "1h"
+ },
+ "event_kind": "model-performance-detected",
+ "severity": "low",
+ "number_of_events": 5
+ },
+ {
+ "id": "e5f6g7h8-9105fb2b891f1c2hg5df894c24f70968b1e5f596",
+ "name": "alert_5",
+ "project": "reporting-app",
+ "activation_time": "2024-10-27T16:55:10Z",
+ "notifications": [
+ {
+ "kind": "slack",
+ "err": ""
+ }
+ ],
+ "entity_id": "batch-inference-v2-infer.7ad15b839fc206e4ba9f02c84f31e57a",
+ "entity_kind": "job",
+ "criteria": {
+ "count": 4,
+ "period": "3h"
+ },
+ "event_kind": "system-performance-suspected",
+ "severity": "high",
+ "number_of_events": 20
+ },
+ {
+ "id": "f6g7h8i9-1026fc3c902f2d3hi6df915d35g81079c2f6g717",
+ "name": "alert_6",
+ "project": "tutorial-admin",
+ "activation_time": "2024-10-28T09:25:40Z",
+ "notifications": [
+ {
+ "kind": "email",
+ "err": "SMTP server not reachable"
+ }
+ ],
+ "entity_id": "a2b4c6d8970ef28ed1df450e80d37624de7e1749.statistical-shift.general_drift",
+ "entity_kind": "model-endpoint-result",
+ "criteria": {
+ "count": 6,
+ "period": "2h"
+ },
+ "event_kind": "mm-app-failed",
+ "severity": "high",
+ "number_of_events": 3
+ },
+ {
+ "id": "g7h8i9j0-2137gd4d013f3e4ij7df026e46h92180d3g7h829",
+ "name": "alert_7",
+ "project": "workflow-proj",
+ "activation_time": "2024-10-29T07:30:50Z",
+ "notifications": [
+ {
+ "kind": "webhook",
+ "err": ""
+ }
+ ],
+ "entity_id": "c6d3e7f0970ef28ed1df450e80d37624de7e1749.data-quality-issue.drift",
+ "entity_kind": "model-endpoint-result",
+ "criteria": {
+ "count": 1,
+ "period": "30m"
+ },
+ "event_kind": "model-performance-suspected",
+ "severity": "low",
+ "number_of_events": 4
+ },
+ {
+ "id": "h8i9j0k1-3248he5e124g4f5jk8df137f57i032c4h8i9310",
+ "name": "alert_8",
+ "project": "analysis-hub",
+ "activation_time": "2024-10-30T11:15:10Z",
+ "notifications": [
+ {
+ "kind": "slack",
+ "err": "Failed to send notification due to network issue"
+ }
+ ],
+ "entity_id": "batch-inference-v1-infer.c3b47590a6821d7ef9f2ab04c8e163a5",
+ "entity_kind": "job",
+ "criteria": {
+ "count": 9,
+ "period": "4h"
+ },
+ "event_kind": "concept-drift-detected",
+ "severity": "medium",
+ "number_of_events": 16
+ },
+ {
+ "id": "i9j0k1l2-4359if6f235h5g6kl9df248g68j143d5i9j0421",
+ "name": "alert_9",
+ "project": "reporting-app",
+ "activation_time": "2024-10-31T17:05:35Z",
+ "notifications": [
+ {
+ "kind": "email",
+ "err": ""
+ },
+ {
+ "kind": "webhook",
+ "err": ""
+ }
+ ],
+ "entity_id": "d9e8f5b4970ef28ed1df450e80d37624de7e1749.input-missing-values.general_drift",
+ "entity_kind": "model-endpoint-result",
+ "criteria": {
+ "count": 7,
+ "period": "1h"
+ },
+ "event_kind": "data-drift-suspected",
+ "severity": "high",
+ "number_of_events": 18
+ },
+ {
+ "id": "j0k1l2m3-5460jg7g346i6h7lm0df359h79k254e6j0k1532",
+ "name": "alert_10",
+ "project": "workflow-proj",
+ "activation_time": "2024-11-01T06:20:15Z",
+ "notifications": [
+ {
+ "kind": "git",
+ "err": ""
+ }
+ ],
+ "entity_id": "c2a3e1f8970ef28ed1df450e80d37624de7e1749.class-distribution.general_drift",
+ "entity_kind": "model-endpoint-result",
+ "criteria": {
+ "count": 5,
+ "period": "2h"
+ },
+ "event_kind": "system-performance-detected",
+ "severity": "medium",
+ "number_of_events": 9
+ },
+ {
+ "id": "k1l2m3n4-6571kh8h457j7i8mn1df460i80l365f7k1l2643",
+ "name": "alert_11",
+ "project": "analysis-hub",
+ "activation_time": "2024-11-02T14:10:50Z",
+ "notifications": [
+ {
+ "kind": "email",
+ "err": ""
+ },
+ {
+ "kind": "webhook",
+ "err": "API endpoint not responding"
+ }
+ ],
+ "entity_id": "f3d4b5a1970ef28ed1df450e80d37624de7e1749.outlier-detection.general_drift",
+ "entity_kind": "model-endpoint-result",
+ "criteria": {
+ "count": 10,
+ "period": "3h"
+ },
+ "event_kind": "mm-app-anomaly-suspected",
+ "severity": "high",
+ "number_of_events": 13
+ },
+ {
+ "id": "l2m3n4o5-7682li9i568k8jno2df571j91m476g8l2m3754",
+ "name": "alert_12",
+ "project": "tutorial-admin",
+ "activation_time": "2024-11-03T18:35:25Z",
+ "notifications": [
+ {
+ "kind": "slack",
+ "err": ""
+ }
+ ],
+ "entity_id": "tutorial-function.af2de87229ab49139748db785e0c3d6b",
+ "entity_kind": "job",
+ "criteria": {
+ "count": 3,
+ "period": "1h"
+ },
+ "event_kind": "failed",
+ "severity": "low",
+ "number_of_events": 14
+ },
+ {
+ "id": "m3n4o5p6-8793mj0j679l9knp3df682k02n587h9m3n4875",
+ "name": "alert_13",
+ "project": "workflow-proj",
+ "activation_time": "2024-11-04T09:05:00Z",
+ "notifications": [
+ {
+ "kind": "email",
+ "err": "SMTP server not reachable"
+ }
+ ],
+ "entity_id": "d5e6f4b3970ef28ed1df450e80d37624de7e1749.feature-importance.general_drift",
+ "entity_kind": "model-endpoint-result",
+ "criteria": {
+ "count": 8,
+ "period": "4h"
+ },
+ "event_kind": "concept-drift-suspected",
+ "severity": "high",
+ "number_of_events": 6
+ },
+ {
+ "id": "n4o5p6q7-9804nk1k780m0lop4df793l13o698i0n4o5986",
+ "name": "alert_14",
+ "project": "reporting-app",
+ "activation_time": "2024-11-05T13:15:45Z",
+ "notifications": [
+ {
+ "kind": "webhook",
+ "err": ""
+ }
+ ],
+ "entity_id": "application-2",
+ "entity_kind": "model-monitoring-application",
+ "criteria": {
+ "count": 2,
+ "period": "30m"
+ },
+ "event_kind": "mm-app-failed",
+ "severity": "high",
+ "number_of_events": 11
+ },
+ {
+ "id": "o5p6q7r8-0915ol2l891n1mp5df804m24p709j1o5p6097",
+ "name": "alert_15",
+ "project": "tutorial-admin",
+ "activation_time": "2024-11-06T12:25:20Z",
+ "notifications": [
+ {
+ "kind": "git",
+ "err": ""
+ }
+ ],
+ "entity_id": "get-data.a23bcd3ef5d9f0174abc8e09324a76bc",
+ "entity_kind": "job",
+ "criteria": {
+ "count": 5,
+ "period": "2h"
+ },
+ "event_kind": "data-drift-detected",
+ "severity": "low",
+ "number_of_events": 2
+ },
+ {
+ "id": "p6q7r8s9-1026pm3m902o2nq6df915n35q810k2p6q7108",
+ "name": "alert_16",
+ "project": "workflow-proj",
+ "activation_time": "2024-11-07T14:00:35Z",
+ "notifications": [
+ {
+ "kind": "slack",
+ "err": "Failed to send notification due to network issue"
+ }
+ ],
+ "entity_id": "c9e7f6a3970ef28ed1df450e80d37624de7e1749.null-value-frequency.general_drift",
+ "entity_kind": "model-endpoint-result",
+ "criteria": {
+ "count": 7,
+ "period": "1h"
+ },
+ "event_kind": "system-performance-suspected",
+ "severity": "high",
+ "number_of_events": 17
+ },
+ {
+ "id": "q7r8s9t0-2137qn4n013p3or7df026o46r92190l3q7r8219",
+ "name": "alert_17",
+ "project": "analysis-hub",
+ "activation_time": "2024-11-08T08:40:55Z",
+ "notifications": [
+ {
+ "kind": "email",
+ "err": ""
+ },
+ {
+ "kind": "webhook",
+ "err": ""
+ }
+ ],
+ "entity_id": "b4d5f8e7970ef28ed1df450e80d37624de7e1749.imbalance-ratio.general_drift",
+ "entity_kind": "model-endpoint-result",
+ "criteria": {
+ "count": 9,
+ "period": "3h"
+ },
+ "event_kind": "model-performance-detected",
+ "severity": "high",
+ "number_of_events": 19
+ },
+ {
+ "id": "r8s9t0u1-3248ro5o124q4ps8df137p57s032m4r8s9320",
+ "name": "alert_18",
+ "project": "reporting-app",
+ "activation_time": "2024-11-09T11:50:30Z",
+ "notifications": [
+ {
+ "kind": "webhook",
+ "err": ""
+ }
+ ],
+ "entity_id": "get-dev.af12d780b9ce4a6c0357ef3941b5ad68",
+ "entity_kind": "job",
+ "criteria": {
+ "count": 6,
+ "period": "4h"
+ },
+ "event_kind": "concept-drift-detected",
+ "severity": "low",
+ "number_of_events": 8
+ },
+ {
+ "id":"s9t0u1v2-4359sp6p235r4qt9df248q68t143n5s9t0421",
+ "name": "alert_19",
+ "project": "tutorial-admin",
+ "activation_time": "2024-11-10T10:10:10Z",
+ "notifications": [
+ {
+ "kind": "git",
+ "err": ""
+ }
+ ],
+ "entity_id": "model-endpoint-19.app-19.result-19",
+ "entity_kind": "model-endpoint-result",
+ "criteria": {
+ "count": 4,
+ "period": "2h"
+ },
+ "event_kind": "mm-app-anomaly-detected",
+ "severity": "high",
+ "number_of_events": 10
+ },
+ {
+ "id": "t0u1v2w3-5460tq7q346s5ru0df359r79u254o6t0u1532",
+ "name": "alert_20",
+ "project": "workflow-proj",
+ "activation_time": "2024-11-11T13:30:45Z",
+ "notifications": [
+ {
+ "kind": "email",
+ "err": ""
+ },
+ {
+ "kind": "slack",
+ "err": "Failed to send notification due to network issue"
+ }
+ ],
+ "entity_id": "d5e6f4b3970ef28ed1df450e80d37624de7e1749.feature-importance.general_drift",
+ "entity_kind": "model-endpoint-result",
+ "criteria": {
+ "count": 1,
+ "period": "30m"
+ },
+ "event_kind": "mm-app-failed",
+ "severity": "high",
+ "number_of_events": 5
+ }
+ ]
+}
diff --git a/tests/mockServer/mock.js b/tests/mockServer/mock.js
index 11b4429dad..131c71bdf6 100644
--- a/tests/mockServer/mock.js
+++ b/tests/mockServer/mock.js
@@ -44,6 +44,7 @@ import {
} from 'lodash'
import mime from 'mime-types'
+import alerts from './data/alerts.json'
import frontendSpec from './data/frontendSpec.json'
import projects from './data/projects.json'
import projectsSummary from './data/summary.json'
@@ -875,6 +876,13 @@ function getRun(req, res) {
res.send({ data: run_prj_uid })
}
+// TODO:ML-8368 add getAlert controller
+
+function getAlerts(req, res) {
+ // TODO:ML-8514 Update getAlerts to support both parameters and query strings.
+ res.send({ alerts: alerts.activations })
+}
+
function patchRun(req, res) {
const collectedRun = runs.runs
.filter(run => run.metadata.project === req.params.project)
@@ -2614,6 +2622,7 @@ app.get(`${mlrunAPIIngress}/project-summaries/:project`, getProjectSummary)
app.get(`${mlrunAPIIngress}/projects/:project/runs`, getRuns)
app.get(`${mlrunAPIIngress}/projects/*/runs`, getRuns)
+app.get(`${mlrunAPIIngress}/projects/*/alert-activations`, getAlerts)
app.get(`${mlrunAPIIngress}/run/:project/:uid`, getRun)
app.patch(`${mlrunAPIIngress}/run/:project/:uid`, patchRun)
app.delete(`${mlrunAPIIngress}/projects/:project/runs/:uid`, deleteRun)