Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove checks that slow down plugin load and cause "Initializing plugin..." #2624

Merged
merged 16 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions engine/apps/auth_token/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ def authenticate_credentials(self, token_string: str, request: Request) -> Tuple
except InvalidToken:
raise exceptions.AuthenticationFailed("Invalid token.")

# TODO: decide if we want to add this check instead of doing in plugin sync
iskhakov marked this conversation as resolved.
Show resolved Hide resolved
if auth_token.organization.api_token_status == Organization.API_TOKEN_STATUS_FAILED:
raise exceptions.AuthenticationFailed("Invalid token.")
if auth_token.organization.api_token_status != Organization.API_TOKEN_STATUS_OK:
raise exceptions.AuthenticationFailed("Invalid token.")

user = self._get_user(request, auth_token.organization)
return user, auth_token

Expand Down
62 changes: 62 additions & 0 deletions engine/apps/grafana_plugin/views/status.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,81 @@
from django.apps import apps
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.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


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
# _, resp = GrafanaAPIClient(api_url=organization.grafana_url, api_token=organization.api_token).check_token()
# token_ok = resp["connected"]
# TODO: Consider checking from OnCall db instead of Grafana to make it faster:
token_ok = organization.api_token_status == Organization.API_TOKEN_STATUS_OK
else:
DynamicSetting = apps.get_model("base", "DynamicSetting")
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

# Start task to refresh organization data in OnCall database with Grafana
plugin_sync_organization_async.apply_async((organization.pk,))
iskhakov marked this conversation as resolved.
Show resolved Hide resolved

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.5"""
stack_id = self.instance_context["stack_id"]
org_id = self.instance_context["org_id"]
is_installed = False
Expand Down
3 changes: 3 additions & 0 deletions engine/apps/grafana_plugin/views/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def post(self, request: Request) -> Response:
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
Expand Down Expand Up @@ -56,6 +58,7 @@ def post(self, request: Request) -> Response:
)

def get(self, _request: Request) -> Response:
"""Deprecated"""
stack_id = self.instance_context["stack_id"]
org_id = self.instance_context["org_id"]
token_ok = False
Expand Down
9 changes: 1 addition & 8 deletions grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';

import classnames from 'classnames';
import dayjs from 'dayjs';
Expand Down Expand Up @@ -71,8 +71,6 @@ export const GrafanaPluginRootPage = (props: AppRootProps) => {
};

export const Root = observer((props: AppRootProps) => {
const [didFinishLoading, setDidFinishLoading] = useState(false);

const store = useStore();

useEffect(() => {
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 0 additions & 6 deletions grafana-plugin/src/plugin/PluginSetup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}>
Expand All @@ -62,7 +57,6 @@ const PluginSetup: FC<PluginSetupProps> = observer(({ InitializedComponent, ...p
</PluginSetupWrapper>
);
}

return <InitializedComponent {...props} />;
});

Expand Down
4 changes: 3 additions & 1 deletion grafana-plugin/src/state/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,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 & {
Expand Down Expand Up @@ -396,7 +398,7 @@ class PluginState {
): Promise<PluginConnectedStatusResponse | string> => {
try {
return await makeRequest<PluginConnectedStatusResponse>(`${this.ONCALL_BASE_URL}/status`, {
method: 'GET',
method: 'POST',
});
} catch (e) {
return this.getHumanReadableErrorFromOnCallError(
Expand Down
38 changes: 13 additions & 25 deletions grafana-plugin/src/state/rootBaseStore/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ import { APP_VERSION, CLOUD_VERSION_REGEX, GRAFANA_LICENSE_CLOUD, GRAFANA_LICENS
// ------ Dashboard ------ //

export class RootBaseStore {
@observable
appLoading = true;

@observable
currentTimezone: Timezone = moment.tz.guess() as Timezone;

Expand Down Expand Up @@ -130,7 +127,6 @@ export class RootBaseStore {
}

setupPluginError(errorMsg: string) {
this.appLoading = false;
this.initializationError = errorMsg;
}

Expand All @@ -150,7 +146,6 @@ export class RootBaseStore {
* 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;

Expand All @@ -159,27 +154,28 @@ export class RootBaseStore {
return this.setupPluginError('🚫 Plugin has not been initialized');
}

const maintenanceMode = await PluginState.checkIfBackendIsInMaintenanceMode(this.onCallApiUrl);
iskhakov marked this conversation as resolved.
Show resolved Hide resolved
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);
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} 🚧`);
}

iskhakov marked this conversation as resolved.
Show resolved Hide resolved
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.');
}
Expand Down Expand Up @@ -211,25 +207,17 @@ export class RootBaseStore {
}
}
} else {
const syncDataResponse = await PluginState.syncDataWithOnCall(this.onCallApiUrl);
iskhakov marked this conversation as resolved.
Show resolved Hide resolved

if (typeof syncDataResponse === 'string') {
return this.setupPluginError(syncDataResponse);
}

iskhakov marked this conversation as resolved.
Show resolved Hide resolved
// 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');
}

this.appLoading = false;
}

checkMissingSetupPermissions() {
Expand Down