From 2731c205d80eb537ac9d638c8ee9668d8b25cd11 Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Fri, 27 Dec 2024 12:05:24 -0300 Subject: [PATCH] Minor updates and refactorings --- dev/.env.dev.example | 1 + engine/apps/api/tests/test_organization.py | 3 -- engine/apps/mattermost/backend.py | 1 + engine/apps/mattermost/tests/conftest.py | 18 ++++++- engine/common/api_helpers/mixins.py | 5 +- engine/conftest.py | 11 ----- engine/settings/base.py | 6 ++- engine/settings/ci_test.py | 10 ++-- engine/settings/dev.py | 3 ++ .../src/containers/AlertRules/AlertRules.tsx | 5 +- .../parts/connectors/MattermostConnector.tsx | 14 +++--- .../ExpandedIntegrationRouteDisplay.tsx | 3 +- .../containers/UserSettings/UserSettings.tsx | 2 +- .../parts/connectors/Connectors.tsx | 2 +- .../parts/connectors/MattermostConnector.tsx | 24 ++++++---- .../tabs/MattermostInfo/MattermostInfo.tsx | 4 +- .../models/mattermost/mattermost_channel.ts | 48 ++++++++++++++++++- 17 files changed, 108 insertions(+), 52 deletions(-) diff --git a/dev/.env.dev.example b/dev/.env.dev.example index da276f8a23..426c2cd46b 100644 --- a/dev/.env.dev.example +++ b/dev/.env.dev.example @@ -18,6 +18,7 @@ MATTERMOST_CLIENT_OAUTH_SECRET= MATTERMOST_HOST= MATTERMOST_BOT_TOKEN= MATTERMOST_LOGIN_RETURN_REDIRECT_HOST=http://localhost:8080 +MATTERMOST_SIGNING_SECRET= DJANGO_SETTINGS_MODULE=settings.dev SECRET_KEY=jyRnfRIeMjYfKdoFa9dKXcNaEGGc8GH1TChmYoWW diff --git a/engine/apps/api/tests/test_organization.py b/engine/apps/api/tests/test_organization.py index 46f7cc6552..f4545cd6b8 100644 --- a/engine/apps/api/tests/test_organization.py +++ b/engine/apps/api/tests/test_organization.py @@ -361,6 +361,3 @@ def test_get_organization_telegram_config_checks( assert response.status_code == status.HTTP_200_OK expected_result["is_integration_chatops_connected"] = True assert response.json() == expected_result - - -# TODO: Add test to validate mattermost is integrated once integration PR changes are made diff --git a/engine/apps/mattermost/backend.py b/engine/apps/mattermost/backend.py index 8f0759360f..a43c7757cc 100644 --- a/engine/apps/mattermost/backend.py +++ b/engine/apps/mattermost/backend.py @@ -10,6 +10,7 @@ class MattermostBackend(BaseMessagingBackend): label = "Mattermost" short_label = "Mattermost" available_for_use = True + templater = "apps.mattermost.alert_rendering.AlertMattermostTemplater" def unlink_user(self, user): from apps.mattermost.models import MattermostUser diff --git a/engine/apps/mattermost/tests/conftest.py b/engine/apps/mattermost/tests/conftest.py index 497c4a0c89..aef00804d5 100644 --- a/engine/apps/mattermost/tests/conftest.py +++ b/engine/apps/mattermost/tests/conftest.py @@ -1,6 +1,22 @@ import pytest +from django.conf import settings -from apps.mattermost.tests.factories import MattermostMessageFactory, MattermostUserFactory +if not settings.FEATURE_MATTERMOST_INTEGRATION_ENABLED: + pytest.skip("Mattermost integration is not enabled", allow_module_level=True) +else: + from apps.mattermost.tests.factories import ( + MattermostChannelFactory, + MattermostMessageFactory, + MattermostUserFactory, + ) + + +@pytest.fixture() +def make_mattermost_channel(): + def _make_mattermost_channel(organization, **kwargs): + return MattermostChannelFactory(organization=organization, **kwargs) + + return _make_mattermost_channel @pytest.fixture() diff --git a/engine/common/api_helpers/mixins.py b/engine/common/api_helpers/mixins.py index 25bfb33398..552aaade45 100644 --- a/engine/common/api_helpers/mixins.py +++ b/engine/common/api_helpers/mixins.py @@ -23,7 +23,6 @@ ) from apps.alerts.models import Alert, AlertGroup from apps.base.messaging import get_messaging_backends -from apps.mattermost.alert_rendering import AlertMattermostTemplater from common.api_helpers.exceptions import BadRequest from common.jinja_templater import apply_jinja_template from common.jinja_templater.apply_jinja_template import JinjaTemplateError, JinjaTemplateWarning @@ -239,9 +238,8 @@ def filter_lookups(child): PHONE_CALL = "phone_call" SMS = "sms" TELEGRAM = "telegram" -MATTERMOST = "mattermost" # templates with its own field in db, this concept replaced by messaging_backend_templates field -NOTIFICATION_CHANNEL_OPTIONS = [SLACK, WEB, PHONE_CALL, SMS, TELEGRAM, MATTERMOST] +NOTIFICATION_CHANNEL_OPTIONS = [SLACK, WEB, PHONE_CALL, SMS, TELEGRAM] TITLE = "title" MESSAGE = "message" @@ -260,7 +258,6 @@ def filter_lookups(child): PHONE_CALL: AlertPhoneCallTemplater, SMS: AlertSmsTemplater, TELEGRAM: AlertTelegramTemplater, - MATTERMOST: AlertMattermostTemplater, } # add additionally supported messaging backends diff --git a/engine/conftest.py b/engine/conftest.py index 39d4b6437f..11cd627f64 100644 --- a/engine/conftest.py +++ b/engine/conftest.py @@ -77,7 +77,6 @@ LabelValueFactory, WebhookAssociatedLabelFactory, ) -from apps.mattermost.tests.factories import MattermostChannelFactory, MattermostMessageFactory from apps.mobile_app.models import MobileAppAuthToken, MobileAppVerificationToken from apps.phone_notifications.phone_backend import PhoneBackend from apps.phone_notifications.tests.factories import PhoneCallRecordFactory, SMSRecordFactory @@ -160,8 +159,6 @@ register(AlertReceiveChannelAssociatedLabelFactory) register(GoogleOAuth2UserFactory) register(UserNotificationBundleFactory) -register(MattermostChannelFactory) -register(MattermostMessageFactory) IS_RBAC_ENABLED = os.getenv("ONCALL_TESTING_RBAC_ENABLED", "True") == "True" @@ -919,14 +916,6 @@ def _make_telegram_message(alert_group, message_type, **kwargs): return _make_telegram_message -@pytest.fixture() -def make_mattermost_channel(): - def _make_mattermost_channel(organization, **kwargs): - return MattermostChannelFactory(organization=organization, **kwargs) - - return _make_mattermost_channel - - @pytest.fixture() def make_phone_call_record(): def _make_phone_call_record(receiver, **kwargs): diff --git a/engine/settings/base.py b/engine/settings/base.py index 83e2917ddc..da33c13ce2 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -311,7 +311,6 @@ class DatabaseTypes: "drf_spectacular", "apps.google", "apps.chatops_proxy", - "apps.mattermost", ] if DATABASE_TYPE == DatabaseTypes.MYSQL: @@ -731,7 +730,7 @@ class BrokerTypes: SLACK_INTEGRATION_MAINTENANCE_ENABLED = os.environ.get("SLACK_INTEGRATION_MAINTENANCE_ENABLED", False) # Mattermost -FEATURE_MATTERMOST_INTEGRATION_ENABLED = os.environ.get("FEATURE_MATTERMOST_INTEGRATION_ENABLED", True) +FEATURE_MATTERMOST_INTEGRATION_ENABLED = getenv_boolean("FEATURE_MATTERMOST_INTEGRATION_ENABLED", default=False) MATTERMOST_CLIENT_OAUTH_ID = os.environ.get("MATTERMOST_CLIENT_OAUTH_ID") MATTERMOST_CLIENT_OAUTH_SECRET = os.environ.get("MATTERMOST_CLIENT_OAUTH_SECRET") MATTERMOST_HOST = os.environ.get("MATTERMOST_HOST") @@ -739,6 +738,9 @@ class BrokerTypes: MATTERMOST_LOGIN_RETURN_REDIRECT_HOST = os.environ.get("MATTERMOST_LOGIN_RETURN_REDIRECT_HOST", None) MATTERMOST_SIGNING_SECRET = os.environ.get("MATTERMOST_SIGNING_SECRET", None) +if FEATURE_MATTERMOST_INTEGRATION_ENABLED: + INSTALLED_APPS += ["apps.mattermost"] + SOCIAL_AUTH_SLACK_LOGIN_KEY = SLACK_CLIENT_OAUTH_ID SOCIAL_AUTH_SLACK_LOGIN_SECRET = SLACK_CLIENT_OAUTH_SECRET SOCIAL_AUTH_MATTERMOST_LOGIN_KEY = MATTERMOST_CLIENT_OAUTH_ID diff --git a/engine/settings/ci_test.py b/engine/settings/ci_test.py index 5d64a62021..753c12961f 100644 --- a/engine/settings/ci_test.py +++ b/engine/settings/ci_test.py @@ -58,7 +58,9 @@ # request_model = models.Request.objects.create( SILK_PROFILER_ENABLED = False -# Dummy token -MATTERMOST_HOST = "http://localhost:8065" -MATTERMOST_BOT_TOKEN = "0000000000:XXXXXXXXXXXXXXXXXXXXXXXXXXXX-XXXXXX" -MATTERMOST_SIGNING_SECRET = "f0cb4953bec053e6e616febf2c2392ff60bd02c453a52ab53d9a8b0d0d7284a6" +FEATURE_MATTERMOST_INTEGRATION_ENABLED = True +if FEATURE_MATTERMOST_INTEGRATION_ENABLED: + INSTALLED_APPS += ["apps.mattermost"] + MATTERMOST_HOST = "http://localhost:8065" + MATTERMOST_BOT_TOKEN = "0000000000:XXXXXXXXXXXXXXXXXXXXXXXXXXXX-XXXXXX" + MATTERMOST_SIGNING_SECRET = "f0cb4953bec053e6e616febf2c2392ff60bd02c453a52ab53d9a8b0d0d7284a6" diff --git a/engine/settings/dev.py b/engine/settings/dev.py index 73d1cc6194..0366fa0947 100644 --- a/engine/settings/dev.py +++ b/engine/settings/dev.py @@ -64,7 +64,10 @@ if TESTING: EXTRA_MESSAGING_BACKENDS = [("apps.base.tests.messaging_backend.TestOnlyBackend", 42)] TELEGRAM_TOKEN = "0000000000:XXXXXXXXXXXXXXXXXXXXXXXXXXXX-XXXXXX" + MATTERMOST_HOST = "http://localhost:8065" MATTERMOST_BOT_TOKEN = "0000000000:XXXXXXXXXXXXXXXXXXXXXXXXXXXX-XXXXXX" + MATTERMOST_SIGNING_SECRET = "f0cb4953bec053e6e616febf2c2392ff60bd02c453a52ab53d9a8b0d0d7284a6" + TWILIO_AUTH_TOKEN = "twilio_auth_token" # charset/collation related tests don't work without this diff --git a/grafana-plugin/src/containers/AlertRules/AlertRules.tsx b/grafana-plugin/src/containers/AlertRules/AlertRules.tsx index 748db8d646..69af50e507 100644 --- a/grafana-plugin/src/containers/AlertRules/AlertRules.tsx +++ b/grafana-plugin/src/containers/AlertRules/AlertRules.tsx @@ -29,12 +29,11 @@ export const ChatOpsConnectors = (props: ChatOpsConnectorsProps) => { useEffect(() => { msteamsChannelStore.updateMSTeamsChannels(); - mattermostChannelStore.updateItems(); + mattermostChannelStore.updateMattermostChannels(); }, []); const isMSTeamsInstalled = msteamsChannelStore.currentTeamToMSTeamsChannel?.length > 0; - const connectedChannels = mattermostChannelStore.getSearchResult(); - const isMattermostInstalled = store.hasFeature(AppFeature.Mattermost) && connectedChannels && connectedChannels.length + const isMattermostInstalled = store.hasFeature(AppFeature.Mattermost) && mattermostChannelStore.currentTeamToMattermostChannel?.length > 0; if (!isSlackInstalled && !isTelegramInstalled && !isMSTeamsInstalled && !isMattermostInstalled) { return null; diff --git a/grafana-plugin/src/containers/AlertRules/parts/connectors/MattermostConnector.tsx b/grafana-plugin/src/containers/AlertRules/parts/connectors/MattermostConnector.tsx index fe8502350d..7316619e45 100644 --- a/grafana-plugin/src/containers/AlertRules/parts/connectors/MattermostConnector.tsx +++ b/grafana-plugin/src/containers/AlertRules/parts/connectors/MattermostConnector.tsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react'; import { GSelect } from 'containers/GSelect/GSelect'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; -import { ChannelFilter } from "models/channel_filter/channel_filter.types"; +import { ChannelFilter } from 'models/channel_filter/channel_filter.types'; import { MattermostChannel } from 'models/mattermost/mattermost.types'; import { useStore } from 'state/useStore'; @@ -27,9 +27,9 @@ export const MattermostConnector = observer((props: MattermostConnectorProps) => const { alertReceiveChannelStore, mattermostChannelStore, - + // dereferencing items is needed to rerender GSelect mattermostChannelStore: { items: mattermostChannelItems }, - } = store + } = store; const channelFilter = alertReceiveChannelStore.channelFilters[channelFilterId]; @@ -53,9 +53,9 @@ export const MattermostConnector = observer((props: MattermostConnectorProps) =>
@@ -78,4 +78,4 @@ export const MattermostConnector = observer((props: MattermostConnectorProps) => ); -}) +}); diff --git a/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx b/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx index a970c3de67..30455ffaf5 100644 --- a/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx +++ b/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx @@ -100,7 +100,6 @@ export const ExpandedIntegrationRouteDisplay: React.FC { setIsLoading(true); (async () => { - await Promise.all([escalationChainStore.updateItems(), telegramChannelStore.updateTelegramChannels(), mattermostChannelStore.updateItems()]); + await Promise.all([escalationChainStore.updateItems(), telegramChannelStore.updateTelegramChannels()]); setIsLoading(false); })(); }, []); diff --git a/grafana-plugin/src/containers/UserSettings/UserSettings.tsx b/grafana-plugin/src/containers/UserSettings/UserSettings.tsx index ba9eb311ec..6829e5c796 100644 --- a/grafana-plugin/src/containers/UserSettings/UserSettings.tsx +++ b/grafana-plugin/src/containers/UserSettings/UserSettings.tsx @@ -46,7 +46,7 @@ function getGoogleMessage(googleError: GoogleError) { } function getMattermostErrorMessage(mattermostError: MattermostError) { - if (mattermostError == MattermostError.MATTERMOST_AUTH_FETCH_USER_ERROR) { + if (mattermostError === MattermostError.MATTERMOST_AUTH_FETCH_USER_ERROR) { return ( <> Couldn't connect your Mattermost account. Failed to fetch user information from your mattermost server. Please diff --git a/grafana-plugin/src/containers/UserSettings/parts/connectors/Connectors.tsx b/grafana-plugin/src/containers/UserSettings/parts/connectors/Connectors.tsx index 396766d311..7aa06d9735 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/connectors/Connectors.tsx +++ b/grafana-plugin/src/containers/UserSettings/parts/connectors/Connectors.tsx @@ -29,8 +29,8 @@ export const Connectors: FC = observer((props) => { {store.hasFeature(AppFeature.Telegram) && } - {store.hasFeature(AppFeature.MsTeams) && } {store.hasFeature(AppFeature.Mattermost) && } + {store.hasFeature(AppFeature.MsTeams) && } Calendar export diff --git a/grafana-plugin/src/containers/UserSettings/parts/connectors/MattermostConnector.tsx b/grafana-plugin/src/containers/UserSettings/parts/connectors/MattermostConnector.tsx index f350ed1d99..7b3ec934e6 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/connectors/MattermostConnector.tsx +++ b/grafana-plugin/src/containers/UserSettings/parts/connectors/MattermostConnector.tsx @@ -25,7 +25,7 @@ export const MattermostConnector = observer((props: MattermostConnectorProps) => const handleConnectButtonClick = useCallback(() => { onTabChange(UserSettingsTab.MattermostInfo); - }, [onTabChange]); + }, []); const handleUnlinkMattermostAccount = useCallback(() => { userStore.unlinkBackend(id, 'MATTERMOST'); @@ -35,24 +35,28 @@ export const MattermostConnector = observer((props: MattermostConnectorProps) => return (
- - {mattermostConfigured ? ( + {storeUser.messaging_backends.MATTERMOST ? ( + - + - )} - + + ) : ( +
+ + + +
+ )}
); }); diff --git a/grafana-plugin/src/containers/UserSettings/parts/tabs/MattermostInfo/MattermostInfo.tsx b/grafana-plugin/src/containers/UserSettings/parts/tabs/MattermostInfo/MattermostInfo.tsx index a4f3cac8cb..71b6852dd2 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/MattermostInfo/MattermostInfo.tsx +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/MattermostInfo/MattermostInfo.tsx @@ -25,9 +25,9 @@ export const MattermostInfo = () => { - Personal Mattermost connection will allow you to manage alert group in your connected mattermost channel + Personal Mattermost connection will allow you to manage alert groups in your connected Mattermost channel - To setup personal mattermost click the button below and login to your mattermost server + To link your Mattermost account, click the button below and login to your server More details in{' '} diff --git a/grafana-plugin/src/models/mattermost/mattermost_channel.ts b/grafana-plugin/src/models/mattermost/mattermost_channel.ts index 0276f21f4c..1be33c7e78 100644 --- a/grafana-plugin/src/models/mattermost/mattermost_channel.ts +++ b/grafana-plugin/src/models/mattermost/mattermost_channel.ts @@ -1,4 +1,4 @@ -import { action, observable, makeObservable, runInAction } from 'mobx'; +import { action, computed, observable, makeObservable, runInAction } from 'mobx'; import { BaseStore } from 'models/base_store'; import { makeRequest } from 'network/network'; @@ -10,9 +10,14 @@ export class MattermostChannelStore extends BaseStore { @observable.shallow items: { [id: string]: MattermostChannel } = {}; + @observable + currentTeamToMattermostChannel?: Array; + @observable.shallow searchResult: { [key: string]: Array } = {}; + private autoUpdateTimer?: ReturnType; + constructor(rootStore: RootStore) { super(rootStore); @@ -21,6 +26,28 @@ export class MattermostChannelStore extends BaseStore { this.path = '/mattermost/channels/'; } + @action.bound + async updateMattermostChannels() { + const response = await makeRequest(this.path, {}); + + const items = response.reduce( + (acc: any, mattermostChannel: MattermostChannel) => ({ + ...acc, + [mattermostChannel.id]: mattermostChannel, + }), + {} + ); + + runInAction(() => { + this.items = { + ...this.items, + ...items, + }; + + this.currentTeamToMattermostChannel = response.map((mattermostChannel: MattermostChannel) => mattermostChannel.id); + }); + } + @action.bound async updateById(id: MattermostChannel['id']) { const response = await this.getById(id); @@ -65,6 +92,21 @@ export class MattermostChannelStore extends BaseStore { ); }; + @computed + get hasItems() { + return Boolean(this.getSearchResult('')?.length); + } + + async startAutoUpdate() { + this.autoUpdateTimer = setInterval(this.updateMattermostChannels.bind(this), 3000); + } + + async stopAutoUpdate() { + if (this.autoUpdateTimer) { + clearInterval(this.autoUpdateTimer); + } + } + @action.bound async makeMattermostChannelDefault(id: MattermostChannel['id']) { return makeRequest(`/mattermost/channels/${id}/set_default`, { @@ -75,4 +117,8 @@ export class MattermostChannelStore extends BaseStore { async deleteMattermostChannel(id: MattermostChannel['id']) { return super.delete(id); } + + async getMattermostChannels() { + return super.getAll(); + } }