v1.9.2
This commit is contained in:
commit
aa85994d78
15 changed files with 129 additions and 62 deletions
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
20.16.0
|
||||
|
|
@ -38,7 +38,7 @@ The following is required to connect to Microsoft Teams to Grafana OnCall:
|
|||
|
||||
- You must have Admin permissions in your Grafana Cloud instance.
|
||||
- You must have Owner permissions in Microsoft Teams.
|
||||
- Install the Grafana OnCall app from the [Microsoft Marketplace](https://appsource.microsoft.com/en-us/product/office/WA200004307).
|
||||
- Install the Grafana IRM app from the [Microsoft Marketplace](https://appsource.microsoft.com/en-us/product/office/WA200004307).
|
||||
|
||||
## Install Microsoft Teams integration for Grafana OnCall
|
||||
|
||||
|
|
|
|||
|
|
@ -447,10 +447,9 @@ def test_pagination(settings, alert_group_public_api_setup):
|
|||
token, alert_groups, _, _ = alert_group_public_api_setup
|
||||
client = APIClient()
|
||||
|
||||
url = reverse("api-public:alert_groups-list")
|
||||
url = "{}?perpage=1".format(reverse("api-public:alert_groups-list"))
|
||||
|
||||
with patch("common.api_helpers.paginators.PathPrefixedPagePagination.get_page_size", return_value=1):
|
||||
response = client.get(url, HTTP_AUTHORIZATION=token)
|
||||
response = client.get(url, HTTP_AUTHORIZATION=token)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
result = response.json()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from typing import Optional, Tuple, Union
|
|||
|
||||
from django.conf import settings
|
||||
from telegram import Bot, InlineKeyboardMarkup, Message, ParseMode
|
||||
from telegram.error import BadRequest, InvalidToken, Unauthorized
|
||||
from telegram.error import BadRequest, InvalidToken, TelegramError, Unauthorized
|
||||
from telegram.utils.request import Request
|
||||
|
||||
from apps.alerts.models import AlertGroup
|
||||
|
|
@ -27,6 +27,18 @@ class TelegramClient:
|
|||
if self.token is None:
|
||||
raise InvalidToken()
|
||||
|
||||
class BadRequestMessage:
|
||||
CHAT_NOT_FOUND = "Chat not found"
|
||||
MESSAGE_IS_NOT_MODIFIED = "Message is not modified"
|
||||
MESSAGE_TO_EDIT_NOT_FOUND = "Message to edit not found"
|
||||
NEED_ADMIN_RIGHTS_IN_THE_CHANNEL = "Need administrator rights in the channel chat"
|
||||
MESSAGE_TO_BE_REPLIED_NOT_FOUND = "Message to be replied not found"
|
||||
|
||||
class UnauthorizedMessage:
|
||||
BOT_WAS_BLOCKED_BY_USER = "Forbidden: bot was blocked by the user"
|
||||
INVALID_TOKEN = "Invalid token"
|
||||
USER_IS_DEACTIVATED = "Forbidden: user is deactivated"
|
||||
|
||||
@property
|
||||
def api_client(self) -> Bot:
|
||||
return Bot(self.token, request=Request(read_timeout=15))
|
||||
|
|
@ -96,7 +108,7 @@ class TelegramClient:
|
|||
disable_web_page_preview=False,
|
||||
)
|
||||
except BadRequest as e:
|
||||
logger.warning("Telegram BadRequest: {}".format(e.message))
|
||||
logger.warning(f"Telegram BadRequest: {e.message}")
|
||||
raise
|
||||
|
||||
return message
|
||||
|
|
@ -171,3 +183,7 @@ class TelegramClient:
|
|||
raise Exception(f"_get_message_and_keyboard with type {message_type} is not implemented")
|
||||
|
||||
return text, keyboard
|
||||
|
||||
@staticmethod
|
||||
def error_message_is(error: TelegramError, messages: list[str]) -> bool:
|
||||
return error.message in messages
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ def ignore_message_unchanged(f):
|
|||
try:
|
||||
return f(*args, **kwargs)
|
||||
except error.BadRequest as e:
|
||||
if "Message is not modified" in e.message:
|
||||
if TelegramClient.error_message_is(e, [TelegramClient.BadRequestMessage.MESSAGE_IS_NOT_MODIFIED]):
|
||||
logger.warning(
|
||||
f"Tried to change Telegram message, but update is identical to original message. "
|
||||
f"args: {args}, kwargs: {kwargs}"
|
||||
|
|
@ -59,7 +59,7 @@ def ignore_message_to_edit_deleted(f):
|
|||
try:
|
||||
return f(*args, **kwargs)
|
||||
except error.BadRequest as e:
|
||||
if "Message to edit not found" in e.message:
|
||||
if TelegramClient.error_message_is(e, [TelegramClient.BadRequestMessage.MESSAGE_TO_EDIT_NOT_FOUND]):
|
||||
logger.warning(
|
||||
f"Tried to edit Telegram message, but message was deleted. args: {args}, kwargs: {kwargs}"
|
||||
)
|
||||
|
|
@ -75,7 +75,7 @@ def ignore_reply_to_message_deleted(f):
|
|||
try:
|
||||
return f(*args, **kwargs)
|
||||
except error.BadRequest as e:
|
||||
if "Replied message not found" in e.message:
|
||||
if TelegramClient.error_message_is(e, [TelegramClient.BadRequestMessage.MESSAGE_TO_BE_REPLIED_NOT_FOUND]):
|
||||
logger.warning(
|
||||
f"Tried to reply to Telegram message, but message was deleted. args: {args}, kwargs: {kwargs}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -123,15 +123,16 @@ class TelegramToOrganizationConnector(models.Model):
|
|||
chat_id=self.channel_chat_id, message_type=TelegramMessage.ALERT_GROUP_MESSAGE, alert_group=alert_group
|
||||
)
|
||||
except error.BadRequest as e:
|
||||
if e.message == "Need administrator rights in the channel chat":
|
||||
if TelegramClient.error_message_is(
|
||||
e,
|
||||
[
|
||||
TelegramClient.BadRequestMessage.NEED_ADMIN_RIGHTS_IN_THE_CHANNEL,
|
||||
TelegramClient.BadRequestMessage.CHAT_NOT_FOUND,
|
||||
],
|
||||
):
|
||||
logger.warning(
|
||||
f"Could not send alert group to Telegram channel with id {self.channel_chat_id} "
|
||||
f"due to lack of admin rights. alert_group {alert_group.pk}"
|
||||
)
|
||||
elif e.message == "Chat not found":
|
||||
logger.warning(
|
||||
f"Could not send alert group to Telegram channel with id {self.channel_chat_id} "
|
||||
f"due to 'Chat not found'. alert_group {alert_group.pk}"
|
||||
f"due to {e.message}. alert_group {alert_group.pk}"
|
||||
)
|
||||
else:
|
||||
telegram_client.send_message(
|
||||
|
|
|
|||
|
|
@ -25,6 +25,14 @@ class TelegramToUserConnector(models.Model):
|
|||
class Meta:
|
||||
unique_together = (("user", "telegram_chat_id"),)
|
||||
|
||||
class NotificationErrorReason:
|
||||
USER_WAS_DISABLED = "Telegram user was disabled"
|
||||
INVALID_TOKEN = "Invalid token"
|
||||
BOT_BLOCKED_BY_USER = "Bot was blocked by the user"
|
||||
FORMATTING_ERROR_IN_RENDERED_TEMPLATE = (
|
||||
"Notification sent but there was a formatting error in the rendered template"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def notify_user(cls, user: User, alert_group: AlertGroup, notification_policy: UserNotificationPolicy) -> None:
|
||||
try:
|
||||
|
|
@ -98,7 +106,7 @@ class TelegramToUserConnector(models.Model):
|
|||
self.user,
|
||||
notification_policy,
|
||||
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
|
||||
reason="Invalid token",
|
||||
reason=self.NotificationErrorReason.INVALID_TOKEN,
|
||||
)
|
||||
return
|
||||
|
||||
|
|
@ -123,7 +131,7 @@ class TelegramToUserConnector(models.Model):
|
|||
self.user,
|
||||
notification_policy,
|
||||
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_FORMATTING_ERROR,
|
||||
reason="Notification sent but there was a formatting error in the rendered template",
|
||||
reason=self.NotificationErrorReason.FORMATTING_ERROR_IN_RENDERED_TEMPLATE,
|
||||
)
|
||||
telegram_client.send_message(
|
||||
chat_id=self.telegram_chat_id,
|
||||
|
|
@ -131,29 +139,29 @@ class TelegramToUserConnector(models.Model):
|
|||
alert_group=alert_group,
|
||||
)
|
||||
except error.Unauthorized as e:
|
||||
if e.message == "Forbidden: bot was blocked by the user":
|
||||
if TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.BOT_WAS_BLOCKED_BY_USER]):
|
||||
TelegramToUserConnector.create_telegram_notification_error(
|
||||
alert_group,
|
||||
self.user,
|
||||
notification_policy,
|
||||
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_BOT_IS_DELETED,
|
||||
reason="Bot was blocked by the user",
|
||||
reason=self.NotificationErrorReason.BOT_BLOCKED_BY_USER,
|
||||
)
|
||||
elif e.message == "Invalid token":
|
||||
elif TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.INVALID_TOKEN]):
|
||||
TelegramToUserConnector.create_telegram_notification_error(
|
||||
alert_group,
|
||||
self.user,
|
||||
notification_policy,
|
||||
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
|
||||
reason="Invalid token",
|
||||
reason=self.NotificationErrorReason.INVALID_TOKEN,
|
||||
)
|
||||
elif e.message == "Forbidden: user is deactivated":
|
||||
elif TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.USER_IS_DEACTIVATED]):
|
||||
TelegramToUserConnector.create_telegram_notification_error(
|
||||
alert_group,
|
||||
self.user,
|
||||
notification_policy,
|
||||
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_USER_IS_DEACTIVATED,
|
||||
reason="Telegram user was disabled",
|
||||
reason=self.NotificationErrorReason.USER_WAS_DISABLED,
|
||||
)
|
||||
else:
|
||||
raise e
|
||||
|
|
@ -175,7 +183,7 @@ class TelegramToUserConnector(models.Model):
|
|||
self.user,
|
||||
notification_policy,
|
||||
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
|
||||
reason="Invalid token",
|
||||
reason=self.NotificationErrorReason.INVALID_TOKEN,
|
||||
)
|
||||
return
|
||||
|
||||
|
|
@ -193,29 +201,29 @@ class TelegramToUserConnector(models.Model):
|
|||
alert_group=alert_group,
|
||||
)
|
||||
except error.Unauthorized as e:
|
||||
if e.message == "Forbidden: bot was blocked by the user":
|
||||
if TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.BOT_WAS_BLOCKED_BY_USER]):
|
||||
TelegramToUserConnector.create_telegram_notification_error(
|
||||
alert_group,
|
||||
self.user,
|
||||
notification_policy,
|
||||
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_BOT_IS_DELETED,
|
||||
reason="Bot was blocked by the user",
|
||||
reason=self.NotificationErrorReason.BOT_BLOCKED_BY_USER,
|
||||
)
|
||||
elif e.message == "Invalid token":
|
||||
elif TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.INVALID_TOKEN]):
|
||||
TelegramToUserConnector.create_telegram_notification_error(
|
||||
alert_group,
|
||||
self.user,
|
||||
notification_policy,
|
||||
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
|
||||
reason="Invalid token",
|
||||
reason=self.NotificationErrorReason.INVALID_TOKEN,
|
||||
)
|
||||
elif e.message == "Forbidden: user is deactivated":
|
||||
elif TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.USER_IS_DEACTIVATED]):
|
||||
TelegramToUserConnector.create_telegram_notification_error(
|
||||
alert_group,
|
||||
self.user,
|
||||
notification_policy,
|
||||
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_USER_IS_DEACTIVATED,
|
||||
reason="Telegram user was disabled",
|
||||
reason=self.NotificationErrorReason.USER_WAS_DISABLED,
|
||||
)
|
||||
else:
|
||||
raise e
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ def edit_message(self, message_pk):
|
|||
try:
|
||||
telegram_client.edit_message(message=message)
|
||||
except error.BadRequest as e:
|
||||
if "Message is not modified" in e.message:
|
||||
if TelegramClient.error_message_is(e, [TelegramClient.BadRequestMessage.MESSAGE_IS_NOT_MODIFIED]):
|
||||
pass
|
||||
except (error.RetryAfter, error.TimedOut) as e:
|
||||
countdown = getattr(e, "retry_after", 3)
|
||||
|
|
@ -165,20 +165,19 @@ def send_log_and_actions_message(self, channel_chat_id, group_chat_id, channel_m
|
|||
reply_to_message_id=reply_to_message_id,
|
||||
)
|
||||
except error.BadRequest as e:
|
||||
if e.message == "Chat not found":
|
||||
if TelegramClient.error_message_is(
|
||||
e,
|
||||
[
|
||||
TelegramClient.BadRequestMessage.CHAT_NOT_FOUND,
|
||||
TelegramClient.BadRequestMessage.MESSAGE_TO_BE_REPLIED_NOT_FOUND,
|
||||
],
|
||||
):
|
||||
logger.warning(
|
||||
f"Could not send log and actions messages to Telegram group with id {group_chat_id} "
|
||||
f"due to 'Chat not found'. alert_group {alert_group.pk}"
|
||||
f"due to '{e.message}'. alert_group {alert_group.pk}"
|
||||
)
|
||||
return
|
||||
elif e.message == "Message to reply not found":
|
||||
logger.warning(
|
||||
f"Could not send log and actions messages to Telegram group with id {group_chat_id} "
|
||||
f"due to 'Message to reply not found'. alert_group {alert_group.pk}"
|
||||
)
|
||||
return
|
||||
else:
|
||||
raise
|
||||
raise
|
||||
|
||||
|
||||
@shared_dedicated_queue_retry_task(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ from apps.telegram.client import TelegramClient
|
|||
from apps.telegram.models import TelegramMessage, TelegramToUserConnector
|
||||
|
||||
|
||||
@patch.object(TelegramClient, "send_raw_message", side_effect=error.BadRequest("Replied message not found"))
|
||||
@patch.object(
|
||||
TelegramClient,
|
||||
"send_raw_message",
|
||||
side_effect=error.BadRequest(TelegramClient.BadRequestMessage.MESSAGE_TO_BE_REPLIED_NOT_FOUND),
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
def test_personal_connector_replied_message_not_found(
|
||||
mock_send_message,
|
||||
|
|
@ -53,19 +57,19 @@ def test_personal_connector_replied_message_not_found(
|
|||
"side_effect,notification_error_code,reason",
|
||||
[
|
||||
(
|
||||
error.Unauthorized("Forbidden: bot was blocked by the user"),
|
||||
error.Unauthorized(TelegramClient.UnauthorizedMessage.BOT_WAS_BLOCKED_BY_USER),
|
||||
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_BOT_IS_DELETED,
|
||||
"Bot was blocked by the user",
|
||||
TelegramToUserConnector.NotificationErrorReason.BOT_BLOCKED_BY_USER,
|
||||
),
|
||||
(
|
||||
error.Unauthorized("Invalid token"),
|
||||
error.Unauthorized(TelegramClient.UnauthorizedMessage.INVALID_TOKEN),
|
||||
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
|
||||
"Invalid token",
|
||||
TelegramToUserConnector.NotificationErrorReason.INVALID_TOKEN,
|
||||
),
|
||||
(
|
||||
error.Unauthorized("Forbidden: user is deactivated"),
|
||||
error.Unauthorized(TelegramClient.UnauthorizedMessage.USER_IS_DEACTIVATED),
|
||||
UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_USER_IS_DEACTIVATED,
|
||||
"Telegram user was disabled",
|
||||
TelegramToUserConnector.NotificationErrorReason.USER_WAS_DISABLED,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ from apps.telegram.client import TelegramClient
|
|||
from apps.telegram.models import TelegramMessage
|
||||
from apps.telegram.tasks import send_log_and_actions_message
|
||||
|
||||
bad_request_error_msg = TelegramClient.BadRequestMessage.MESSAGE_TO_BE_REPLIED_NOT_FOUND
|
||||
|
||||
@patch.object(TelegramClient, "send_raw_message", side_effect=error.BadRequest("Message to reply not found"))
|
||||
|
||||
@patch.object(TelegramClient, "send_raw_message", side_effect=error.BadRequest(bad_request_error_msg))
|
||||
@pytest.mark.django_db
|
||||
def test_send_log_and_actions_replied_message_not_found(
|
||||
mock_send_message,
|
||||
|
|
@ -42,6 +44,6 @@ def test_send_log_and_actions_replied_message_not_found(
|
|||
|
||||
expected_msg = (
|
||||
f"Could not send log and actions messages to Telegram group with id group_chat_id "
|
||||
f"due to 'Message to reply not found'. alert_group {alert_group.pk}"
|
||||
f"due to '{bad_request_error_msg}'. alert_group {alert_group.pk}"
|
||||
)
|
||||
assert expected_msg in caplog.text
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import typing
|
||||
|
||||
from django.core.paginator import EmptyPage
|
||||
from rest_framework.pagination import BasePagination, CursorPagination, PageNumberPagination
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
|
@ -54,6 +55,44 @@ class PathPrefixedPagePagination(BasePathPrefixedPagination, PageNumberPaginatio
|
|||
)
|
||||
return paginated_schema
|
||||
|
||||
def paginate_queryset(self, queryset, request, view=None):
|
||||
request.build_absolute_uri = lambda: create_engine_url(request.get_full_path())
|
||||
per_page = request.query_params.get(self.page_size_query_param, self.page_size)
|
||||
try:
|
||||
per_page = int(per_page)
|
||||
except ValueError:
|
||||
per_page = self.page_size
|
||||
|
||||
if per_page < 1:
|
||||
per_page = self.page_size
|
||||
|
||||
paginator = self.django_paginator_class(queryset, per_page)
|
||||
page_number = request.query_params.get(self.page_query_param, 1)
|
||||
try:
|
||||
page_number = int(page_number)
|
||||
except ValueError:
|
||||
page_number = 1
|
||||
|
||||
if page_number < 1:
|
||||
page_number = 1
|
||||
|
||||
try:
|
||||
self.page = self.get_page(page_number, paginator)
|
||||
except EmptyPage:
|
||||
self.page = paginator.page(paginator.num_pages)
|
||||
|
||||
if paginator.num_pages > 1 and self.template is not None:
|
||||
self.display_page_controls = True
|
||||
|
||||
self.request = request
|
||||
return list(self.page)
|
||||
|
||||
def get_page(self, page_number, paginator):
|
||||
try:
|
||||
return paginator.page(page_number)
|
||||
except EmptyPage:
|
||||
return paginator.page(paginator.num_pages)
|
||||
|
||||
|
||||
class PathPrefixedCursorPagination(BasePathPrefixedPagination, CursorPagination):
|
||||
def get_paginated_response(self, data: PaginatedData) -> Response:
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
20.x
|
||||
|
|
@ -126,9 +126,6 @@
|
|||
"webpack-cli": "^5.1.4",
|
||||
"webpack-livereload-plugin": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/modifiers": "^7.0.0",
|
||||
|
|
|
|||
|
|
@ -79,20 +79,20 @@ export const MSTeamsInstructions: FC<MSTeamsInstructionsProps> = observer((props
|
|||
<a href="https://appsource.microsoft.com/en-us/product/office/WA200004307" target="_blank" rel="noreferrer">
|
||||
<Text type="link">MS Teams marketplace</Text>
|
||||
</a>{' '}
|
||||
and add <Text type="primary">Grafana OnCall app</Text> to your MS Teams org workspace.{' '}
|
||||
and add <Text type="primary">Grafana IRM app</Text> to your MS Teams org workspace.{' '}
|
||||
</Text>
|
||||
)}
|
||||
<Text type="secondary">
|
||||
{!onCallisAdded ? 2 : 1}.{' '}
|
||||
{personalSettings ? (
|
||||
<Text type="secondary">
|
||||
Send a direct message to the Grafana OnCall bot using <Text type="primary">linkUser</Text> command with
|
||||
Send a direct message to the Grafana IRM bot using <Text type="primary">linkUser</Text> command with
|
||||
following code:
|
||||
</Text>
|
||||
) : (
|
||||
<Text type="secondary">
|
||||
Add OnCall bot to your team channel and send this code by{' '}
|
||||
<Text type="primary">@Grafana OnCall linkTeam</Text> command
|
||||
Add IRM bot to your team channel and send this code by <Text type="primary">@Grafana IRM linkTeam</Text>{' '}
|
||||
command
|
||||
</Text>
|
||||
)}
|
||||
<Field className={cx('field-command')}>
|
||||
|
|
|
|||
|
|
@ -660,6 +660,7 @@ class _IntegrationsPage extends React.Component<IntegrationsProps, IntegrationsS
|
|||
|
||||
invalidateRequestFn = (requestedPage: number) => {
|
||||
const { store } = this.props;
|
||||
|
||||
return requestedPage !== store.filtersStore.currentTablePageNum[PAGE.Integrations];
|
||||
};
|
||||
|
||||
|
|
@ -696,6 +697,10 @@ class _IntegrationsPage extends React.Component<IntegrationsProps, IntegrationsS
|
|||
const { alertReceiveChannelStore } = store;
|
||||
const newPage = isOnMount ? store.filtersStore.currentTablePageNum[PAGE.Integrations] : 1;
|
||||
|
||||
runInAction(() => {
|
||||
store.filtersStore.currentTablePageNum[PAGE.Integrations] = newPage;
|
||||
});
|
||||
|
||||
await alertReceiveChannelStore.fetchPaginatedItems({
|
||||
filters: this.getFiltersBasedOnCurrentTab(),
|
||||
page: newPage,
|
||||
|
|
@ -703,9 +708,6 @@ class _IntegrationsPage extends React.Component<IntegrationsProps, IntegrationsS
|
|||
invalidateFn: () => this.invalidateRequestFn(newPage),
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
store.filtersStore.currentTablePageNum[PAGE.Integrations] = newPage;
|
||||
});
|
||||
LocationHelper.update({ p: newPage }, 'partial');
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue