diff --git a/CHANGELOG.md b/CHANGELOG.md index dc2ca2702f..fb8d95167d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Fixed + +- Remove checks delaying plugin load and cause "Initializing plugin..." ([2624](https://github.com/grafana/oncall/pull/2624)) + ## v1.3.17 (2023-07-25) ### Added diff --git a/engine/apps/grafana_plugin/tasks/sync.py b/engine/apps/grafana_plugin/tasks/sync.py index 965606038d..04ee72d2ba 100644 --- a/engine/apps/grafana_plugin/tasks/sync.py +++ b/engine/apps/grafana_plugin/tasks/sync.py @@ -46,11 +46,20 @@ def start_sync_organizations(): @shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=3) def sync_organization_async(organization_pk): + """ + This task is called periodically to sync an organization with Grafana. + It runs syncronization without force_sync flag. + """ run_organization_sync(organization_pk, False) @shared_dedicated_queue_retry_task(autoretry_for=(Exception,), max_retries=1) def plugin_sync_organization_async(organization_pk): + """ + This task is called each time when the plugin is loaded. + It runs syncronization with force_sync flag. + Which means it will sync even if the organization was synced recently. + """ run_organization_sync(organization_pk, True) diff --git a/engine/apps/grafana_plugin/views/status.py b/engine/apps/grafana_plugin/views/status.py index 8dd195bf89..70a4b6adcf 100644 --- a/engine/apps/grafana_plugin/views/status.py +++ b/engine/apps/grafana_plugin/views/status.py @@ -1,10 +1,14 @@ from django.conf import settings +from django.http import JsonResponse from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView +from apps.auth_token.auth import PluginAuthentication +from apps.base.models import DynamicSetting from apps.grafana_plugin.helpers import GrafanaAPIClient from apps.grafana_plugin.permissions import PluginTokenVerified +from apps.grafana_plugin.tasks.sync import plugin_sync_organization_async from apps.user_management.models import Organization from common.api_helpers.mixins import GrafanaHeadersMixin @@ -12,7 +16,64 @@ class StatusView(GrafanaHeadersMixin, APIView): permission_classes = (PluginTokenVerified,) + def post(self, request: Request) -> Response: + """ + Called asyncronounsly on each start of the plugin + Checks if plugin is correctly installed and async runs a task + to sync users, teams and org + """ + # Check if the plugin is currently undergoing maintenance, and return response without querying db + if settings.CURRENTLY_UNDERGOING_MAINTENANCE_MESSAGE: + return JsonResponse( + { + "currently_undergoing_maintenance_message": settings.CURRENTLY_UNDERGOING_MAINTENANCE_MESSAGE, + } + ) + + stack_id = self.instance_context["stack_id"] + org_id = self.instance_context["org_id"] + + is_installed = False + token_ok = False + allow_signup = True + + # Check if organization is in OnCall database + if organization := Organization.objects.get(stack_id=stack_id, org_id=org_id): + is_installed = True + token_ok = organization.api_token_status == Organization.API_TOKEN_STATUS_OK + else: + allow_signup = DynamicSetting.objects.get_or_create( + name="allow_plugin_organization_signup", defaults={"boolean_value": True} + )[0].boolean_value + + # Check if current user is in OnCall database + user_is_present_in_org = PluginAuthentication.is_user_from_request_present_in_organization( + request, organization + ) + # If user is not present in OnCall database, set token_ok to False, which will trigger reinstall + if not user_is_present_in_org: + token_ok = False + organization.api_token_status = Organization.API_TOKEN_STATUS_PENDING + organization.save(update_fields=["api_token_status"]) + + # Start task to refresh organization data in OnCall database with Grafana + plugin_sync_organization_async.apply_async((organization.pk,)) + + return Response( + data={ + "is_installed": is_installed, + "token_ok": token_ok, + "allow_signup": allow_signup, + "is_user_anonymous": self.grafana_context["IsAnonymous"], + "license": settings.LICENSE, + "version": settings.VERSION, + "recaptcha_site_key": settings.RECAPTCHA_V3_SITE_KEY, + "currently_undergoing_maintenance_message": settings.CURRENTLY_UNDERGOING_MAINTENANCE_MESSAGE, + } + ) + def get(self, _request: Request) -> Response: + """Deprecated. May be used for the plugins with versions < 1.3.17""" stack_id = self.instance_context["stack_id"] org_id = self.instance_context["org_id"] is_installed = False diff --git a/engine/apps/grafana_plugin/views/sync.py b/engine/apps/grafana_plugin/views/sync.py index c464b7fc99..6c17f352cd 100644 --- a/engine/apps/grafana_plugin/views/sync.py +++ b/engine/apps/grafana_plugin/views/sync.py @@ -19,11 +19,14 @@ class PluginSyncView(GrafanaHeadersMixin, APIView): permission_classes = (PluginTokenVerified,) def post(self, request: Request) -> Response: + """Deprecated. May be used for the plugins with versions < 1.3.17""" stack_id = self.instance_context["stack_id"] org_id = self.instance_context["org_id"] is_installed = False allow_signup = True + try: + # Check if organization is in OnCall database organization = Organization.objects.get(stack_id=stack_id, org_id=org_id) if organization.api_token_status == Organization.API_TOKEN_STATUS_OK: is_installed = True @@ -56,6 +59,7 @@ def post(self, request: Request) -> Response: ) def get(self, _request: Request) -> Response: + """Deprecated. May be used for the plugins with versions < 1.3.17""" stack_id = self.instance_context["stack_id"] org_id = self.instance_context["org_id"] token_ok = False diff --git a/grafana-plugin/integration-tests/integrations/heartbeat.test.ts b/grafana-plugin/integration-tests/integrations/heartbeat.test.ts index ab8086a360..873e813172 100644 --- a/grafana-plugin/integration-tests/integrations/heartbeat.test.ts +++ b/grafana-plugin/integration-tests/integrations/heartbeat.test.ts @@ -49,25 +49,26 @@ test.describe("updating an integration's heartbeat interval works", async () => expect(heartbeatIntervalValue).toEqual(value); }); - test('"send heartbeat', async ({ adminRolePage: { page } }) => { - const integrationName = generateRandomValue(); - await createIntegration(page, integrationName); + // TODO: Uncomment once https://github.com/grafana/oncall/pull/2648 ready + // test('"send heartbeat', async ({ adminRolePage: { page } }) => { + // const integrationName = generateRandomValue(); + // await createIntegration(page, integrationName); - await _openHeartbeatSettingsForm(page); + // await _openHeartbeatSettingsForm(page); - const heartbeatSettingsForm = page.getByTestId('heartbeat-settings-form'); + // const heartbeatSettingsForm = page.getByTestId('heartbeat-settings-form'); - const endpoint = await heartbeatSettingsForm - .getByTestId('input-wrapper') - .locator('input[class*="input-input"]') - .inputValue(); + // const endpoint = await heartbeatSettingsForm + // .getByTestId('input-wrapper') + // .locator('input[class*="input-input"]') + // .inputValue(); - await page.goto(endpoint); + // await page.goto(endpoint); - await page.goBack(); + // await page.goBack(); - const heartbeatBadge = await page.getByTestId('heartbeat-badge'); + // const heartbeatBadge = await page.getByTestId('heartbeat-badge'); - await expect(heartbeatBadge).toHaveClass(/--success/); - }); + // await expect(heartbeatBadge).toHaveClass(/--success/); + // }); }); diff --git a/grafana-plugin/package.json b/grafana-plugin/package.json index 4e54706262..d6c67721dc 100644 --- a/grafana-plugin/package.json +++ b/grafana-plugin/package.json @@ -1,6 +1,6 @@ { "name": "grafana-oncall-app", - "version": "1.0.0", + "version": "dev-oss", "description": "Grafana OnCall Plugin", "scripts": { "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx --max-warnings=0 ./src", diff --git a/grafana-plugin/src/containers/IntegrationForm/IntegrationForm.tsx b/grafana-plugin/src/containers/IntegrationForm/IntegrationForm.tsx index 543b4c7997..e783fe3a63 100644 --- a/grafana-plugin/src/containers/IntegrationForm/IntegrationForm.tsx +++ b/grafana-plugin/src/containers/IntegrationForm/IntegrationForm.tsx @@ -51,7 +51,7 @@ const IntegrationForm = observer((props: IntegrationFormProps) => { const data = id === 'new' - ? { integration: selectedOption?.value, team: user.current_team } + ? { integration: selectedOption?.value, team: user?.current_team } : prepareForEdit(alertReceiveChannelStore.items[id]); const handleSubmit = useCallback( diff --git a/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.test.tsx b/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.test.tsx index fac91c5c4f..e367aff1a8 100644 --- a/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.test.tsx +++ b/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.test.tsx @@ -31,7 +31,7 @@ enum License { const SELF_HOSTED_INSTALL_PLUGIN_ERROR_MESSAGE = 'ohhh nooo an error msg from self hosted install plugin'; const CHECK_IF_PLUGIN_IS_CONNECTED_ERROR_MESSAGE = 'ohhh nooo a plugin connection error'; -const SNYC_DATA_WITH_ONCALL_ERROR_MESSAGE = 'ohhh noooo a sync issue'; +const UPDATE_PLUGIN_STATUS_ERROR_MESSAGE = 'ohhh noooo a sync issue'; const PLUGIN_CONFIGURATION_FORM_DATA_ID = 'plugin-configuration-form'; const STATUS_MESSAGE_BLOCK_DATA_ID = 'status-message-block'; @@ -71,11 +71,16 @@ afterEach(() => { console.error = originalError; }); -const mockCheckTokenAndSyncDataWithOncall = (license: License = License.OSS) => { - PluginState.checkTokenAndSyncDataWithOncall = jest.fn().mockResolvedValueOnce({ +const mockCheckTokenAndIfPluginIsConnected = (license: License = License.OSS) => { + PluginState.checkTokenAndIfPluginIsConnected = jest.fn().mockResolvedValueOnce({ token_ok: true, license, version: 'v1.2.3', + allow_signup: true, + currently_undergoing_maintenance_message: null, + recaptcha_site_key: 'abc', + is_installed: true, + is_user_anonymous: false, }); }; @@ -99,9 +104,14 @@ describe('reloadPageWithPluginConfiguredQueryParams', () => { // mocks const version = 'v1.2.3'; const license = 'OpenSource'; + const recaptcha_site_key = 'abc'; + const currently_undergoing_maintenance_message = 'false'; // test - reloadPageWithPluginConfiguredQueryParams({ version, license }, pluginEnabled); + reloadPageWithPluginConfiguredQueryParams( + { version, license, recaptcha_site_key, currently_undergoing_maintenance_message }, + pluginEnabled + ); // assertions expect(window.location.href).toEqual( @@ -129,8 +139,8 @@ describe('PluginConfigPage', () => { test('It removes the plugin configured query params if the plugin is enabled', async () => { // mocks const metaJsonDataOnCallApiUrl = 'onCallApiUrlFromMetaJsonData'; - PluginState.checkIfPluginIsConnected = jest.fn(); - mockCheckTokenAndSyncDataWithOncall(); + PluginState.updatePluginStatus = jest.fn(); + mockCheckTokenAndIfPluginIsConnected(); // test setup render(); @@ -139,11 +149,11 @@ describe('PluginConfigPage', () => { // assertions expect(window.history.pushState).toBeCalledWith({ path: MOCK_URL }, '', MOCK_URL); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); - expect(PluginState.checkTokenAndSyncDataWithOncall).toHaveBeenCalledTimes(1); - expect(PluginState.checkTokenAndSyncDataWithOncall).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); + expect(PluginState.checkTokenAndIfPluginIsConnected).toHaveBeenCalledTimes(1); + expect(PluginState.checkTokenAndIfPluginIsConnected).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); }); test("It doesn't make any network calls if the plugin configured query params are provided", async () => { @@ -156,33 +166,33 @@ describe('PluginConfigPage', () => { search: `?pluginConfigured=true&pluginConfiguredLicense=${license}&pluginConfiguredVersion=${version}`, } as ReturnType); - PluginState.checkIfPluginIsConnected = jest.fn(); - mockCheckTokenAndSyncDataWithOncall(); + PluginState.updatePluginStatus = jest.fn(); + mockCheckTokenAndIfPluginIsConnected(); // test setup const component = render(); await screen.findByTestId(STATUS_MESSAGE_BLOCK_DATA_ID); // assertions - expect(PluginState.checkIfPluginIsConnected).not.toHaveBeenCalled(); - expect(PluginState.checkTokenAndSyncDataWithOncall).not.toHaveBeenCalled(); + expect(PluginState.updatePluginStatus).not.toHaveBeenCalled(); + expect(PluginState.checkTokenAndIfPluginIsConnected).not.toHaveBeenCalled(); expect(component.container).toMatchSnapshot(); }); - test("If onCallApiUrl is not set in the plugin's meta jsonData, or in process.env, checkIfPluginIsConnected is not called, and the configuration form is shown", async () => { + test("If onCallApiUrl is not set in the plugin's meta jsonData, or in process.env, updatePluginStatus is not called, and the configuration form is shown", async () => { // mocks delete process.env.ONCALL_API_URL; - PluginState.checkIfPluginIsConnected = jest.fn(); - PluginState.checkTokenAndSyncDataWithOncall = jest.fn(); + PluginState.updatePluginStatus = jest.fn(); + PluginState.checkTokenAndIfPluginIsConnected = jest.fn(); // test setup const component = render(); await screen.findByTestId(PLUGIN_CONFIGURATION_FORM_DATA_ID); // assertions - expect(PluginState.checkIfPluginIsConnected).not.toHaveBeenCalled(); - expect(PluginState.checkTokenAndSyncDataWithOncall).not.toHaveBeenCalled(); + expect(PluginState.updatePluginStatus).not.toHaveBeenCalled(); + expect(PluginState.checkTokenAndIfPluginIsConnected).not.toHaveBeenCalled(); expect(component.container).toMatchSnapshot(); }); @@ -192,7 +202,7 @@ describe('PluginConfigPage', () => { process.env.ONCALL_API_URL = processEnvOnCallApiUrl; PluginState.selfHostedInstallPlugin = jest.fn(); - mockCheckTokenAndSyncDataWithOncall(); + mockCheckTokenAndIfPluginIsConnected(); // test setup render(); @@ -219,47 +229,47 @@ describe('PluginConfigPage', () => { expect(component.container).toMatchSnapshot(); }); - test('If onCallApiUrl is set, and checkIfPluginIsConnected returns an error, it sets an error message', async () => { + test('If onCallApiUrl is set, and updatePluginStatus returns an error, it sets an error message', async () => { // mocks const processEnvOnCallApiUrl = 'onCallApiUrlFromProcessEnv'; const metaJsonDataOnCallApiUrl = 'onCallApiUrlFromMetaJsonData'; process.env.ONCALL_API_URL = processEnvOnCallApiUrl; - PluginState.checkIfPluginIsConnected = jest.fn().mockResolvedValueOnce(CHECK_IF_PLUGIN_IS_CONNECTED_ERROR_MESSAGE); + PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce(CHECK_IF_PLUGIN_IS_CONNECTED_ERROR_MESSAGE); // test setup const component = render(); await screen.findByTestId(STATUS_MESSAGE_BLOCK_DATA_ID); // assertions - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); expect(component.container).toMatchSnapshot(); }); - test('OnCallApiUrl is set, and checkApiTokenSyncData returns an error', async () => { + test('OnCallApiUrl is set, and checkTokenAndIfPluginIsConnected returns an error', async () => { // mocks const processEnvOnCallApiUrl = 'onCallApiUrlFromProcessEnv'; const metaJsonDataOnCallApiUrl = 'onCallApiUrlFromMetaJsonData'; process.env.ONCALL_API_URL = processEnvOnCallApiUrl; - PluginState.checkIfPluginIsConnected = jest.fn().mockResolvedValueOnce(null); - PluginState.checkTokenAndSyncDataWithOncall = jest.fn().mockResolvedValueOnce(SNYC_DATA_WITH_ONCALL_ERROR_MESSAGE); + PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce(null); + PluginState.checkTokenAndIfPluginIsConnected = jest.fn().mockResolvedValueOnce(UPDATE_PLUGIN_STATUS_ERROR_MESSAGE); // test setup const component = render(); await screen.findByTestId(STATUS_MESSAGE_BLOCK_DATA_ID); // assertions - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); expect(component.container).toMatchSnapshot(); }); test.each([License.CLOUD, License.OSS])( - 'OnCallApiUrl is set, and checkApiTokenSyncData does not return an error. It displays properly the plugin connected items based on the license - License: %s', + 'OnCallApiUrl is set, and checkTokenAndIfPluginIsConnected does not return an error. It displays properly the plugin connected items based on the license - License: %s', async (license) => { // mocks const processEnvOnCallApiUrl = 'onCallApiUrlFromProcessEnv'; @@ -267,16 +277,16 @@ describe('PluginConfigPage', () => { process.env.ONCALL_API_URL = processEnvOnCallApiUrl; - PluginState.checkIfPluginIsConnected = jest.fn().mockResolvedValueOnce(null); - mockCheckTokenAndSyncDataWithOncall(license); + PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce(null); + mockCheckTokenAndIfPluginIsConnected(license); // test setup const component = render(); await screen.findByTestId(STATUS_MESSAGE_BLOCK_DATA_ID); // assertions - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); expect(component.container).toMatchSnapshot(); } ); @@ -288,8 +298,9 @@ describe('PluginConfigPage', () => { process.env.ONCALL_API_URL = processEnvOnCallApiUrl; window.location.reload = jest.fn(); - PluginState.checkIfPluginIsConnected = jest.fn().mockResolvedValueOnce(null); - mockCheckTokenAndSyncDataWithOncall(License.OSS); + + PluginState.updatePluginStatus = jest.fn().mockResolvedValue(null); + mockCheckTokenAndIfPluginIsConnected(License.OSS); if (successful) { PluginState.resetPlugin = jest.fn().mockResolvedValueOnce(null); @@ -307,11 +318,11 @@ describe('PluginConfigPage', () => { await userEvent.click(screen.getByText('Remove')); // assertions - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); - expect(PluginState.checkTokenAndSyncDataWithOncall).toHaveBeenCalledTimes(1); - expect(PluginState.checkTokenAndSyncDataWithOncall).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); + expect(PluginState.checkTokenAndIfPluginIsConnected).toHaveBeenCalledTimes(1); + expect(PluginState.checkTokenAndIfPluginIsConnected).toHaveBeenCalledWith(metaJsonDataOnCallApiUrl); expect(PluginState.resetPlugin).toHaveBeenCalledTimes(1); expect(PluginState.resetPlugin).toHaveBeenCalledWith(); diff --git a/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.tsx b/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.tsx index 2919ad3261..cb0b62cacf 100644 --- a/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.tsx +++ b/grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.tsx @@ -1,6 +1,6 @@ import React, { FC, useCallback, useEffect, useState } from 'react'; -import { Button, HorizontalGroup, Label, Legend, LoadingPlaceholder } from '@grafana/ui'; +import { Button, HorizontalGroup, Label, Legend, LinkButton, LoadingPlaceholder, VerticalGroup } from '@grafana/ui'; import { useLocation } from 'react-router-dom'; import { OnCallPluginConfigPageProps } from 'types'; @@ -61,12 +61,17 @@ const PluginConfigPage: FC = ({ const [pluginConnectionCheckError, setPluginConnectionCheckError] = useState(null); const [pluginIsConnected, setPluginIsConnected] = useState( pluginConfiguredRedirect - ? { version: pluginConfiguredVersionQueryParam, license: pluginConfiguredLicenseQueryParam } + ? { + version: pluginConfiguredVersionQueryParam, + license: pluginConfiguredLicenseQueryParam, + recaptcha_site_key: 'abc', + currently_undergoing_maintenance_message: 'false', + } : null ); - const [syncingPlugin, setSyncingPlugin] = useState(false); - const [syncError, setSyncError] = useState(null); + const [updatingPluginStatus, setUpdatingPluginStatus] = useState(false); + const [updatingPluginStatusError, setUpdatingPluginStatusError] = useState(null); const [resettingPlugin, setResettingPlugin] = useState(false); const [pluginResetError, setPluginResetError] = useState(null); @@ -78,27 +83,27 @@ const PluginConfigPage: FC = ({ const resetQueryParams = useCallback(() => removePluginConfiguredQueryParams(pluginIsEnabled), [pluginIsEnabled]); - const triggerDataSyncWithOnCall = useCallback(async () => { + const triggerUpdatePluginStatus = useCallback(async () => { resetMessages(); - setSyncingPlugin(true); + setUpdatingPluginStatus(true); - const syncDataResponse = await PluginState.checkTokenAndSyncDataWithOncall(onCallApiUrl); + const pluginConnectionStatus = await PluginState.checkTokenAndIfPluginIsConnected(onCallApiUrl); - if (typeof syncDataResponse === 'string') { - setSyncError(syncDataResponse); + if (typeof pluginConnectionStatus === 'string') { + setUpdatingPluginStatusError(pluginConnectionStatus); } else { - const { token_ok, ...versionLicenseInfo } = syncDataResponse; + const { token_ok, ...versionLicenseInfo } = pluginConnectionStatus; setPluginIsConnected(versionLicenseInfo); reloadPageWithPluginConfiguredQueryParams(versionLicenseInfo, pluginIsEnabled); } - setSyncingPlugin(false); + setUpdatingPluginStatus(false); }, [onCallApiUrl, pluginIsEnabled]); useEffect(resetQueryParams, [resetQueryParams]); useEffect(() => { - const configurePluginAndSyncData = async () => { + const configurePluginAndUpdatePluginStatus = async () => { /** * If the plugin has never been configured, onCallApiUrl will be undefined in the plugin's jsonData * In that case, check to see if ONCALL_API_URL has been supplied as an env var. @@ -123,12 +128,12 @@ const PluginConfigPage: FC = ({ * there's no reason to check if the plugin is connected, we know it can't be */ if (onCallApiUrl) { - const pluginConnectionResponse = await PluginState.checkIfPluginIsConnected(onCallApiUrl); + const pluginConnectionResponse = await PluginState.updatePluginStatus(onCallApiUrl); if (typeof pluginConnectionResponse === 'string') { setPluginConnectionCheckError(pluginConnectionResponse); } else { - triggerDataSyncWithOnCall(); + triggerUpdatePluginStatus(); } } setCheckingIfPluginIsConnected(false); @@ -139,7 +144,7 @@ const PluginConfigPage: FC = ({ * plugin setup */ if (!pluginConfiguredRedirect) { - configurePluginAndSyncData(); + configurePluginAndUpdatePluginStatus(); } }, [pluginMetaOnCallApiUrl, processEnvOnCallApiUrl, onCallApiUrl, pluginConfiguredRedirect]); @@ -147,7 +152,7 @@ const PluginConfigPage: FC = ({ setPluginResetError(null); setPluginConnectionCheckError(null); setPluginIsConnected(null); - setSyncError(null); + setUpdatingPluginStatusError(null); }, []); const resetState = useCallback(() => { @@ -177,7 +182,7 @@ const PluginConfigPage: FC = ({ const ReconfigurePluginButtons = () => ( - {licenseType === GRAFANA_LICENSE_OSS ? : null} @@ -188,7 +193,7 @@ const PluginConfigPage: FC = ({ if (checkingIfPluginIsConnected) { content = ; - } else if (syncingPlugin) { + } else if (updatingPluginStatus) { content = ; } else if (pluginConnectionCheckError || pluginResetError) { content = ( @@ -197,24 +202,35 @@ const PluginConfigPage: FC = ({ ); - } else if (syncError) { + } else if (updatingPluginStatusError) { content = ( <> - + ); } else if (!pluginIsConnected) { content = ( - + ); } else { // plugin is fully connected and synced + const pluginLink = ( + + Open Grafana OnCall + + ); content = licenseType === GRAFANA_LICENSE_OSS ? ( - + + {pluginLink} + + ) : ( - + + + {pluginLink} + ); } @@ -223,10 +239,6 @@ const PluginConfigPage: FC = ({ Configure Grafana OnCall {pluginIsConnected ? ( <> -

- Plugin is connected! Continue to Grafana OnCall by clicking OnCall under Alerts & IRM in the navigation over - there 👈 -

diff --git a/grafana-plugin/src/containers/PluginConfigPage/__snapshots__/PluginConfigPage.test.tsx.snap b/grafana-plugin/src/containers/PluginConfigPage/__snapshots__/PluginConfigPage.test.tsx.snap index 32d6cc104e..c15126c189 100644 --- a/grafana-plugin/src/containers/PluginConfigPage/__snapshots__/PluginConfigPage.test.tsx.snap +++ b/grafana-plugin/src/containers/PluginConfigPage/__snapshots__/PluginConfigPage.test.tsx.snap @@ -55,7 +55,7 @@ exports[`PluginConfigPage If onCallApiUrl is not set in the plugin's meta jsonDa `; -exports[`PluginConfigPage If onCallApiUrl is not set in the plugin's meta jsonData, or in process.env, checkIfPluginIsConnected is not called, and the configuration form is shown 1`] = ` +exports[`PluginConfigPage If onCallApiUrl is not set in the plugin's meta jsonData, or in process.env, updatePluginStatus is not called, and the configuration form is shown 1`] = `
`; -exports[`PluginConfigPage If onCallApiUrl is set, and checkIfPluginIsConnected returns an error, it sets an error message 1`] = ` +exports[`PluginConfigPage If onCallApiUrl is set, and updatePluginStatus returns an error, it sets an error message 1`] = `
Configure Grafana OnCall -

- Plugin is connected! Continue to Grafana OnCall by clicking OnCall under Alerts & IRM in the navigation over there 👈 -

@@ -230,29 +227,50 @@ exports[`PluginConfigPage It doesn't make any network calls if the plugin config
       Connected to OnCall (v1.2.3, OpenSource)
     
   
- + + + Open Grafana OnCall + + +
+
+ +
+
`; -exports[`PluginConfigPage OnCallApiUrl is set, and checkApiTokenSyncData does not return an error. It displays properly the plugin connected items based on the license - License: OpenSource 1`] = ` +exports[`PluginConfigPage OnCallApiUrl is set, and checkTokenAndIfPluginIsConnected does not return an error. It displays properly the plugin connected items based on the license - License: OpenSource 1`] = `
Configure Grafana OnCall -

- Plugin is connected! Continue to Grafana OnCall by clicking OnCall under Alerts & IRM in the navigation over there 👈 -

@@ -262,29 +280,50 @@ exports[`PluginConfigPage OnCallApiUrl is set, and checkApiTokenSyncData does no
       Connected to OnCall (v1.2.3, OpenSource)
     
   
- + + + Open Grafana OnCall + + +
+
+ +
+ `; -exports[`PluginConfigPage OnCallApiUrl is set, and checkApiTokenSyncData does not return an error. It displays properly the plugin connected items based on the license - License: some-other-license 1`] = ` +exports[`PluginConfigPage OnCallApiUrl is set, and checkTokenAndIfPluginIsConnected does not return an error. It displays properly the plugin connected items based on the license - License: some-other-license 1`] = `
Configure Grafana OnCall -

- Plugin is connected! Continue to Grafana OnCall by clicking OnCall under Alerts & IRM in the navigation over there 👈 -

@@ -295,20 +334,44 @@ exports[`PluginConfigPage OnCallApiUrl is set, and checkApiTokenSyncData does no
     
   
-
`; -exports[`PluginConfigPage OnCallApiUrl is set, and checkApiTokenSyncData returns an error 1`] = ` +exports[`PluginConfigPage OnCallApiUrl is set, and checkTokenAndIfPluginIsConnected returns an error 1`] = `
{ }; export const Root = observer((props: AppRootProps) => { - const [didFinishLoading, setDidFinishLoading] = useState(false); - const store = useStore(); useEffect(() => { @@ -106,13 +104,8 @@ export const Root = observer((props: AppRootProps) => { const updateBasicData = async () => { await store.updateBasicData(); await store.alertGroupStore.fetchIRMPlan(); - setDidFinishLoading(true); }; - if (!didFinishLoading) { - return null; - } - const location = useLocation(); const page = getMatchedPage(location.pathname); diff --git a/grafana-plugin/src/plugin/PluginSetup/PluginSetup.test.tsx b/grafana-plugin/src/plugin/PluginSetup/PluginSetup.test.tsx index 7e75a5b9e5..517ffe313e 100644 --- a/grafana-plugin/src/plugin/PluginSetup/PluginSetup.test.tsx +++ b/grafana-plugin/src/plugin/PluginSetup/PluginSetup.test.tsx @@ -70,20 +70,17 @@ describe('PluginSetup', () => { test('app is loading', async () => { const rootBaseStore = new RootBaseStore(); - rootBaseStore.appLoading = true; await createComponentAndMakeAssertions(rootBaseStore); }); test('there is an error message', async () => { const rootBaseStore = new RootBaseStore(); - rootBaseStore.appLoading = false; rootBaseStore.initializationError = 'ohhhh noo'; await createComponentAndMakeAssertions(rootBaseStore); }); test('there is an error message - retry setup', async () => { const rootBaseStore = new RootBaseStore(); - rootBaseStore.appLoading = false; rootBaseStore.initializationError = 'ohhhh noo'; const mockedSetupPlugin = await createComponentAndMakeAssertions(rootBaseStore); @@ -95,7 +92,6 @@ describe('PluginSetup', () => { test('currently undergoing maintenance', async () => { const rootBaseStore = new RootBaseStore(); - rootBaseStore.appLoading = false; rootBaseStore.currentlyUndergoingMaintenance = true; rootBaseStore.initializationError = 'there is some sort of maintenance'; await createComponentAndMakeAssertions(rootBaseStore); @@ -103,7 +99,6 @@ describe('PluginSetup', () => { test('app successfully initialized', async () => { const rootBaseStore = new RootBaseStore(); - rootBaseStore.appLoading = false; rootBaseStore.initializationError = null; await createComponentAndMakeAssertions(rootBaseStore); }); @@ -112,7 +107,6 @@ describe('PluginSetup', () => { runtime.config.featureToggles.topnav = isTopNavBar; const rootBaseStore = new RootBaseStore(); - rootBaseStore.appLoading = true; await createComponentAndMakeAssertions(rootBaseStore); }); }); diff --git a/grafana-plugin/src/plugin/PluginSetup/__snapshots__/PluginSetup.test.tsx.snap b/grafana-plugin/src/plugin/PluginSetup/__snapshots__/PluginSetup.test.tsx.snap index 04eb5aa280..02800aa02f 100644 --- a/grafana-plugin/src/plugin/PluginSetup/__snapshots__/PluginSetup.test.tsx.snap +++ b/grafana-plugin/src/plugin/PluginSetup/__snapshots__/PluginSetup.test.tsx.snap @@ -2,54 +2,24 @@ exports[`PluginSetup app initialized with topnavbar = false 1`] = `
-
- Grafana OnCall Logo -
- Initializing plugin... -
+
+ hello
`; exports[`PluginSetup app initialized with topnavbar = true 1`] = `
-
- Grafana OnCall Logo -
- Initializing plugin... -
+
+ hello
`; exports[`PluginSetup app is loading 1`] = `
-
- Grafana OnCall Logo -
- Initializing plugin... -
+
+ hello
`; diff --git a/grafana-plugin/src/plugin/PluginSetup/index.tsx b/grafana-plugin/src/plugin/PluginSetup/index.tsx index 18ae2ffa03..da485b4049 100644 --- a/grafana-plugin/src/plugin/PluginSetup/index.tsx +++ b/grafana-plugin/src/plugin/PluginSetup/index.tsx @@ -35,15 +35,10 @@ const PluginSetupWrapper: FC = ({ text, children }) => const PluginSetup: FC = observer(({ InitializedComponent, ...props }) => { const store = useStore(); const setupPlugin = useCallback(() => store.setupPlugin(props.meta), [props.meta]); - useEffect(() => { setupPlugin(); }, [setupPlugin]); - if (store.appLoading) { - return ; - } - if (store.initializationError) { return ( @@ -62,7 +57,6 @@ const PluginSetup: FC = observer(({ InitializedComponent, ...p ); } - return ; }); diff --git a/grafana-plugin/src/state/plugin/__snapshots__/plugin.test.ts.snap b/grafana-plugin/src/state/plugin/__snapshots__/plugin.test.ts.snap index 329fd8fce6..8152d352b8 100644 --- a/grafana-plugin/src/state/plugin/__snapshots__/plugin.test.ts.snap +++ b/grafana-plugin/src/state/plugin/__snapshots__/plugin.test.ts.snap @@ -55,8 +55,3 @@ exports[`PluginState.getHumanReadableErrorFromOnCallError it handles an unknown "An unknown error occurred when trying to install the plugin. Verify OnCall API URL, http://hello.com, is correct (NOTE: OnCall API URL is currently being taken from process.env of your UI)? Refresh your page and try again, or try removing your plugin configuration and reconfiguring." `; - -exports[`PluginState.pollOnCallDataSyncStatus it returns an error message if the pollCount is greater than 10 1`] = ` -"There was an issue while synchronizing data required for the plugin. -Verify your OnCall backend setup (ie. that Celery workers are launched and properly configured)" -`; diff --git a/grafana-plugin/src/state/plugin/index.ts b/grafana-plugin/src/state/plugin/index.ts index c7e7cf8022..1e2f7deb93 100644 --- a/grafana-plugin/src/state/plugin/index.ts +++ b/grafana-plugin/src/state/plugin/index.ts @@ -2,7 +2,6 @@ import { getBackendSrv } from '@grafana/runtime'; import { OnCallAppPluginMeta, OnCallPluginMetaJSONData, OnCallPluginMetaSecureJSONData } from 'types'; import { makeRequest, isNetworkError } from 'network'; -import FaroHelper from 'utils/faro'; export type UpdateGrafanaPluginSettingsProps = { jsonData?: Partial; @@ -11,6 +10,8 @@ export type UpdateGrafanaPluginSettingsProps = { export type PluginStatusResponseBase = Pick & { version: string; + recaptcha_site_key: string; + currently_undergoing_maintenance_message: string; }; export type PluginSyncStatusResponse = PluginStatusResponseBase & { @@ -25,10 +26,6 @@ type PluginConnectedStatusResponse = PluginStatusResponseBase & { is_user_anonymous: boolean; }; -type PluginIsInMaintenanceModeResponse = { - currently_undergoing_maintenance_message: string; -}; - type CloudProvisioningConfigResponse = null; type SelfHostedProvisioningConfigResponse = Omit & { @@ -44,7 +41,6 @@ export type InstallationVerb = 'install' | 'sync'; class PluginState { static ONCALL_BASE_URL = '/plugin'; static GRAFANA_PLUGIN_SETTINGS_URL = '/api/plugins/grafana-oncall-app/settings'; - static SYNC_STATUS_POLLING_RETRY_LIMIT = 10; static grafanaBackend = getBackendSrv(); static generateOnCallApiUrlConfiguredThroughEnvVarMsg = (isConfiguredThroughEnvVar: boolean): string => @@ -208,75 +204,9 @@ class PluginState { }); }; - static getPluginSyncStatus = (): Promise => - makeRequest(`${this.ONCALL_BASE_URL}/sync`, { method: 'GET' }); - - static timeout = (pollCount: number) => new Promise((resolve) => setTimeout(resolve, 10 * 2 ** pollCount)); - - /** - * DON'T CALL THIS METHOD DIRECTLY - * This really only exists to properly test the recursive nature of pollOnCallDataSyncStatus - * Without this it is impossible (or very hacky) to mock the recursive calls - */ - static _pollOnCallDataSyncStatus = ( - onCallApiUrl: string, - onCallApiUrlIsConfiguredThroughEnvVar: boolean, - pollCount: number - ) => this.pollOnCallDataSyncStatus(onCallApiUrl, onCallApiUrlIsConfiguredThroughEnvVar, pollCount); - - /** - * Poll, for a configured amount of time, the status of the OnCall backend data sync - * Returns a PluginSyncStatusResponse if the sync was successful (ie. token_ok is true), otherwise null - */ - static pollOnCallDataSyncStatus = async ( - onCallApiUrl: string, - onCallApiUrlIsConfiguredThroughEnvVar: boolean, - pollCount = 0 - ): Promise => { - if (pollCount > this.SYNC_STATUS_POLLING_RETRY_LIMIT) { - return `There was an issue while synchronizing data required for the plugin.\nVerify your OnCall backend setup (ie. that Celery workers are launched and properly configured)`; - } - - try { - const syncResponse = await this.getPluginSyncStatus(); - if (syncResponse?.token_ok) { - return syncResponse; - } - - await this.timeout(pollCount); - return await this._pollOnCallDataSyncStatus(onCallApiUrl, onCallApiUrlIsConfiguredThroughEnvVar, pollCount + 1); - } catch (e) { - return this.getHumanReadableErrorFromOnCallError(e, onCallApiUrl, 'sync', onCallApiUrlIsConfiguredThroughEnvVar); - } - }; - - /** - * Trigger a data sync with the OnCall backend AND then poll, for a configured amount of time, the status of that sync - * If the - * Returns a PluginSyncStatusResponse if the sync was succesful, otherwise null - */ - static syncDataWithOnCall = async ( - onCallApiUrl: string, - onCallApiUrlIsConfiguredThroughEnvVar = false + static checkTokenAndIfPluginIsConnected = async ( + onCallApiUrl: string ): Promise => { - try { - const startSyncResponse = await makeRequest(`${this.ONCALL_BASE_URL}/sync`, { method: 'POST' }); - if (typeof startSyncResponse === 'string') { - // an error occurred trying to initiate the sync - return startSyncResponse; - } - - if (!FaroHelper.faro) { - FaroHelper.initializeFaro(onCallApiUrl); - } - - return await this.pollOnCallDataSyncStatus(onCallApiUrl, onCallApiUrlIsConfiguredThroughEnvVar); - } catch (e) { - return this.getHumanReadableErrorFromOnCallError(e, onCallApiUrl, 'sync', onCallApiUrlIsConfiguredThroughEnvVar); - } - }; - - static checkTokenAndSyncDataWithOncall = async (onCallApiUrl: string): Promise => { /** * Allows the plugin config page to repair settings like the app initialization screen if a user deletes * an API key on accident but leaves the plugin settings intact. @@ -290,7 +220,7 @@ class PluginState { } } - return await PluginState.syncDataWithOnCall(onCallApiUrl); + return await PluginState.updatePluginStatus(onCallApiUrl); }; static installPlugin = async ( @@ -372,31 +302,13 @@ class PluginState { return null; }; - static checkIfBackendIsInMaintenanceMode = async ( - onCallApiUrl: string, - onCallApiUrlIsConfiguredThroughEnvVar = false - ): Promise => { - try { - return await makeRequest('/maintenance-mode-status', { - method: 'GET', - }); - } catch (e) { - return this.getHumanReadableErrorFromOnCallError( - e, - onCallApiUrl, - 'install', - onCallApiUrlIsConfiguredThroughEnvVar - ); - } - }; - - static checkIfPluginIsConnected = async ( + static updatePluginStatus = async ( onCallApiUrl: string, onCallApiUrlIsConfiguredThroughEnvVar = false ): Promise => { try { return await makeRequest(`${this.ONCALL_BASE_URL}/status`, { - method: 'GET', + method: 'POST', }); } catch (e) { return this.getHumanReadableErrorFromOnCallError( diff --git a/grafana-plugin/src/state/plugin/plugin.test.ts b/grafana-plugin/src/state/plugin/plugin.test.ts index 98fab74ea3..6ca62b71e3 100644 --- a/grafana-plugin/src/state/plugin/plugin.test.ts +++ b/grafana-plugin/src/state/plugin/plugin.test.ts @@ -1,6 +1,6 @@ import { makeRequest as makeRequestOriginal, isNetworkError as isNetworkErrorOriginal } from 'network'; -import PluginState, { InstallationVerb, PluginSyncStatusResponse, UpdateGrafanaPluginSettingsProps } from '.'; +import PluginState, { InstallationVerb, UpdateGrafanaPluginSettingsProps } from '.'; const makeRequest = makeRequestOriginal as jest.Mock>; const isNetworkError = isNetworkErrorOriginal as unknown as jest.Mock>; @@ -234,247 +234,6 @@ describe('PluginState.createGrafanaToken', () => { ); }); -describe('PluginState.getPluginSyncStatus', () => { - test('it returns the plugin sync response', async () => { - // mocks - const mockedResp: PluginSyncStatusResponse = { - license: 'asdasdf', - version: 'asdasf', - token_ok: true, - recaptcha_site_key: 'asdasdf', - }; - makeRequest.mockResolvedValueOnce(mockedResp); - - // test - const response = await PluginState.getPluginSyncStatus(); - - // assertions - expect(response).toEqual(mockedResp); - expect(makeRequest).toHaveBeenCalledTimes(1); - expect(makeRequest).toHaveBeenCalledWith(`${ONCALL_BASE_URL}/sync`, { method: 'GET' }); - }); -}); - -describe('PluginState.pollOnCallDataSyncStatus', () => { - const onCallApiUrl = 'http://hello.com'; - const onCallApiUrlIsConfiguredThroughEnvVar = true; - - test('it returns an error message if the pollCount is greater than 10', async () => { - // mocks - const mockSyncResponse = { token_ok: false }; - - PluginState.getPluginSyncStatus = jest.fn().mockResolvedValue(mockSyncResponse); - PluginState.timeout = jest.fn(); - - // test - const response = await PluginState.pollOnCallDataSyncStatus(onCallApiUrl, onCallApiUrlIsConfiguredThroughEnvVar); - - // assertions - expect(response).toMatchSnapshot(); - - expect(PluginState.getPluginSyncStatus).toHaveBeenCalledTimes(11); - expect(PluginState.getPluginSyncStatus).toHaveBeenCalledWith(); - - expect(PluginState.timeout).toHaveBeenCalledTimes(11); - expect(PluginState.timeout).toHaveBeenLastCalledWith(10); - }); - - test('it returns successfully if the getPluginSyncStatus response token_ok is true', async () => { - // mocks - const mockSyncResponse = { token_ok: true, foo: 'bar' }; - - PluginState.getPluginSyncStatus = jest.fn().mockResolvedValueOnce(mockSyncResponse); - - // test - const response = await PluginState.pollOnCallDataSyncStatus(onCallApiUrl, onCallApiUrlIsConfiguredThroughEnvVar); - - // assertions - expect(response).toEqual(mockSyncResponse); - - expect(PluginState.getPluginSyncStatus).toHaveBeenCalledTimes(1); - expect(PluginState.getPluginSyncStatus).toHaveBeenCalledWith(); - }); - - test('it recursively calls itself if the getPluginSyncStatus response token_ok is not true', async () => { - // mocks - const mockSyncResponse = { token_ok: false }; - const mock_pollOnCallDataSyncStatusResponse = { foo: 'bar' }; - - PluginState.getPluginSyncStatus = jest.fn().mockResolvedValueOnce(mockSyncResponse); - PluginState.timeout = jest.fn(); - PluginState._pollOnCallDataSyncStatus = jest.fn().mockResolvedValueOnce(mock_pollOnCallDataSyncStatusResponse); - - // test - const response = await PluginState.pollOnCallDataSyncStatus(onCallApiUrl, onCallApiUrlIsConfiguredThroughEnvVar, 8); - - // assertions - expect(response).toEqual(mock_pollOnCallDataSyncStatusResponse); - - expect(PluginState.getPluginSyncStatus).toHaveBeenCalledTimes(1); - expect(PluginState.getPluginSyncStatus).toHaveBeenCalledWith(); - - expect(PluginState.timeout).toHaveBeenCalledTimes(1); - expect(PluginState.timeout).toHaveBeenCalledWith(8); - - expect(PluginState._pollOnCallDataSyncStatus).toHaveBeenCalledTimes(1); - expect(PluginState._pollOnCallDataSyncStatus).toHaveBeenCalledWith( - onCallApiUrl, - onCallApiUrlIsConfiguredThroughEnvVar, - 9 - ); - }); - - test('it returns the result of getHumanReadableErrorFromOnCallError in the event of an error from getPluginSyncStatus', async () => { - // mocks - const mockError = { foo: 'bar' }; - const mockedHumanReadableError = 'kjdfkjfdjkfdkjfd'; - - PluginState.getPluginSyncStatus = jest.fn().mockRejectedValueOnce(mockError); - PluginState._pollOnCallDataSyncStatus = jest.fn(); - PluginState.getHumanReadableErrorFromOnCallError = jest.fn().mockReturnValueOnce(mockedHumanReadableError); - - // test - const response = await PluginState.pollOnCallDataSyncStatus(onCallApiUrl, onCallApiUrlIsConfiguredThroughEnvVar); - - // assertions - expect(response).toEqual(mockedHumanReadableError); - - expect(PluginState.getPluginSyncStatus).toHaveBeenCalledTimes(1); - expect(PluginState.getPluginSyncStatus).toHaveBeenCalledWith(); - - expect(PluginState.getHumanReadableErrorFromOnCallError).toHaveBeenCalledTimes(1); - expect(PluginState.getHumanReadableErrorFromOnCallError).toHaveBeenCalledWith( - mockError, - onCallApiUrl, - 'sync', - onCallApiUrlIsConfiguredThroughEnvVar - ); - - expect(PluginState._pollOnCallDataSyncStatus).not.toHaveBeenCalled(); - }); - - test('it returns the result of getHumanReadableErrorFromOnCallError in the event of an error from a recursive call to pollOnCallDataSyncStatus', async () => { - // mocks - const mockSyncResponse = { token_ok: false }; - const mockError = { foo: 'bar' }; - const mockedHumanReadableError = 'kjdfkjfdjkfdkjfd'; - - PluginState.getPluginSyncStatus = jest.fn().mockResolvedValueOnce(mockSyncResponse); - PluginState._pollOnCallDataSyncStatus = jest.fn().mockRejectedValueOnce(mockError); - PluginState.timeout = jest.fn(); - PluginState.getHumanReadableErrorFromOnCallError = jest.fn().mockReturnValueOnce(mockedHumanReadableError); - - // test - const response = await PluginState.pollOnCallDataSyncStatus(onCallApiUrl, onCallApiUrlIsConfiguredThroughEnvVar, 5); - - // assertions - expect(response).toEqual(mockedHumanReadableError); - - expect(PluginState.getPluginSyncStatus).toHaveBeenCalledTimes(1); - expect(PluginState.getPluginSyncStatus).toHaveBeenCalledWith(); - - expect(PluginState.timeout).toHaveBeenCalledTimes(1); - expect(PluginState.timeout).toHaveBeenCalledWith(5); - - expect(PluginState._pollOnCallDataSyncStatus).toHaveBeenCalledTimes(1); - expect(PluginState._pollOnCallDataSyncStatus).toHaveBeenCalledWith( - onCallApiUrl, - onCallApiUrlIsConfiguredThroughEnvVar, - 6 - ); - - expect(PluginState.getHumanReadableErrorFromOnCallError).toHaveBeenCalledTimes(1); - expect(PluginState.getHumanReadableErrorFromOnCallError).toHaveBeenCalledWith( - mockError, - onCallApiUrl, - 'sync', - onCallApiUrlIsConfiguredThroughEnvVar - ); - }); -}); - -describe('PluginState.syncDataWithOnCall', () => { - const onCallApiUrl = 'http://hello.com'; - const onCallApiUrlIsConfiguredThroughEnvVar = true; - const requestUrl = `${ONCALL_BASE_URL}/sync`; - const requestArgs = { method: 'POST' }; - - test('it returns the error mesage if the start sync returns an error', async () => { - // mocks - const errorMsg = 'asdfasdf'; - - makeRequest.mockResolvedValueOnce(errorMsg); - PluginState.getGrafanaToken = jest.fn().mockReturnValueOnce({ id: 1 }); - PluginState.pollOnCallDataSyncStatus = jest.fn(); - - // test - const response = await PluginState.syncDataWithOnCall(onCallApiUrl, onCallApiUrlIsConfiguredThroughEnvVar); - - // assertions - expect(response).toEqual(errorMsg); - - expect(makeRequest).toHaveBeenCalledTimes(1); - expect(makeRequest).toHaveBeenCalledWith(requestUrl, requestArgs); - - expect(PluginState.pollOnCallDataSyncStatus).not.toHaveBeenCalled(); - }); - - test('it calls pollOnCallDataSyncStatus if the start sync does not return an error', async () => { - // mocks - const mockedResponse = { foo: 'bar' }; - const mockedPollOnCallDataSyncStatusResponse = 'dfjkdfjdf'; - - makeRequest.mockResolvedValueOnce(mockedResponse); - PluginState.getGrafanaToken = jest.fn().mockReturnValueOnce({ id: 1 }); - PluginState.pollOnCallDataSyncStatus = jest.fn().mockResolvedValueOnce(mockedPollOnCallDataSyncStatusResponse); - - // test - const response = await PluginState.syncDataWithOnCall(onCallApiUrl, onCallApiUrlIsConfiguredThroughEnvVar); - - // assertions - expect(response).toEqual(mockedPollOnCallDataSyncStatusResponse); - - expect(makeRequest).toHaveBeenCalledTimes(1); - expect(makeRequest).toHaveBeenCalledWith(requestUrl, requestArgs); - - expect(PluginState.pollOnCallDataSyncStatus).toHaveBeenCalledTimes(1); - expect(PluginState.pollOnCallDataSyncStatus).toHaveBeenCalledWith( - onCallApiUrl, - onCallApiUrlIsConfiguredThroughEnvVar - ); - }); - - test('it calls getHumanReadableErrorFromOnCallError if an unknown error pops up', async () => { - // mocks - const mockedError = { foo: 'bar' }; - const mockedHumanReadableError = 'asdfjkdfjkdfjk'; - - makeRequest.mockRejectedValueOnce(mockedError); - PluginState.getGrafanaToken = jest.fn().mockReturnValueOnce({ id: 1 }); - PluginState.pollOnCallDataSyncStatus = jest.fn(); - PluginState.getHumanReadableErrorFromOnCallError = jest.fn().mockReturnValueOnce(mockedHumanReadableError); - - // test - const response = await PluginState.syncDataWithOnCall(onCallApiUrl, onCallApiUrlIsConfiguredThroughEnvVar); - - // assertions - expect(response).toEqual(mockedHumanReadableError); - - expect(makeRequest).toHaveBeenCalledTimes(1); - expect(makeRequest).toHaveBeenCalledWith(requestUrl, requestArgs); - - expect(PluginState.getHumanReadableErrorFromOnCallError).toHaveBeenCalledTimes(1); - expect(PluginState.getHumanReadableErrorFromOnCallError).toHaveBeenCalledWith( - mockedError, - onCallApiUrl, - 'sync', - onCallApiUrlIsConfiguredThroughEnvVar - ); - - expect(PluginState.pollOnCallDataSyncStatus).not.toHaveBeenCalled(); - }); -}); - describe('PluginState.installPlugin', () => { it.each([true, false])('returns the proper response - self hosted: %s', async (selfHosted) => { // mocks @@ -682,25 +441,7 @@ describe('PluginState.selfHostedInstallPlugin', () => { }); }); -describe('PluginState.checkIfBackendIsInMaintenanceMode', () => { - test('it returns the API response', async () => { - // mocks - const maintenanceModeMsg = 'asdfljkadsjlfkajsdf'; - const mockedResp = { currently_undergoing_maintenance_message: maintenanceModeMsg }; - const onCallApiUrl = 'http://hello.com'; - makeRequest.mockResolvedValueOnce(mockedResp); - - // test - const response = await PluginState.checkIfBackendIsInMaintenanceMode(onCallApiUrl); - - // assertions - expect(response).toEqual(mockedResp); - expect(makeRequest).toHaveBeenCalledTimes(1); - expect(makeRequest).toHaveBeenCalledWith('/maintenance-mode-status', { method: 'GET' }); - }); -}); - -describe('PluginState.checkIfPluginIsConnected', () => { +describe('PluginState.updatePluginStatus', () => { test('it returns the API response', async () => { // mocks const mockedResp = { foo: 'bar' }; @@ -708,13 +449,13 @@ describe('PluginState.checkIfPluginIsConnected', () => { makeRequest.mockResolvedValueOnce(mockedResp); // test - const response = await PluginState.checkIfPluginIsConnected(onCallApiUrl); + const response = await PluginState.updatePluginStatus(onCallApiUrl); // assertions expect(response).toEqual(mockedResp); expect(makeRequest).toHaveBeenCalledTimes(1); - expect(makeRequest).toHaveBeenCalledWith(`${ONCALL_BASE_URL}/status`, { method: 'GET' }); + expect(makeRequest).toHaveBeenCalledWith(`${ONCALL_BASE_URL}/status`, { method: 'POST' }); }); test('it returns a human readable error in the event of an unsuccessful api call', async () => { @@ -727,13 +468,13 @@ describe('PluginState.checkIfPluginIsConnected', () => { PluginState.getHumanReadableErrorFromOnCallError = jest.fn().mockReturnValueOnce(mockedHumanReadableError); // test - const response = await PluginState.checkIfPluginIsConnected(onCallApiUrl); + const response = await PluginState.updatePluginStatus(onCallApiUrl); // assertions expect(response).toEqual(mockedHumanReadableError); expect(makeRequest).toHaveBeenCalledTimes(1); - expect(makeRequest).toHaveBeenCalledWith(`${ONCALL_BASE_URL}/status`, { method: 'GET' }); + expect(makeRequest).toHaveBeenCalledWith(`${ONCALL_BASE_URL}/status`, { method: 'POST' }); expect(PluginState.getHumanReadableErrorFromOnCallError).toHaveBeenCalledTimes(1); expect(PluginState.getHumanReadableErrorFromOnCallError).toHaveBeenCalledWith( diff --git a/grafana-plugin/src/state/rootBaseStore/index.ts b/grafana-plugin/src/state/rootBaseStore/index.ts index cf9cebc94f..4075fb4adf 100644 --- a/grafana-plugin/src/state/rootBaseStore/index.ts +++ b/grafana-plugin/src/state/rootBaseStore/index.ts @@ -33,13 +33,11 @@ import { makeRequest } from 'network'; import { AppFeature } from 'state/features'; import PluginState from 'state/plugin'; import { APP_VERSION, CLOUD_VERSION_REGEX, GRAFANA_LICENSE_CLOUD, GRAFANA_LICENSE_OSS } from 'utils/consts'; +import FaroHelper from 'utils/faro'; // ------ Dashboard ------ // export class RootBaseStore { - @observable - appLoading = true; - @observable currentTimezone: Timezone = moment.tz.guess() as Timezone; @@ -117,6 +115,7 @@ export class RootBaseStore { }; return Promise.all([ + this.userStore.loadCurrentUser(), this.organizationStore.loadCurrentOrganization(), this.grafanaTeamStore.updateItems(), updateFeatures(), @@ -130,56 +129,63 @@ export class RootBaseStore { } setupPluginError(errorMsg: string) { - this.appLoading = false; this.initializationError = errorMsg; } /** + * This function is called in the background when the plugin is loaded. + * It will check the status of the plugin and + * rerender the screen with the appropriate message if the plugin is not setup correctly. + * * First check to see if the plugin has been provisioned (plugin's meta jsonData has an onCallApiUrl saved) * If not, tell the user they first need to configure/provision the plugin. * * Otherwise, get the plugin connection status from the OnCall API and check a few pre-conditions: + * - OnCall api should not be under maintenance * - plugin must be considered installed by the OnCall API * - token_ok must be true * - This represents the status of the Grafana API token. It can be false in the event that either the token * hasn't been created, or if the API token was revoked in Grafana. * - user must be not "anonymous" (this is determined by the plugin-proxy) * - the OnCall API must be currently allowing signup - * - the user must have an Admin role - * If these conditions are all met then trigger a data sync w/ the OnCall backend and poll its response + * - the user must have an Admin role and necessary permissions * Finally, try to load the current user from the OnCall backend */ async setupPlugin(meta: OnCallAppPluginMeta) { - this.appLoading = true; this.initializationError = null; this.onCallApiUrl = meta.jsonData?.onCallApiUrl; + if (!FaroHelper.faro) { + FaroHelper.initializeFaro(this.onCallApiUrl); + } + if (!this.onCallApiUrl) { // plugin is not provisioned return this.setupPluginError('🚫 Plugin has not been initialized'); } - const maintenanceMode = await PluginState.checkIfBackendIsInMaintenanceMode(this.onCallApiUrl); - if (typeof maintenanceMode === 'string') { - return this.setupPluginError(maintenanceMode); - } else if (maintenanceMode.currently_undergoing_maintenance_message) { - this.currentlyUndergoingMaintenance = true; - return this.setupPluginError(`🚧 ${maintenanceMode.currently_undergoing_maintenance_message} 🚧`); - } - // at this point we know the plugin is provisioned - const pluginConnectionStatus = await PluginState.checkIfPluginIsConnected(this.onCallApiUrl); + const pluginConnectionStatus = await PluginState.updatePluginStatus(this.onCallApiUrl); if (typeof pluginConnectionStatus === 'string') { return this.setupPluginError(pluginConnectionStatus); } + // Check if the plugin is currently undergoing maintenance + if (pluginConnectionStatus.currently_undergoing_maintenance_message) { + this.currentlyUndergoingMaintenance = true; + return this.setupPluginError(`🚧 ${pluginConnectionStatus.currently_undergoing_maintenance_message} 🚧`); + } + const { allow_signup, is_installed, is_user_anonymous, token_ok } = pluginConnectionStatus; + // Anonymous users are not allowed to use the plugin if (is_user_anonymous) { return this.setupPluginError( '😞 Grafana OnCall is available for authorized users only, please sign in to proceed.' ); - } else if (!is_installed || !token_ok) { + } + // If the plugin is not installed in the OnCall backend, or token is not valid, then we need to install it + if (!is_installed || !token_ok) { if (!allow_signup) { return this.setupPluginError('🚫 OnCall has temporarily disabled signup of new users. Please try again later.'); } @@ -211,25 +217,18 @@ export class RootBaseStore { } } } else { - const syncDataResponse = await PluginState.syncDataWithOnCall(this.onCallApiUrl); - - if (typeof syncDataResponse === 'string') { - return this.setupPluginError(syncDataResponse); - } - // everything is all synced successfully at this point.. - this.backendVersion = syncDataResponse.version; - this.backendLicense = syncDataResponse.license; - this.recaptchaSiteKey = syncDataResponse.recaptcha_site_key; + this.backendVersion = pluginConnectionStatus.version; + this.backendLicense = pluginConnectionStatus.license; + this.recaptchaSiteKey = pluginConnectionStatus.recaptcha_site_key; } - - try { - await this.userStore.loadCurrentUser(); - } catch (e) { - return this.setupPluginError('OnCall was not able to load the current user. Try refreshing the page'); + if (!this.userStore.currentUser) { + try { + await this.userStore.loadCurrentUser(); + } catch (e) { + return this.setupPluginError('OnCall was not able to load the current user. Try refreshing the page'); + } } - - this.appLoading = false; } checkMissingSetupPermissions() { diff --git a/grafana-plugin/src/state/rootBaseStore/rootBaseStore.test.ts b/grafana-plugin/src/state/rootBaseStore/rootBaseStore.test.ts index e3229fc215..4f5992ee60 100644 --- a/grafana-plugin/src/state/rootBaseStore/rootBaseStore.test.ts +++ b/grafana-plugin/src/state/rootBaseStore/rootBaseStore.test.ts @@ -39,7 +39,6 @@ describe('rootBaseStore', () => { await rootBaseStore.setupPlugin(generatePluginData()); // assertions - expect(rootBaseStore.appLoading).toBe(false); expect(rootBaseStore.initializationError).toEqual('🚫 Plugin has not been initialized'); }); @@ -49,19 +48,15 @@ describe('rootBaseStore', () => { const onCallApiUrl = 'http://asdfasdf.com'; const rootBaseStore = new RootBaseStore(); - PluginState.checkIfBackendIsInMaintenanceMode = jest - .fn() - .mockResolvedValueOnce({ currently_undergoing_maintenance_message: null }); - PluginState.checkIfPluginIsConnected = jest.fn().mockResolvedValueOnce(errorMsg); + PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce(errorMsg); // test await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl)); // assertions - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(onCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(onCallApiUrl); - expect(rootBaseStore.appLoading).toBe(false); expect(rootBaseStore.initializationError).toEqual(errorMsg); }); @@ -71,7 +66,7 @@ describe('rootBaseStore', () => { const rootBaseStore = new RootBaseStore(); const maintenanceMessage = 'mncvnmvcmnvkjdjkd'; - PluginState.checkIfBackendIsInMaintenanceMode = jest + PluginState.updatePluginStatus = jest .fn() .mockResolvedValueOnce({ currently_undergoing_maintenance_message: maintenanceMessage }); @@ -79,10 +74,9 @@ describe('rootBaseStore', () => { await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl)); // assertions - expect(PluginState.checkIfBackendIsInMaintenanceMode).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfBackendIsInMaintenanceMode).toHaveBeenCalledWith(onCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(onCallApiUrl); - expect(rootBaseStore.appLoading).toBe(false); expect(rootBaseStore.initializationError).toEqual(`🚧 ${maintenanceMessage} 🚧`); expect(rootBaseStore.currentlyUndergoingMaintenance).toBe(true); }); @@ -92,10 +86,7 @@ describe('rootBaseStore', () => { const onCallApiUrl = 'http://asdfasdf.com'; const rootBaseStore = new RootBaseStore(); - PluginState.checkIfBackendIsInMaintenanceMode = jest - .fn() - .mockResolvedValueOnce({ currently_undergoing_maintenance_message: null }); - PluginState.checkIfPluginIsConnected = jest.fn().mockResolvedValueOnce({ + PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce({ is_user_anonymous: true, is_installed: true, token_ok: true, @@ -108,10 +99,9 @@ describe('rootBaseStore', () => { await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl)); // assertions - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(onCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(onCallApiUrl); - expect(rootBaseStore.appLoading).toBe(false); expect(rootBaseStore.initializationError).toEqual( '😞 Grafana OnCall is available for authorized users only, please sign in to proceed.' ); @@ -122,10 +112,7 @@ describe('rootBaseStore', () => { const onCallApiUrl = 'http://asdfasdf.com'; const rootBaseStore = new RootBaseStore(); - PluginState.checkIfBackendIsInMaintenanceMode = jest - .fn() - .mockResolvedValueOnce({ currently_undergoing_maintenance_message: null }); - PluginState.checkIfPluginIsConnected = jest.fn().mockResolvedValueOnce({ + PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce({ is_user_anonymous: false, is_installed: false, token_ok: true, @@ -139,12 +126,11 @@ describe('rootBaseStore', () => { await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl)); // assertions - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(onCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(onCallApiUrl); expect(PluginState.installPlugin).toHaveBeenCalledTimes(0); - expect(rootBaseStore.appLoading).toBe(false); expect(rootBaseStore.initializationError).toEqual( '🚫 OnCall has temporarily disabled signup of new users. Please try again later.' ); @@ -159,10 +145,7 @@ describe('rootBaseStore', () => { contextSrv.accessControlEnabled = jest.fn().mockReturnValue(false); contextSrv.hasAccess = jest.fn().mockReturnValue(false); - PluginState.checkIfBackendIsInMaintenanceMode = jest - .fn() - .mockResolvedValueOnce({ currently_undergoing_maintenance_message: null }); - PluginState.checkIfPluginIsConnected = jest.fn().mockResolvedValueOnce({ + PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce({ is_user_anonymous: false, is_installed: false, token_ok: true, @@ -177,12 +160,11 @@ describe('rootBaseStore', () => { await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl)); // assertions - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(onCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(onCallApiUrl); expect(PluginState.installPlugin).toHaveBeenCalledTimes(0); - expect(rootBaseStore.appLoading).toBe(false); expect(rootBaseStore.initializationError).toEqual( '🚫 User with Admin permissions in your organization must sign on and setup OnCall before it can be used' ); @@ -201,10 +183,7 @@ describe('rootBaseStore', () => { contextSrv.accessControlEnabled = jest.fn().mockResolvedValueOnce(false); contextSrv.hasAccess = jest.fn().mockReturnValue(true); - PluginState.checkIfBackendIsInMaintenanceMode = jest - .fn() - .mockResolvedValueOnce({ currently_undergoing_maintenance_message: null }); - PluginState.checkIfPluginIsConnected = jest.fn().mockResolvedValueOnce({ + PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce({ ...scenario, is_user_anonymous: false, allow_signup: true, @@ -219,8 +198,8 @@ describe('rootBaseStore', () => { await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl)); // assertions - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(onCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(onCallApiUrl); expect(PluginState.installPlugin).toHaveBeenCalledTimes(1); expect(PluginState.installPlugin).toHaveBeenCalledWith(); @@ -228,7 +207,6 @@ describe('rootBaseStore', () => { expect(mockedLoadCurrentUser).toHaveBeenCalledTimes(1); expect(mockedLoadCurrentUser).toHaveBeenCalledWith(); - expect(rootBaseStore.appLoading).toBe(false); expect(rootBaseStore.initializationError).toBeNull(); }); @@ -255,10 +233,7 @@ describe('rootBaseStore', () => { contextSrv.accessControlEnabled = jest.fn().mockReturnValue(true); rootBaseStore.checkMissingSetupPermissions = jest.fn().mockImplementation(() => scenario.missing_permissions); - PluginState.checkIfBackendIsInMaintenanceMode = jest - .fn() - .mockResolvedValueOnce({ currently_undergoing_maintenance_message: null }); - PluginState.checkIfPluginIsConnected = jest.fn().mockResolvedValueOnce({ + PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce({ ...scenario, is_user_anonymous: false, allow_signup: true, @@ -273,10 +248,8 @@ describe('rootBaseStore', () => { await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl)); // assertions - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(onCallApiUrl); - - expect(rootBaseStore.appLoading).toBe(false); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(onCallApiUrl); if (scenario.expected_result) { expect(PluginState.installPlugin).toHaveBeenCalledTimes(1); @@ -308,10 +281,7 @@ describe('rootBaseStore', () => { contextSrv.accessControlEnabled = jest.fn().mockReturnValue(false); contextSrv.hasAccess = jest.fn().mockReturnValue(true); - PluginState.checkIfBackendIsInMaintenanceMode = jest - .fn() - .mockResolvedValueOnce({ currently_undergoing_maintenance_message: null }); - PluginState.checkIfPluginIsConnected = jest.fn().mockResolvedValueOnce({ + PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce({ is_user_anonymous: false, is_installed: false, token_ok: true, @@ -327,8 +297,8 @@ describe('rootBaseStore', () => { await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl)); // assertions - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(onCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(onCallApiUrl); expect(PluginState.installPlugin).toHaveBeenCalledTimes(1); expect(PluginState.installPlugin).toHaveBeenCalledWith(); @@ -340,7 +310,6 @@ describe('rootBaseStore', () => { 'install' ); - expect(rootBaseStore.appLoading).toBe(false); expect(rootBaseStore.initializationError).toEqual(humanReadableErrorMsg); }); @@ -349,13 +318,8 @@ describe('rootBaseStore', () => { const onCallApiUrl = 'http://asdfasdf.com'; const rootBaseStore = new RootBaseStore(); const mockedLoadCurrentUser = jest.fn(); - const version = 'asdfalkjslkjdf'; - const license = 'lkjdkjfdkjfdjkfd'; - PluginState.checkIfBackendIsInMaintenanceMode = jest - .fn() - .mockResolvedValueOnce({ currently_undergoing_maintenance_message: null }); - PluginState.checkIfPluginIsConnected = jest.fn().mockResolvedValueOnce({ + PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce({ is_user_anonymous: false, is_installed: true, token_ok: true, @@ -363,23 +327,18 @@ describe('rootBaseStore', () => { version: 'asdfasdf', license: 'asdfasdf', }); - PluginState.syncDataWithOnCall = jest.fn().mockResolvedValueOnce({ version, license, token_ok: true }); rootBaseStore.userStore.loadCurrentUser = mockedLoadCurrentUser; // test await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl)); // assertions - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(onCallApiUrl); - - expect(PluginState.syncDataWithOnCall).toHaveBeenCalledTimes(1); - expect(PluginState.syncDataWithOnCall).toHaveBeenCalledWith(onCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(onCallApiUrl); expect(mockedLoadCurrentUser).toHaveBeenCalledTimes(1); expect(mockedLoadCurrentUser).toHaveBeenCalledWith(); - expect(rootBaseStore.appLoading).toBe(false); expect(rootBaseStore.initializationError).toBeNull(); }); @@ -388,12 +347,9 @@ describe('rootBaseStore', () => { const onCallApiUrl = 'http://asdfasdf.com'; const rootBaseStore = new RootBaseStore(); const mockedLoadCurrentUser = jest.fn(); - const syncDataWithOnCallError = 'asdasdfasdfasf'; + const updatePluginStatusError = 'asdasdfasdfasf'; - PluginState.checkIfBackendIsInMaintenanceMode = jest - .fn() - .mockResolvedValueOnce({ currently_undergoing_maintenance_message: null }); - PluginState.checkIfPluginIsConnected = jest.fn().mockResolvedValueOnce({ + PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce({ is_user_anonymous: false, is_installed: true, token_ok: true, @@ -401,20 +357,16 @@ describe('rootBaseStore', () => { version: 'asdfasdf', license: 'asdfasdf', }); - PluginState.syncDataWithOnCall = jest.fn().mockResolvedValueOnce(syncDataWithOnCallError); + PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce(updatePluginStatusError); rootBaseStore.userStore.loadCurrentUser = mockedLoadCurrentUser; // test await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl)); // assertions - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledTimes(1); - expect(PluginState.checkIfPluginIsConnected).toHaveBeenCalledWith(onCallApiUrl); - - expect(PluginState.syncDataWithOnCall).toHaveBeenCalledTimes(1); - expect(PluginState.syncDataWithOnCall).toHaveBeenCalledWith(onCallApiUrl); + expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1); + expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(onCallApiUrl); - expect(rootBaseStore.appLoading).toBe(false); - expect(rootBaseStore.initializationError).toEqual(syncDataWithOnCallError); + expect(rootBaseStore.initializationError).toEqual(updatePluginStatusError); }); });