diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index fc6d4a64..0717e96a 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -144,12 +144,14 @@ jobs: git clone https://x-access-token:${{ steps.generate-token.outputs.token }}@github.com/grafana/ops-devenv.git git clone https://x-access-token:${{ steps.generate-token.outputs.token }}@github.com/grafana/gops-labels.git - - name: Tilt CI - standard and expensive E2E tests + - name: Tilt CI - Expensive E2E tests if: inputs.run-expensive-tests shell: bash env: E2E_TESTS_CMD: "cd ../../grafana-plugin && yarn test:e2e-expensive" GRAFANA_VERSION: ${{ inputs.grafana_version }} + GF_FEATURE_TOGGLES_ENABLE: "externalServiceAccounts" + ONCALL_API_URL: "http://oncall-dev-engine:8080" GRAFANA_ADMIN_USERNAME: "irm" GRAFANA_ADMIN_PASSWORD: "irm" BROWSERS: ${{ inputs.browsers }} diff --git a/.github/workflows/expensive-e2e-tests.yml b/.github/workflows/expensive-e2e-tests.yml index 6e8af944..14969765 100644 --- a/.github/workflows/expensive-e2e-tests.yml +++ b/.github/workflows/expensive-e2e-tests.yml @@ -24,8 +24,8 @@ jobs: # - 9.3.16 # - 9.4.13 # - 9.5.7 - - 10.0.11 - 10.1.7 + - 10.3.3 # TODO: fix issues with running e2e tests against Grafana v10.2.x and v10.3.x # - 10.2.4 # - latest @@ -55,7 +55,7 @@ jobs: # - uses: slackapi/slack-github-action@v1.24.0 with: - channel-id: gops-oncall-dev + channel-id: gops-irm-dev # yamllint disable rule:line-length payload: | { diff --git a/.github/workflows/linting-and-tests.yml b/.github/workflows/linting-and-tests.yml index 83f1ff61..28458a03 100644 --- a/.github/workflows/linting-and-tests.yml +++ b/.github/workflows/linting-and-tests.yml @@ -239,9 +239,16 @@ jobs: end-to-end-tests: name: Standard e2e tests uses: ./.github/workflows/e2e-tests.yml + strategy: + matrix: + grafana_version: + - 10.1.7 + - 10.3.3 + # TODO: fix issues with running e2e tests against Grafana v10.2.x and latest + # - 10.2.4 + # - latest + fail-fast: false with: - # TODO: fix issues with running e2e tests against Grafana v10.2.x and v10.3.x - grafana_version: 10.1.7 - # grafana_version: 10.3.3 + grafana_version: ${{ matrix.grafana_version }} run-expensive-tests: false browsers: "chromium" diff --git a/Tiltfile b/Tiltfile index 1e00bad3..8077d595 100644 --- a/Tiltfile +++ b/Tiltfile @@ -1,6 +1,7 @@ load('ext://uibutton', 'cmd_button', 'location', 'text_input', 'bool_input') load("ext://configmap", "configmap_create") +grafana_url = os.getenv("GRAFANA_URL", "http://grafana:3000") running_under_parent_tiltfile = os.getenv("TILT_PARENT", "false") == "true" twilio_values=[ "oncall.twilio.accountSid=" + os.getenv("TWILIO_ACCOUNT_SID", ""), @@ -29,6 +30,14 @@ def plugin_json(): return plugin_file return 'NOT_A_PLUGIN' +def extra_env(): + return { + "GF_APP_URL": grafana_url, + "GF_SERVER_ROOT_URL": grafana_url, + "GF_FEATURE_TOGGLES_ENABLE": "externalServiceAccounts", + "ONCALL_API_URL": "http://oncall-dev-engine:8080" + } + allow_k8s_contexts(["kind-kind"]) @@ -83,8 +92,6 @@ def load_grafana(): # The user/pass that you will login to Grafana with grafana_admin_user_pass = os.getenv("GRAFANA_ADMIN_USER_PASS", "oncall") grafana_version = os.getenv("GRAFANA_VERSION", "latest") - grafana_url = os.getenv("GRAFANA_URL", "http://grafana:3000") - if 'plugin' in profiles: k8s_resource( diff --git a/engine/apps/alerts/models/alert_group.py b/engine/apps/alerts/models/alert_group.py index 3fceaec0..49fa6325 100644 --- a/engine/apps/alerts/models/alert_group.py +++ b/engine/apps/alerts/models/alert_group.py @@ -201,7 +201,6 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models. resolved_by_user: typing.Optional["User"] root_alert_group: typing.Optional["AlertGroup"] silenced_by_user: typing.Optional["User"] - slack_log_message: typing.Optional["SlackMessage"] slack_messages: "RelatedManager['SlackMessage']" users: "RelatedManager['User']" labels: "RelatedManager['AlertGroupAssociatedLabel']" @@ -396,6 +395,7 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models. related_name="wiped_alert_groups", ) + # TODO: drop this column in future release slack_log_message = models.OneToOneField( "slack.SlackMessage", on_delete=models.SET_NULL, diff --git a/engine/apps/alerts/signals.py b/engine/apps/alerts/signals.py index a01d06cf..3de51028 100644 --- a/engine/apps/alerts/signals.py +++ b/engine/apps/alerts/signals.py @@ -36,10 +36,6 @@ alert_group_action_triggered_signal.connect( AlertGroupSlackRepresentative.on_alert_group_action_triggered, ) -alert_group_update_log_report_signal.connect( - AlertGroupSlackRepresentative.on_alert_group_update_log_report, -) - alert_group_update_resolution_note_signal.connect( AlertGroupSlackRepresentative.on_alert_group_update_resolution_note, ) diff --git a/engine/apps/slack/representatives/alert_group_representative.py b/engine/apps/slack/representatives/alert_group_representative.py index 8cf1274c..88884943 100644 --- a/engine/apps/slack/representatives/alert_group_representative.py +++ b/engine/apps/slack/representatives/alert_group_representative.py @@ -90,19 +90,7 @@ def on_alert_group_action_triggered_async(log_record_id): autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else None ) def on_alert_group_update_log_report_async(alert_group_id): - from apps.alerts.models import AlertGroup - - alert_group = AlertGroup.objects.get(pk=alert_group_id) - logger.debug(f"Start on_alert_group_update_log_report for alert_group {alert_group_id}") - organization = alert_group.channel.organization - if alert_group.slack_message and organization.slack_team_identity: - logger.debug(f"Process on_alert_group_update_log_report for alert_group {alert_group_id}") - UpdateLogReportMessageStep = ScenarioStep.get_step("distribute_alerts", "UpdateLogReportMessageStep") - step = UpdateLogReportMessageStep(organization.slack_team_identity, organization) - step.process_signal(alert_group) - else: - logger.debug(f"Drop on_alert_group_update_log_report for alert_group {alert_group_id}") - logger.debug(f"Finish on_alert_group_update_log_report for alert_group {alert_group_id}") + return "Deprecated, will be removed after queue cleanup" class AlertGroupSlackRepresentative(AlertGroupAbstractRepresentative): @@ -173,32 +161,6 @@ class AlertGroupSlackRepresentative(AlertGroupAbstractRepresentative): logger.debug(f"SLACK on_alert_group_action_triggered: async {log_record_id} {force_sync}") on_alert_group_action_triggered_async.apply_async((log_record_id,)) - @classmethod - def on_alert_group_update_log_report(cls, **kwargs): - from apps.alerts.models import AlertGroup - - alert_group = kwargs["alert_group"] - - if isinstance(alert_group, AlertGroup): - alert_group_id = alert_group.pk - else: - alert_group_id = alert_group - try: - alert_group = AlertGroup.objects.get(pk=alert_group_id) - except AlertGroup.DoesNotExist as e: - logger.warning(f"SLACK update log report: alert group {alert_group_id} has been deleted") - raise e - - logger.debug( - f"Received alert_group_update_log_report signal in SLACK representative for alert_group {alert_group_id}" - ) - - if alert_group.notify_in_slack_enabled is False: - logger.debug(f"Skipping alert_group {alert_group_id} since notify_in_slack is disabled") - return - - on_alert_group_update_log_report_async.apply_async((alert_group_id,)) - @classmethod def on_alert_group_update_resolution_note(cls, **kwargs): alert_group = kwargs["alert_group"] diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index ff7c25de..d25fd193 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -4,7 +4,6 @@ import typing from datetime import datetime from django.core.cache import cache -from django.utils import timezone from apps.alerts.constants import ActionSource from apps.alerts.incident_appearance.renderers.constants import DEFAULT_BACKUP_TITLE @@ -14,25 +13,17 @@ from apps.api.permissions import RBACPermission from apps.slack.chatops_proxy_routing import make_private_metadata, make_value from apps.slack.constants import CACHE_UPDATE_INCIDENT_SLACK_MESSAGE_LIFETIME from apps.slack.errors import ( - SlackAPICantUpdateMessageError, SlackAPIChannelArchivedError, - SlackAPIChannelInactiveError, SlackAPIChannelNotFoundError, SlackAPIError, - SlackAPIInvalidAuthError, SlackAPIMessageNotFoundError, SlackAPIRatelimitError, SlackAPIRestrictedActionError, SlackAPITokenError, ) from apps.slack.scenarios import scenario_step -from apps.slack.scenarios.slack_renderer import AlertGroupLogSlackRenderer from apps.slack.slack_formatter import SlackFormatter -from apps.slack.tasks import ( - post_or_update_log_report_message_task, - send_message_to_thread_if_bot_not_in_channel, - update_incident_slack_message, -) +from apps.slack.tasks import send_message_to_thread_if_bot_not_in_channel, update_incident_slack_message from apps.slack.types import ( Block, BlockActionType, @@ -95,7 +86,6 @@ class AlertShootingStep(scenario_step.ScenarioStep): else: # check if alert group was posted to slack before posting message to thread if not alert.group.skip_escalation_in_slack: - self._send_log_report_message(alert.group, channel_id) self._send_message_to_thread_if_bot_not_in_channel(alert.group, channel_id) else: # check if alert group was posted to slack before updating its message @@ -208,11 +198,6 @@ class AlertShootingStep(scenario_step.ScenarioStep): blocks=blocks, ) - def _send_log_report_message(self, alert_group: AlertGroup, channel_id: str) -> None: - post_or_update_log_report_message_task.apply_async( - (alert_group.pk, self.slack_team_identity.pk), - ) - def _send_message_to_thread_if_bot_not_in_channel(self, alert_group: AlertGroup, channel_id: str) -> None: send_message_to_thread_if_bot_not_in_channel.apply_async( (alert_group.pk, self.slack_team_identity.pk, channel_id), @@ -895,76 +880,6 @@ class DeleteGroupStep(scenario_step.ScenarioStep): message.delete() -class UpdateLogReportMessageStep(scenario_step.ScenarioStep): - def process_signal(self, alert_group: AlertGroup) -> None: - if alert_group.skip_escalation_in_slack or alert_group.channel.is_rate_limited_in_slack: - return - - self.update_log_message(alert_group) - - def update_log_message(self, alert_group: AlertGroup) -> None: - slack_message = alert_group.slack_message - if slack_message is None: - logger.info( - f"Cannot update log message for alert_group {alert_group.pk} because SlackMessage doesn't exist" - ) - return None - - slack_log_message = alert_group.slack_log_message - - if slack_log_message is not None: - # prevent too frequent updates - if timezone.now() <= slack_log_message.last_updated + timezone.timedelta(seconds=5): - return - - attachments = AlertGroupLogSlackRenderer.render_incident_log_report_for_slack(alert_group) - logger.debug( - f"Update log message for alert_group {alert_group.pk}, slack_log_message {slack_log_message.pk}" - ) - try: - self._slack_client.chat_update( - channel=slack_message.channel_id, - text="Alert Group log", - ts=slack_log_message.slack_id, - attachments=attachments, - ) - except SlackAPIRatelimitError as e: - if not alert_group.channel.is_rate_limited_in_slack: - alert_group.channel.start_send_rate_limit_message_task(e.retry_after) - except SlackAPIMessageNotFoundError: - alert_group.slack_log_message = None - alert_group.save(update_fields=["slack_log_message"]) - except ( - SlackAPITokenError, - SlackAPIChannelNotFoundError, - SlackAPIChannelArchivedError, - SlackAPIChannelInactiveError, - SlackAPIInvalidAuthError, - SlackAPICantUpdateMessageError, - ): - pass - else: - slack_log_message.last_updated = timezone.now() - slack_log_message.save(update_fields=["last_updated"]) - logger.debug( - f"Finished update log message for alert_group {alert_group.pk}, " - f"slack_log_message {slack_log_message.pk}" - ) - # check how much time has passed since slack message was created - # to prevent eternal loop of restarting update log message task - elif timezone.now() <= slack_message.created_at + timezone.timedelta(minutes=5): - logger.debug( - f"Update log message failed for alert_group {alert_group.pk}: " - f"log message does not exist yet. Restarting post_or_update_log_report_message_task..." - ) - post_or_update_log_report_message_task.apply_async( - (alert_group.pk, self.slack_team_identity.pk, True), - countdown=3, - ) - else: - logger.debug(f"Update log message failed for alert_group {alert_group.pk}: " f"log message does not exist.") - - STEPS_ROUTING: ScenarioRoute.RoutingSteps = [ { "payload_type": PayloadType.INTERACTIVE_MESSAGE, diff --git a/engine/apps/slack/scenarios/slack_renderer.py b/engine/apps/slack/scenarios/slack_renderer.py index ee5de69c..59a63bf0 100644 --- a/engine/apps/slack/scenarios/slack_renderer.py +++ b/engine/apps/slack/scenarios/slack_renderer.py @@ -39,17 +39,3 @@ class AlertGroupLogSlackRenderer: for plan_line in escalation_policies_plan[time]: result += f"*{humanize.naturaldelta(time)}:* {plan_line}\n" return result - - @staticmethod - def render_incident_log_report_for_slack(alert_group: "AlertGroup"): - attachments = [] - past = AlertGroupLogSlackRenderer.render_alert_group_past_log_report_text(alert_group) - future = AlertGroupLogSlackRenderer.render_alert_group_future_log_report_text(alert_group) - text = past + future - if len(text) > 0: - attachments.append( - { - "text": text, - } - ) - return attachments diff --git a/engine/apps/slack/tasks.py b/engine/apps/slack/tasks.py index 71595ec7..4575d86f 100644 --- a/engine/apps/slack/tasks.py +++ b/engine/apps/slack/tasks.py @@ -20,7 +20,6 @@ from apps.slack.errors import ( SlackAPITokenError, SlackAPIUsergroupNotFoundError, ) -from apps.slack.scenarios.scenario_step import ScenarioStep from apps.slack.utils import ( get_cache_key_update_incident_slack_message, get_populate_slack_channel_task_id_key, @@ -289,27 +288,7 @@ def populate_slack_user_identities(organization_pk): autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else None ) def post_or_update_log_report_message_task(alert_group_pk, slack_team_identity_pk, update=False): - logger.debug(f"Start post_or_update_log_report_message_task for alert_group {alert_group_pk}") - from apps.alerts.models import AlertGroup - from apps.slack.models import SlackTeamIdentity - - UpdateLogReportMessageStep = ScenarioStep.get_step("distribute_alerts", "UpdateLogReportMessageStep") - - slack_team_identity = SlackTeamIdentity.objects.get(pk=slack_team_identity_pk) - alert_group = AlertGroup.objects.get(pk=alert_group_pk) - step = UpdateLogReportMessageStep(slack_team_identity, alert_group.channel.organization) - - if alert_group.skip_escalation_in_slack or alert_group.channel.is_rate_limited_in_slack: - return - - if update: # flag to prevent multiple posting log message to slack - step.update_log_message(alert_group) - else: - # don't post a new message, as it is available from the button - # this is an intermediate step, so we will only update posted messages but not post new ones - # once majority of messages are updated, we can remove this step (https://github.com/grafana/oncall/pull/4686) - pass - logger.debug(f"Finish post_or_update_log_report_message_task for alert_group {alert_group_pk}") + return "Deprecated, will be removed after queue cleanup" @shared_dedicated_queue_retry_task( diff --git a/engine/apps/slack/tests/test_scenario_steps/test_distribute_alerts.py b/engine/apps/slack/tests/test_scenario_steps/test_distribute_alerts.py index 75b32aa8..1eba2b9a 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_distribute_alerts.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_distribute_alerts.py @@ -1,12 +1,11 @@ from unittest.mock import patch import pytest -from django.utils import timezone from apps.alerts.models import AlertGroup -from apps.slack.errors import SlackAPICantUpdateMessageError, SlackAPIRestrictedActionError +from apps.slack.errors import SlackAPIRestrictedActionError from apps.slack.models import SlackMessage -from apps.slack.scenarios.distribute_alerts import AlertShootingStep, UpdateLogReportMessageStep +from apps.slack.scenarios.distribute_alerts import AlertShootingStep from apps.slack.scenarios.scenario_step import ScenarioStep from apps.slack.tests.conftest import build_slack_response @@ -65,37 +64,3 @@ def test_alert_shooting_no_channel_filter( mock_post_alert_group_to_slack.assert_called_once() assert mock_post_alert_group_to_slack.call_args[1]["channel_id"] == "DEFAULT_CHANNEL_ID" - - -@pytest.mark.django_db -def test_update_log_report_cant_update( - make_slack_team_identity, - make_organization, - make_alert_receive_channel, - make_alert_group, - make_alert, - make_slack_message, -): - slack_team_identity = make_slack_team_identity() - organization = make_organization( - slack_team_identity=slack_team_identity, general_log_channel_id="DEFAULT_CHANNEL_ID" - ) - alert_receive_channel = make_alert_receive_channel(organization) - - alert_group = make_alert_group(alert_receive_channel, channel_filter=None) - # alert = make_alert(alert_group, raw_request_data={}) - log_message = make_slack_message( - alert_group=alert_group, - channel_id="RANDOM_CHANNEL_ID", - slack_id="RANDOM_MESSAGE_ID", - last_updated=timezone.now() - timezone.timedelta(minutes=5), - ) - alert_group.slack_log_message = log_message - - step = UpdateLogReportMessageStep(slack_team_identity, organization) - with patch.object(step._slack_client, "api_call") as mock_slack_api_call: - mock_slack_api_call.side_effect = SlackAPICantUpdateMessageError( - response=build_slack_response({"error": "cant_update_message"}) - ) - # not raising error, will not retry - step.update_log_message(alert_group) diff --git a/engine/settings/celery_task_routes.py b/engine/settings/celery_task_routes.py index 03d44e10..1eccac6c 100644 --- a/engine/settings/celery_task_routes.py +++ b/engine/settings/celery_task_routes.py @@ -156,9 +156,6 @@ CELERY_TASK_ROUTES = { "apps.grafana_plugin.tasks.sync_v2.sync_organizations_v2": {"queue": "long"}, # SLACK "apps.integrations.tasks.notify_about_integration_ratelimit_in_slack": {"queue": "slack"}, - "apps.slack.helpers.alert_group_representative.on_alert_group_action_triggered_async": {"queue": "slack"}, - "apps.slack.helpers.alert_group_representative.on_alert_group_update_log_report_async": {"queue": "slack"}, - "apps.slack.helpers.alert_group_representative.on_create_alert_slack_representative_async": {"queue": "slack"}, "apps.slack.tasks.clean_slack_channel_leftovers": {"queue": "slack"}, "apps.slack.tasks.check_slack_message_exists_before_post_message_to_thread": {"queue": "slack"}, "apps.slack.tasks.clean_slack_integration_leftovers": {"queue": "slack"}, @@ -167,7 +164,6 @@ CELERY_TASK_ROUTES = { "apps.slack.tasks.populate_slack_user_identities": {"queue": "slack"}, "apps.slack.tasks.populate_slack_usergroups": {"queue": "slack"}, "apps.slack.tasks.populate_slack_usergroups_for_team": {"queue": "slack"}, - "apps.slack.tasks.post_or_update_log_report_message_task": {"queue": "slack"}, "apps.slack.tasks.post_slack_rate_limit_message": {"queue": "slack"}, "apps.slack.tasks.send_message_to_thread_if_bot_not_in_channel": {"queue": "slack"}, "apps.slack.tasks.start_update_slack_user_group_for_schedules": {"queue": "slack"}, @@ -178,6 +174,8 @@ CELERY_TASK_ROUTES = { "queue": "slack" }, "apps.slack.representatives.alert_group_representative.on_alert_group_action_triggered_async": {"queue": "slack"}, + # TODO: remove post_or_update_log_report_message_task and on_alert_group_update_log_report_async in subsequent PR + "apps.slack.tasks.post_or_update_log_report_message_task": {"queue": "slack"}, "apps.slack.representatives.alert_group_representative.on_alert_group_update_log_report_async": {"queue": "slack"}, # TELEGRAM "apps.telegram.tasks.edit_message": {"queue": "telegram"}, diff --git a/grafana-plugin/e2e-tests/alerts/directPaging.test.ts b/grafana-plugin/e2e-tests/alerts/directPaging.test.ts index 75f86e7d..ac7b5e94 100644 --- a/grafana-plugin/e2e-tests/alerts/directPaging.test.ts +++ b/grafana-plugin/e2e-tests/alerts/directPaging.test.ts @@ -1,6 +1,5 @@ -import semver from 'semver'; - import { test, expect } from '../fixtures'; +import { isGrafanaVersionLowerThan } from '../utils/constants'; import { clickButton, fillInInput } from '../utils/forms'; import { goToOnCallPage } from '../utils/navigation'; @@ -22,9 +21,7 @@ test('we can directly page a user', async ({ adminRolePage }) => { const addRespondersPopup = page.getByTestId('add-responders-popup'); - await addRespondersPopup[semver.lt(process.env.CURRENT_GRAFANA_VERSION, '10.3.0') ? 'getByText' : 'getByLabel']( - 'Users' - ).click(); + await addRespondersPopup[isGrafanaVersionLowerThan('10.3.0') ? 'getByText' : 'getByLabel']('Users').click(); await addRespondersPopup.getByText(adminRolePage.userName).first().click(); // If user is not on call, confirm invitation diff --git a/grafana-plugin/e2e-tests/globalSetup.ts b/grafana-plugin/e2e-tests/globalSetup.ts index e7cfa34a..b7613a3a 100644 --- a/grafana-plugin/e2e-tests/globalSetup.ts +++ b/grafana-plugin/e2e-tests/globalSetup.ts @@ -6,7 +6,6 @@ import { type APIRequestContext, Page, } from '@playwright/test'; -import semver from 'semver'; import { VIEWER_USER_STORAGE_STATE, EDITOR_USER_STORAGE_STATE, ADMIN_USER_STORAGE_STATE } from '../playwright.config'; @@ -21,6 +20,7 @@ import { IS_CLOUD, IS_OPEN_SOURCE, OrgRole, + isGrafanaVersionLowerThan, } from './utils/constants'; import { goToOnCallPage } from './utils/navigation'; @@ -62,7 +62,7 @@ const idempotentlyInitializePlugin = async (page: Page) => { if (await openPluginConfigurationButton.isVisible()) { await openPluginConfigurationButton.click(); // Before 10.3 Admin user needs to create service account manually - if (semver.lt(process.env.CURRENT_GRAFANA_VERSION, '10.3.0')) { + if (isGrafanaVersionLowerThan('10.3.0')) { await page.getByTestId('recreate-service-account').click(); } await page.getByTestId('connect-plugin').click(); diff --git a/grafana-plugin/e2e-tests/insights/insights.test.ts b/grafana-plugin/e2e-tests/insights/insights.test.ts index 411a8cd4..2f03d717 100644 --- a/grafana-plugin/e2e-tests/insights/insights.test.ts +++ b/grafana-plugin/e2e-tests/insights/insights.test.ts @@ -1,7 +1,6 @@ -import semver from 'semver'; - import { test, expect } from '../fixtures'; import { resolveFiringAlert } from '../utils/alertGroup'; +import { isGrafanaVersionLowerThan } from '../utils/constants'; import { createEscalationChain, EscalationStep } from '../utils/escalationChain'; import { clickButton, generateRandomValue } from '../utils/forms'; import { createIntegrationAndSendDemoAlert } from '../utils/integrations'; @@ -15,10 +14,7 @@ import { createOnCallSchedule } from '../utils/schedule'; * and use the currentGrafanaVersion fixture once this bugged is patched in playwright * https://github.com/microsoft/playwright/issues/29608 */ -test.skip( - () => semver.lt(process.env.CURRENT_GRAFANA_VERSION, '10.0.0'), - 'Insights is only available in Grafana 10.0.0 and above' -); +test.skip(() => isGrafanaVersionLowerThan('10.0.0'), 'Insights is only available in Grafana 10.0.0 and above'); /** * skipping as these tests are currently flaky diff --git a/grafana-plugin/e2e-tests/labels/createNewLabelKeysAndValues.test.ts b/grafana-plugin/e2e-tests/labels/createNewLabelKeysAndValues.test.ts index 96226620..5787e1db 100644 --- a/grafana-plugin/e2e-tests/labels/createNewLabelKeysAndValues.test.ts +++ b/grafana-plugin/e2e-tests/labels/createNewLabelKeysAndValues.test.ts @@ -1,8 +1,14 @@ import { test, expect } from '../fixtures'; +import { isGrafanaVersionGreaterThan } from '../utils/constants'; import { clickButton, generateRandomValidLabel, openDropdown } from '../utils/forms'; import { openCreateIntegrationModal } from '../utils/integrations'; import { goToOnCallPage } from '../utils/navigation'; +test.skip( + () => isGrafanaVersionGreaterThan('10.3.0'), + 'Above 10.3 labels need enterprise version to validate permissions' +); + test('New label keys and labels can be created @expensive', async ({ adminRolePage }) => { const { page } = adminRolePage; await goToOnCallPage(page, 'integrations'); diff --git a/grafana-plugin/e2e-tests/pluginInitialization/initialization.test.ts b/grafana-plugin/e2e-tests/pluginInitialization/initialization.test.ts index 005d03d2..774c1840 100644 --- a/grafana-plugin/e2e-tests/pluginInitialization/initialization.test.ts +++ b/grafana-plugin/e2e-tests/pluginInitialization/initialization.test.ts @@ -1,9 +1,7 @@ -import semver from 'semver'; - import { waitInMs } from 'utils/async'; import { test, expect, Page } from '../fixtures'; -import { OrgRole } from '../utils/constants'; +import { OrgRole, isGrafanaVersionLowerThan } from '../utils/constants'; import { goToGrafanaPage, goToOnCallPage } from '../utils/navigation'; import { createGrafanaUser, loginAndWaitTillGrafanaIsLoaded } from '../utils/users'; @@ -48,10 +46,7 @@ test.describe('Plugin initialization', () => { }) => { test.slow(); - test.skip( - semver.lt(process.env.CURRENT_GRAFANA_VERSION, '10.3.0'), - 'Extension is only available in Grafana 10.3.0 and above' - ); + test.skip(isGrafanaVersionLowerThan('10.3.0'), 'Extension is only available in Grafana 10.3.0 and above'); // Create new editor user const USER_NAME = `editor-${new Date().getTime()}`; diff --git a/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts b/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts index a76025b6..4e0bfa64 100644 --- a/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts +++ b/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts @@ -1,10 +1,9 @@ -import semver from 'semver'; - import { scheduleViewToDaysInOneRow } from 'models/schedule/schedule.helpers'; import { ScheduleView } from 'models/schedule/schedule.types'; import { HTML_ID } from 'utils/DOM'; import { expect, Page, test } from '../fixtures'; +import { isGrafanaVersionLowerThan } from '../utils/constants'; import { generateRandomValue } from '../utils/forms'; import { createOnCallSchedule } from '../utils/schedule'; @@ -13,7 +12,7 @@ const getNumberOfWeekdaysInFinalSchedule = async (page: Page) => const getScheduleViewRadioButtonLocator = (page: Page, view: ScheduleView) => page .getByTestId('schedule-view-picker') - [semver.lt(process.env.CURRENT_GRAFANA_VERSION, '10.2.0') ? 'getByText' : 'getByLabel'](view, { exact: true }); + [isGrafanaVersionLowerThan('10.2.0') ? 'getByText' : 'getByLabel'](view, { exact: true }); test('schedule view (week/2 weeks/month) toggler works', async ({ adminRolePage }) => { const { page, userName } = adminRolePage; diff --git a/grafana-plugin/e2e-tests/users/usersActions.test.ts b/grafana-plugin/e2e-tests/users/usersActions.test.ts index 91cf9f09..d72fd70f 100644 --- a/grafana-plugin/e2e-tests/users/usersActions.test.ts +++ b/grafana-plugin/e2e-tests/users/usersActions.test.ts @@ -1,6 +1,5 @@ -import semver from 'semver'; - import { test, expect } from '../fixtures'; +import { isGrafanaVersionLowerThan } from '../utils/constants'; import { goToOnCallPage } from '../utils/navigation'; import { verifyThatUserCanViewOtherUsers, accessProfileTabs } from '../utils/users'; @@ -26,7 +25,7 @@ test.describe('Users screen actions', () => { const tabsToCheck = ['tab-phone-verification', 'tab-slack', 'tab-telegram']; // After 10.3 it's been moved to global user profile - if (semver.lt(process.env.CURRENT_GRAFANA_VERSION, '10.3.0')) { + if (isGrafanaVersionLowerThan('10.3.0')) { tabsToCheck.unshift('tab-mobile-app'); } @@ -74,10 +73,10 @@ test.describe('Users screen actions', () => { await page.waitForTimeout(2000); await page - .locator('div') - .filter({ hasText: /^Search or filter results\.\.\.$/ }) - .nth(1) - .click(); + .locator('div') + .filter({ hasText: /^Search or filter results\.\.\.$/ }) + .nth(1) + .click(); await page.keyboard.insertText(userName); await page.keyboard.press('Enter'); await page.waitForTimeout(2000); diff --git a/grafana-plugin/e2e-tests/utils/constants.ts b/grafana-plugin/e2e-tests/utils/constants.ts index 9cbee844..4e8932b1 100644 --- a/grafana-plugin/e2e-tests/utils/constants.ts +++ b/grafana-plugin/e2e-tests/utils/constants.ts @@ -1,3 +1,5 @@ +import semver from 'semver'; + export const BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; export const MAILSLURP_API_KEY = process.env.MAILSLURP_API_KEY; @@ -19,3 +21,6 @@ export enum OrgRole { } export const MOSCOW_TIMEZONE = 'Europe/Moscow'; + +export const isGrafanaVersionGreaterThan = (version: string) => semver.gt(process.env.CURRENT_GRAFANA_VERSION, version); +export const isGrafanaVersionLowerThan = (version: string) => semver.lt(process.env.CURRENT_GRAFANA_VERSION, version); diff --git a/grafana-plugin/e2e-tests/utils/userSettings.ts b/grafana-plugin/e2e-tests/utils/userSettings.ts index a38878bc..6698899c 100644 --- a/grafana-plugin/e2e-tests/utils/userSettings.ts +++ b/grafana-plugin/e2e-tests/utils/userSettings.ts @@ -19,7 +19,7 @@ export const verifyUserPhoneNumber = async (page: Page): Promise => { await openUserSettingsModal(page); // go to the Phone Verification tab - await page.locator('a[aria-label="Tab Phone Verification"]').click(); + await page.getByTestId('tab-phone-verification').click(); // check to see if we've already verified our phone number.. no need to do it more than once if (await getForgetPhoneNumberButton(page).isVisible()) { diff --git a/grafana-plugin/package.json b/grafana-plugin/package.json index 73df3a5e..7b37bc1f 100644 --- a/grafana-plugin/package.json +++ b/grafana-plugin/package.json @@ -18,7 +18,7 @@ "test:report": "HTML_REPORT_ENABLED=true yarn test", "test:silent": "yarn test --silent", "test:e2e": "yarn playwright test --grep-invert @expensive", - "test:e2e-expensive": "yarn playwright test", + "test:e2e-expensive": "yarn playwright test --grep @expensive", "test:e2e:watch": "yarn test:e2e --ui", "test:e2e-expensive:watch": "yarn test:e2e-expensive --ui", "test:e2e:gen": "yarn playwright codegen http://localhost:3000", diff --git a/grafana-plugin/src/plugin.json b/grafana-plugin/src/plugin.json index 3d9782ae..7fd2af1f 100644 --- a/grafana-plugin/src/plugin.json +++ b/grafana-plugin/src/plugin.json @@ -674,7 +674,11 @@ { "action": "users.roles:read", "scope": "users:*" - } + }, + { "action": "grafana-labels-app.label:read" }, + { "action": "grafana-labels-app.label:write" }, + { "action": "grafana-labels-app.label:create" }, + { "action": "grafana-labels-app.label:delete" } ] }, "dependencies": {