Remove checks that slow down plugin load and cause "Initializing plugin..." (#2624)
# What this PR does * Removes "Initializing plugin.." message during load * Removes black screen when plugin loads * Removes wait for syncs between OnCall and Grafana * Deprecates GET /status, POST /sync, GET /sync in favour of single POST /status ## Which issue(s) this PR fixes ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required)
This commit is contained in:
parent
5341a7ea5b
commit
c73d0f385a
19 changed files with 368 additions and 653 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 @@ from common.api_helpers.mixins import GrafanaHeadersMixin
|
|||
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
|
||||
|
|
|
|||
|
|
@ -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 @@ class PluginSyncView(GrafanaHeadersMixin, APIView):
|
|||
)
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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/);
|
||||
// });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(<PluginConfigPage {...generateComponentProps(metaJsonDataOnCallApiUrl, true)} />);
|
||||
|
|
@ -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<typeof useLocationOriginal>);
|
||||
|
||||
PluginState.checkIfPluginIsConnected = jest.fn();
|
||||
mockCheckTokenAndSyncDataWithOncall();
|
||||
PluginState.updatePluginStatus = jest.fn();
|
||||
mockCheckTokenAndIfPluginIsConnected();
|
||||
|
||||
// test setup
|
||||
const component = render(<PluginConfigPage {...generateComponentProps(metaJsonDataOnCallApiUrl)} />);
|
||||
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(<PluginConfigPage {...generateComponentProps()} />);
|
||||
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(<PluginConfigPage {...generateComponentProps()} />);
|
||||
|
|
@ -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(<PluginConfigPage {...generateComponentProps(metaJsonDataOnCallApiUrl)} />);
|
||||
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(<PluginConfigPage {...generateComponentProps(metaJsonDataOnCallApiUrl)} />);
|
||||
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(<PluginConfigPage {...generateComponentProps(metaJsonDataOnCallApiUrl)} />);
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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<OnCallPluginConfigPageProps> = ({
|
|||
const [pluginConnectionCheckError, setPluginConnectionCheckError] = useState<string>(null);
|
||||
const [pluginIsConnected, setPluginIsConnected] = useState<PluginStatusResponseBase>(
|
||||
pluginConfiguredRedirect
|
||||
? { version: pluginConfiguredVersionQueryParam, license: pluginConfiguredLicenseQueryParam }
|
||||
? {
|
||||
version: pluginConfiguredVersionQueryParam,
|
||||
license: pluginConfiguredLicenseQueryParam,
|
||||
recaptcha_site_key: 'abc',
|
||||
currently_undergoing_maintenance_message: 'false',
|
||||
}
|
||||
: null
|
||||
);
|
||||
|
||||
const [syncingPlugin, setSyncingPlugin] = useState<boolean>(false);
|
||||
const [syncError, setSyncError] = useState<string>(null);
|
||||
const [updatingPluginStatus, setUpdatingPluginStatus] = useState<boolean>(false);
|
||||
const [updatingPluginStatusError, setUpdatingPluginStatusError] = useState<string>(null);
|
||||
|
||||
const [resettingPlugin, setResettingPlugin] = useState<boolean>(false);
|
||||
const [pluginResetError, setPluginResetError] = useState<string>(null);
|
||||
|
|
@ -78,27 +83,27 @@ const PluginConfigPage: FC<OnCallPluginConfigPageProps> = ({
|
|||
|
||||
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<OnCallPluginConfigPageProps> = ({
|
|||
* 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<OnCallPluginConfigPageProps> = ({
|
|||
* plugin setup
|
||||
*/
|
||||
if (!pluginConfiguredRedirect) {
|
||||
configurePluginAndSyncData();
|
||||
configurePluginAndUpdatePluginStatus();
|
||||
}
|
||||
}, [pluginMetaOnCallApiUrl, processEnvOnCallApiUrl, onCallApiUrl, pluginConfiguredRedirect]);
|
||||
|
||||
|
|
@ -147,7 +152,7 @@ const PluginConfigPage: FC<OnCallPluginConfigPageProps> = ({
|
|||
setPluginResetError(null);
|
||||
setPluginConnectionCheckError(null);
|
||||
setPluginIsConnected(null);
|
||||
setSyncError(null);
|
||||
setUpdatingPluginStatusError(null);
|
||||
}, []);
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
|
|
@ -177,7 +182,7 @@ const PluginConfigPage: FC<OnCallPluginConfigPageProps> = ({
|
|||
|
||||
const ReconfigurePluginButtons = () => (
|
||||
<HorizontalGroup>
|
||||
<Button variant="primary" onClick={triggerDataSyncWithOnCall} size="md">
|
||||
<Button variant="primary" onClick={triggerUpdatePluginStatus} size="md">
|
||||
Retry Sync
|
||||
</Button>
|
||||
{licenseType === GRAFANA_LICENSE_OSS ? <RemoveConfigButton /> : null}
|
||||
|
|
@ -188,7 +193,7 @@ const PluginConfigPage: FC<OnCallPluginConfigPageProps> = ({
|
|||
|
||||
if (checkingIfPluginIsConnected) {
|
||||
content = <LoadingPlaceholder text="Validating your plugin connection..." />;
|
||||
} else if (syncingPlugin) {
|
||||
} else if (updatingPluginStatus) {
|
||||
content = <LoadingPlaceholder text="Syncing data required for your plugin..." />;
|
||||
} else if (pluginConnectionCheckError || pluginResetError) {
|
||||
content = (
|
||||
|
|
@ -197,24 +202,35 @@ const PluginConfigPage: FC<OnCallPluginConfigPageProps> = ({
|
|||
<ReconfigurePluginButtons />
|
||||
</>
|
||||
);
|
||||
} else if (syncError) {
|
||||
} else if (updatingPluginStatusError) {
|
||||
content = (
|
||||
<>
|
||||
<StatusMessageBlock text={syncError} />
|
||||
<StatusMessageBlock text={updatingPluginStatusError} />
|
||||
<ReconfigurePluginButtons />
|
||||
</>
|
||||
);
|
||||
} else if (!pluginIsConnected) {
|
||||
content = (
|
||||
<ConfigurationForm onSuccessfulSetup={triggerDataSyncWithOnCall} defaultOnCallApiUrl={processEnvOnCallApiUrl} />
|
||||
<ConfigurationForm onSuccessfulSetup={triggerUpdatePluginStatus} defaultOnCallApiUrl={processEnvOnCallApiUrl} />
|
||||
);
|
||||
} else {
|
||||
// plugin is fully connected and synced
|
||||
const pluginLink = (
|
||||
<LinkButton href={`/a/grafana-oncall-app/`} variant="primary">
|
||||
Open Grafana OnCall
|
||||
</LinkButton>
|
||||
);
|
||||
content =
|
||||
licenseType === GRAFANA_LICENSE_OSS ? (
|
||||
<RemoveConfigButton />
|
||||
<HorizontalGroup>
|
||||
{pluginLink}
|
||||
<RemoveConfigButton />
|
||||
</HorizontalGroup>
|
||||
) : (
|
||||
<Label>This is a cloud managed configuration.</Label>
|
||||
<VerticalGroup>
|
||||
<Label>This is a cloud managed configuration.</Label>
|
||||
{pluginLink}
|
||||
</VerticalGroup>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -223,10 +239,6 @@ const PluginConfigPage: FC<OnCallPluginConfigPageProps> = ({
|
|||
<Legend>Configure Grafana OnCall</Legend>
|
||||
{pluginIsConnected ? (
|
||||
<>
|
||||
<p>
|
||||
Plugin is connected! Continue to Grafana OnCall by clicking OnCall under Alerts & IRM in the navigation over
|
||||
there 👈
|
||||
</p>
|
||||
<StatusMessageBlock
|
||||
text={`Connected to OnCall (${pluginIsConnected.version}, ${pluginIsConnected.license})`}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ exports[`PluginConfigPage If onCallApiUrl is not set in the plugin's meta jsonDa
|
|||
</div>
|
||||
`;
|
||||
|
||||
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`] = `
|
||||
<div>
|
||||
<legend
|
||||
class="css-11wqcat"
|
||||
|
|
@ -156,7 +156,7 @@ exports[`PluginConfigPage If onCallApiUrl is not set in the plugin's meta jsonDa
|
|||
</div>
|
||||
`;
|
||||
|
||||
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`] = `
|
||||
<div>
|
||||
<legend
|
||||
class="css-11wqcat"
|
||||
|
|
@ -218,9 +218,6 @@ exports[`PluginConfigPage It doesn't make any network calls if the plugin config
|
|||
>
|
||||
Configure Grafana OnCall
|
||||
</legend>
|
||||
<p>
|
||||
Plugin is connected! Continue to Grafana OnCall by clicking OnCall under Alerts & IRM in the navigation over there 👈
|
||||
</p>
|
||||
<pre
|
||||
data-testid="status-message-block"
|
||||
>
|
||||
|
|
@ -230,29 +227,50 @@ exports[`PluginConfigPage It doesn't make any network calls if the plugin config
|
|||
Connected to OnCall (v1.2.3, OpenSource)
|
||||
</span>
|
||||
</pre>
|
||||
<button
|
||||
class="css-1ed0qk5-button"
|
||||
type="button"
|
||||
<div
|
||||
class="css-ve64a7-horizontal-group"
|
||||
style="width: 100%; height: 100%;"
|
||||
>
|
||||
<span
|
||||
class="css-1mhnkuh"
|
||||
<div
|
||||
class="css-cvef6c-layoutChildrenWrapper"
|
||||
>
|
||||
Remove current configuration
|
||||
</span>
|
||||
</button>
|
||||
<a
|
||||
class="css-z53gi5-button"
|
||||
href="/a/grafana-oncall-app/"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="css-1mhnkuh"
|
||||
>
|
||||
Open Grafana OnCall
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="css-cvef6c-layoutChildrenWrapper"
|
||||
>
|
||||
<button
|
||||
class="css-1ed0qk5-button"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="css-1mhnkuh"
|
||||
>
|
||||
Remove current configuration
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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`] = `
|
||||
<div>
|
||||
<legend
|
||||
class="css-11wqcat"
|
||||
>
|
||||
Configure Grafana OnCall
|
||||
</legend>
|
||||
<p>
|
||||
Plugin is connected! Continue to Grafana OnCall by clicking OnCall under Alerts & IRM in the navigation over there 👈
|
||||
</p>
|
||||
<pre
|
||||
data-testid="status-message-block"
|
||||
>
|
||||
|
|
@ -262,29 +280,50 @@ exports[`PluginConfigPage OnCallApiUrl is set, and checkApiTokenSyncData does no
|
|||
Connected to OnCall (v1.2.3, OpenSource)
|
||||
</span>
|
||||
</pre>
|
||||
<button
|
||||
class="css-1ed0qk5-button"
|
||||
type="button"
|
||||
<div
|
||||
class="css-ve64a7-horizontal-group"
|
||||
style="width: 100%; height: 100%;"
|
||||
>
|
||||
<span
|
||||
class="css-1mhnkuh"
|
||||
<div
|
||||
class="css-cvef6c-layoutChildrenWrapper"
|
||||
>
|
||||
Remove current configuration
|
||||
</span>
|
||||
</button>
|
||||
<a
|
||||
class="css-z53gi5-button"
|
||||
href="/a/grafana-oncall-app/"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="css-1mhnkuh"
|
||||
>
|
||||
Open Grafana OnCall
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="css-cvef6c-layoutChildrenWrapper"
|
||||
>
|
||||
<button
|
||||
class="css-1ed0qk5-button"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="css-1mhnkuh"
|
||||
>
|
||||
Remove current configuration
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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`] = `
|
||||
<div>
|
||||
<legend
|
||||
class="css-11wqcat"
|
||||
>
|
||||
Configure Grafana OnCall
|
||||
</legend>
|
||||
<p>
|
||||
Plugin is connected! Continue to Grafana OnCall by clicking OnCall under Alerts & IRM in the navigation over there 👈
|
||||
</p>
|
||||
<pre
|
||||
data-testid="status-message-block"
|
||||
>
|
||||
|
|
@ -295,20 +334,44 @@ exports[`PluginConfigPage OnCallApiUrl is set, and checkApiTokenSyncData does no
|
|||
</span>
|
||||
</pre>
|
||||
<div
|
||||
class="css-jt4xma-Label"
|
||||
class="css-1j7sh2x-vertical-group"
|
||||
style="width: 100%; height: 100%;"
|
||||
>
|
||||
<label>
|
||||
<div
|
||||
class="css-bxa289-layoutChildrenWrapper"
|
||||
>
|
||||
<div
|
||||
class="css-xhqy0o"
|
||||
class="css-jt4xma-Label"
|
||||
>
|
||||
This is a cloud managed configuration.
|
||||
<label>
|
||||
<div
|
||||
class="css-xhqy0o"
|
||||
>
|
||||
This is a cloud managed configuration.
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="css-bxa289-layoutChildrenWrapper"
|
||||
>
|
||||
<a
|
||||
class="css-z53gi5-button"
|
||||
href="/a/grafana-oncall-app/"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="css-1mhnkuh"
|
||||
>
|
||||
Open Grafana OnCall
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`PluginConfigPage OnCallApiUrl is set, and checkApiTokenSyncData returns an error 1`] = `
|
||||
exports[`PluginConfigPage OnCallApiUrl is set, and checkTokenAndIfPluginIsConnected returns an error 1`] = `
|
||||
<div>
|
||||
<legend
|
||||
class="css-11wqcat"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import dayjs from 'dayjs';
|
||||
|
|
@ -71,8 +71,6 @@ export const GrafanaPluginRootPage = (props: AppRootProps) => {
|
|||
};
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,54 +2,24 @@
|
|||
|
||||
exports[`PluginSetup app initialized with topnavbar = false 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="spin"
|
||||
>
|
||||
<img
|
||||
alt="Grafana OnCall Logo"
|
||||
src="[object Object]"
|
||||
/>
|
||||
<div
|
||||
class="spin-text"
|
||||
>
|
||||
Initializing plugin...
|
||||
</div>
|
||||
<div>
|
||||
hello
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`PluginSetup app initialized with topnavbar = true 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="spin"
|
||||
>
|
||||
<img
|
||||
alt="Grafana OnCall Logo"
|
||||
src="[object Object]"
|
||||
/>
|
||||
<div
|
||||
class="spin-text"
|
||||
>
|
||||
Initializing plugin...
|
||||
</div>
|
||||
<div>
|
||||
hello
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`PluginSetup app is loading 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="spin"
|
||||
>
|
||||
<img
|
||||
alt="Grafana OnCall Logo"
|
||||
src="[object Object]"
|
||||
/>
|
||||
<div
|
||||
class="spin-text"
|
||||
>
|
||||
Initializing plugin...
|
||||
</div>
|
||||
<div>
|
||||
hello
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -35,15 +35,10 @@ const PluginSetupWrapper: FC<PluginSetupWrapperProps> = ({ text, children }) =>
|
|||
const PluginSetup: FC<PluginSetupProps> = observer(({ InitializedComponent, ...props }) => {
|
||||
const store = useStore();
|
||||
const setupPlugin = useCallback(() => store.setupPlugin(props.meta), [props.meta]);
|
||||
|
||||
useEffect(() => {
|
||||
setupPlugin();
|
||||
}, [setupPlugin]);
|
||||
|
||||
if (store.appLoading) {
|
||||
return <PluginSetupWrapper text="Initializing plugin..." />;
|
||||
}
|
||||
|
||||
if (store.initializationError) {
|
||||
return (
|
||||
<PluginSetupWrapper text={store.initializationError}>
|
||||
|
|
@ -62,7 +57,6 @@ const PluginSetup: FC<PluginSetupProps> = observer(({ InitializedComponent, ...p
|
|||
</PluginSetupWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return <InitializedComponent {...props} />;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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<OnCallPluginMetaJSONData>;
|
||||
|
|
@ -11,6 +10,8 @@ export type UpdateGrafanaPluginSettingsProps = {
|
|||
|
||||
export type PluginStatusResponseBase = Pick<OnCallPluginMetaJSONData, 'license'> & {
|
||||
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<OnCallPluginMetaJSONData, 'onCallApiUrl'> & {
|
||||
|
|
@ -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<PluginSyncStatusResponse> =>
|
||||
makeRequest<PluginSyncStatusResponse>(`${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
|
||||
static checkTokenAndIfPluginIsConnected = async (
|
||||
onCallApiUrl: string
|
||||
): Promise<PluginSyncStatusResponse | string> => {
|
||||
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
|
||||
): Promise<PluginSyncStatusResponse | string> => {
|
||||
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<PluginSyncStatusResponse | string> => {
|
||||
/**
|
||||
* 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 <RT = CloudProvisioningConfigResponse>(
|
||||
|
|
@ -372,31 +302,13 @@ class PluginState {
|
|||
return null;
|
||||
};
|
||||
|
||||
static checkIfBackendIsInMaintenanceMode = async (
|
||||
onCallApiUrl: string,
|
||||
onCallApiUrlIsConfiguredThroughEnvVar = false
|
||||
): Promise<PluginIsInMaintenanceModeResponse | string> => {
|
||||
try {
|
||||
return await makeRequest<PluginIsInMaintenanceModeResponse>('/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<PluginConnectedStatusResponse | string> => {
|
||||
try {
|
||||
return await makeRequest<PluginConnectedStatusResponse>(`${this.ONCALL_BASE_URL}/status`, {
|
||||
method: 'GET',
|
||||
method: 'POST',
|
||||
});
|
||||
} catch (e) {
|
||||
return this.getHumanReadableErrorFromOnCallError(
|
||||
|
|
|
|||
|
|
@ -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<ReturnType<typeof makeRequestOriginal>>;
|
||||
const isNetworkError = isNetworkErrorOriginal as unknown as jest.Mock<ReturnType<typeof isNetworkErrorOriginal>>;
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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.updatePluginStatus).toHaveBeenCalledTimes(1);
|
||||
expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(onCallApiUrl);
|
||||
|
||||
expect(PluginState.syncDataWithOnCall).toHaveBeenCalledTimes(1);
|
||||
expect(PluginState.syncDataWithOnCall).toHaveBeenCalledWith(onCallApiUrl);
|
||||
|
||||
expect(rootBaseStore.appLoading).toBe(false);
|
||||
expect(rootBaseStore.initializationError).toEqual(syncDataWithOnCallError);
|
||||
expect(rootBaseStore.initializationError).toEqual(updatePluginStatusError);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue