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
85 changes: 85 additions & 0 deletions alerta/database/backends/postgres/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,91 @@ def get_alert_tags(self, query=None, topn=1000):
""".format(where=query.where)
return [{'environment': t.environment, 'tag': t.tag, 'count': t.count} for t in self._fetchall(select, query.vars, limit=topn)]

# FILTER TABS

def get_filter_tabs(self):
select = """SELECT * FROM filter_tabs ORDER BY index"""
return self._fetchall(select, {}, 'ALL')

def get_filter_tab(self, name):
select = """SELECT * FROM filter_tabs WHERE name=%(name)s"""
return self._fetchone(select, {'name': name})

def update_filter_tabs(self, tabs):
tab_updates = ','.join([
f"""
(%(name{i})s, %(index{i})s, %(filter{i})s)
""" for i in range(len(tabs))
])
update = f"""
UPDATE filter_tabs as tab
SET name=update.name, index=update.index, filter=update.filter::jsonb
FROM (VALUES
{tab_updates}
) AS update(name, index, filter)
WHERE tab.name = update.name
RETURNING tab.*
"""
objs = {}
for i, tab in enumerate(tabs):
objs.update({f'{key}{i}': value for key, value in tab.items()})

return self._updateall(update, objs, True)

def update_filter_tab_indexes(self, tabs):
tab_updates = ','.join([
f"""
(%(name{i})s, %(index{i})s)
""" for i in range(len(tabs))
])
update = f"""
UPDATE filter_tabs as tab
SET index=update.index
FROM (VALUES
{tab_updates}
) AS update(name, index)
WHERE tab.name = update.name
RETURNING tab.*
"""
objs = {}
for i, tab in enumerate(tabs):
objs.update({f'{key}{i}': value for key, value in tab.items()})

return self._updateall(update, objs, True)

def delete_filter_tab(self, name):
select = """DELETE FROM filter_tabs WHERE name=%(name)s RETURNING name"""
return self._deleteone(select, {'name': name})

def delete_filter_tabs(self, names):
select = """DELETE FROM filter_tabs WHERE name=ANY(%(names)s) RETURNING name"""
return self._deleteall(select, {'names': names}, returning=True)

def create_filter_tab(self, filter):
insert = """
INSERT INTO filter_tabs (name, index, filter)
VALUES (%(name)s, %(index)s, %(filter)s)
RETURNING *
"""
return self._insert(insert, vars(filter))

def create_filter_tabs(self, tabs):
tab_values = ','.join([
f"""
(%(name{i})s, %(index{i})s, %(filter{i})s)
""" for i in range(len(tabs))
])
insert = f"""
INSERT INTO filter_tabs (name, index, filter)
VALUES {tab_values}
RETURNING *
"""

objs = {}
for i, tab in enumerate(tabs):
objs.update({f'{key}{i}': value for key, value in tab.items()})
return self._insert_all(insert, objs)

# BLACKOUTS

def create_blackout(self, blackout):
Expand Down
26 changes: 26 additions & 0 deletions alerta/database/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,32 @@ def get_services(self, query=None, topn=1000):
def get_alert_tags(self, query=None, topn=1000):
raise NotImplementedError

# FILTER TABS

def get_filter_tabs(self):
raise NotImplementedError

def get_filter_tab(self):
raise NotImplementedError

def update_filter_tabs(self, tabs):
raise NotImplementedError

def update_filter_tab_indexes(self, tabs):
raise NotImplementedError

def create_filter_tab(self):
raise NotImplementedError

def create_filter_tabs(self):
raise NotImplementedError

def delete_filter_tab(self, id):
raise NotImplementedError

def delete_filter_tabs(self, ids: list[str]):
raise NotImplementedError

# BLACKOUTS

def create_blackout(self, blackout):
Expand Down
151 changes: 151 additions & 0 deletions alerta/models/filter_tab.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
from datetime import UTC, datetime, timedelta

from werkzeug.datastructures import MultiDict

from alerta.app import db

VALID_PARAMS = [
'id',
'resource',
'event',
'environment',
'severity',
'status',
'service',
'value',
'text',
'tag',
'tags',
'customTags',
'attributes',
'origin',
'createTime',
'timeout',
'rawData',
'customer',
'duplicateCount',
'previousSeverity',
'receiveTime',
'lastReceiveId',
'lastReceiveTime',
'updateTime',
]


class FilterTab:

def __init__(self,name: str, index: int, **kwargs) -> None:
if name is None:
raise ValueError('Missing mandatory value for "name"')
if index is None:
raise ValueError('Missing mandatory value for "index"')

self.name = name
self.index = index
self.filter = kwargs.get('filter') or {}

@classmethod
def parse(cls, json: dict[str, str | int | dict[str, str]]) -> 'FilterTab':
if not isinstance(json.get('index'), int):
raise ValueError('index must be an int')
if not isinstance(json.get('name'), str):
raise ValueError('name must be a string')

return FilterTab(
name=json['name'],
index=json['index'],
filter=json.get('filter', None),
)

@property
def serialize(self):
return {
'name': self.name,
'index': self.index,
'filter': self.filter,
}

@property
def filter_args(self):
def to_isoformat(date: datetime):
return date.isoformat(timespec='milliseconds').replace('+00:00', 'Z')
data = []
for key, value in self.filter.items():
if key == 'dateRange':
if value == {}:
continue
if 'from' in value:
if value.get('select'):
from_time = datetime.fromtimestamp(value['from'], tz=UTC)
else:
from_time = (datetime.now(UTC) + timedelta(seconds=int(value['from'])))
data.append(('from-date', to_isoformat(from_time)))
if 'to' in value:
to_time = datetime.fromtimestamp(value['to'], tz=UTC)
data.append(('to-date', to_isoformat(to_time)))
if key == 'attributes':
for attr_key, attr_value in value.items():
if isinstance(attr_value, list):
for item in attr_value:
data.append((f'attributes.{attr_key}', item))
else:
data.append((f'attributes.{attr_key}', attr_value))
elif key not in VALID_PARAMS or isinstance(value, dict):
continue
elif isinstance(value, list):
for val in value:
data.append((key, val))
else:
data.append(key, value)

return MultiDict(data)

def __repr__(self) -> str:
return f'AlertTab(name={self.name}, index={self.index}, filter={self.filter},'

@ classmethod
def from_db(cls, rec) -> 'FilterTab':
return FilterTab(
name=rec.name,
index=rec.index,
filter=rec.filter
)

# create a filter tab
def create(self) -> 'FilterTab':
return FilterTab.from_db(db.create_filter_tab(self))

# create a filter tabs
@staticmethod
def create_all(tabs: list['FilterTab']) -> list['FilterTab']:
return [FilterTab.from_db(tab) for tab in db.create_filter_tabs(tabs)]

# get a filter tab
@ staticmethod
def find_by_id(id: str):
return FilterTab.from_db(db.get_filter_tab(id))

@staticmethod
def delete_all(ids: list[str]):
return db.delete_filter_tabs(ids)

@staticmethod
def update_all(tabs: list['FilterTab']):
return db.update_filter_tabs(tabs)

@staticmethod
def update_indexes(tabs):
return db.update_filter_tab_indexes(tabs)

@ staticmethod
def find_all() -> list['FilterTab']:
return [
FilterTab.from_db(notification_channel)
for notification_channel in db.get_filter_tabs()
]

# def update(self, **kwargs) -> 'FilterTab':
# return FilterTab.from_db(db.update(self.id, **kwargs))

def delete(self) -> bool:
return db.delete_filter_tab(self.id)
6 changes: 6 additions & 0 deletions alerta/sql/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -628,3 +628,9 @@ CREATE UNIQUE INDEX IF NOT EXISTS env_res_evt_cust_key ON alerts USING btree (en


CREATE UNIQUE INDEX IF NOT EXISTS org_cust_key ON heartbeats USING btree (origin, (COALESCE(customer, ''::text)));

CREATE TABLE IF NOT EXISTS filter_tabs (
name text PRIMARY KEY,
"index" integer NOT NULL,
"filter" jsonb
);
2 changes: 1 addition & 1 deletion alerta/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

api = Blueprint('api', __name__)

from . import alerta, alerts, blackouts, config, customers, groups, keys, oembed, permissions, users, notification_rules, notification_channels, notification_history, on_call, escalation_rules, notification_groups, notification_delays, notification_sends # noqa isort:skip
from . import alerta, alerts, blackouts, config, customers, groups, keys, oembed, permissions, users, notification_rules, notification_channels, notification_history, on_call, escalation_rules, notification_groups, notification_delays, notification_sends, filter_tabs # noqa isort:skip
if get_config('HEARTBEAT_URL') is None:
from . import heartbeats # noqa
else:
Expand Down
Loading
Loading