Plugins: Keep working when there is no internet access (#77978)

* fix: make connections and plugins-catalog work when GCOM is not available

* fix: remove unused import
This commit is contained in:
Levente Balogh
2023-11-14 09:49:14 +02:00
committed by GitHub
parent ea2b493937
commit ea12eecac5
2 changed files with 44 additions and 23 deletions
+17 -6
View File
@@ -30,13 +30,24 @@ export async function getPluginDetails(id: string): Promise<CatalogPluginDetails
} }
export async function getRemotePlugins(): Promise<RemotePlugin[]> { export async function getRemotePlugins(): Promise<RemotePlugin[]> {
// We are also fetching deprecated plugins, because we would like to be able to label plugins in the list that are both installed and deprecated. try {
// (We won't show not installed deprecated plugins in the list) const { items: remotePlugins }: { items: RemotePlugin[] } = await getBackendSrv().get(`${GCOM_API_ROOT}/plugins`, {
const { items: remotePlugins }: { items: RemotePlugin[] } = await getBackendSrv().get(`${GCOM_API_ROOT}/plugins`, { // We are also fetching deprecated plugins, because we would like to be able to label plugins in the list that are both installed and deprecated.
includeDeprecated: true, // (We won't show not installed deprecated plugins in the list)
}); includeDeprecated: true,
});
return remotePlugins.filter(isRemotePluginVisibleByConfig); return remotePlugins.filter(isRemotePluginVisibleByConfig);
} catch (error) {
if (isFetchError(error)) {
// It can happen that GCOM is not available, in that case we show a limited set of information to the user.
error.isHandled = true;
console.error('Failed to fetch plugins from catalog (default https://grafana.com/api/plugins)');
return [];
}
throw error;
}
} }
export async function getPluginErrors(): Promise<PluginError[]> { export async function getPluginErrors(): Promise<PluginError[]> {
@@ -1,5 +1,5 @@
import { createAction, createAsyncThunk, Update } from '@reduxjs/toolkit'; import { createAction, createAsyncThunk, Update } from '@reduxjs/toolkit';
import { from, forkJoin, timeout, lastValueFrom, catchError, throwError, of } from 'rxjs'; import { from, forkJoin, timeout, lastValueFrom, catchError, of } from 'rxjs';
import { PanelPlugin, PluginError } from '@grafana/data'; import { PanelPlugin, PluginError } from '@grafana/data';
import { config, getBackendSrv, isFetchError } from '@grafana/runtime'; import { config, getBackendSrv, isFetchError } from '@grafana/runtime';
@@ -27,13 +27,22 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/pending` }); thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/pending` });
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/pending` }); thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/pending` });
const local$ = from(getLocalPlugins());
const remote$ = from(getRemotePlugins());
const instance$ = const instance$ =
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
? from(getInstancePlugins()) ? from(getInstancePlugins())
: of(undefined); : of(undefined);
const TIMEOUT = 500;
const pluginErrors$ = from(getPluginErrors()); const pluginErrors$ = from(getPluginErrors());
const local$ = from(getLocalPlugins());
// Unknown error while fetching remote plugins from GCOM.
// (In this case we still operate, but only with locally available plugins.)
const remote$ = from(getRemotePlugins()).pipe(
catchError((err) => {
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/rejected` });
console.error(err);
return of([]);
})
);
forkJoin({ forkJoin({
local: local$, local: local$,
@@ -42,20 +51,14 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t
pluginErrors: pluginErrors$, pluginErrors: pluginErrors$,
}) })
.pipe( .pipe(
// Fetching the list of plugins from GCOM is slow / errors out // Fetching the list of plugins from GCOM is slow / times out
// (We are waiting for TIMEOUT, and if there is still no response from GCOM we continue with locally
// installed plugins only by returning a new observable. We also still wait for the remote request to finish or
// time out, but we don't block the main execution flow.)
timeout({ timeout({
each: 500, each: TIMEOUT,
with: () => { with: () => {
remote$ remote$
// The request to fetch remote plugins from GCOM failed
.pipe(
catchError((err) => {
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/rejected` });
return throwError(
() => new Error('Failed to fetch plugins from catalog (default https://grafana.com/api/plugins)')
);
})
)
// Remote plugins loaded after a timeout, updating the store // Remote plugins loaded after a timeout, updating the store
.subscribe(async (remote: RemotePlugin[]) => { .subscribe(async (remote: RemotePlugin[]) => {
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/fulfilled` }); thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/fulfilled` });
@@ -69,7 +72,7 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t
} }
}); });
return forkJoin({ local: local$, pluginErrors: pluginErrors$ }); return forkJoin({ local: local$, instance: instance$, pluginErrors: pluginErrors$ });
}, },
}) })
) )
@@ -85,16 +88,23 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t
instance?: InstancePlugin[]; instance?: InstancePlugin[];
pluginErrors: PluginError[]; pluginErrors: PluginError[];
}) => { }) => {
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/fulfilled` });
// Both local and remote plugins are loaded // Both local and remote plugins are loaded
if (local && remote) { if (local && remote) {
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/fulfilled` });
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/fulfilled` });
thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes({ local, remote, instance, pluginErrors }))); thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes({ local, remote, instance, pluginErrors })));
// Only remote plugins are loaded (remote timed out) // Only remote plugins are loaded (remote timed out)
} else if (local) { } else if (local) {
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/fulfilled` });
thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes({ local, pluginErrors }))); thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes({ local, pluginErrors })));
} }
},
(error) => {
console.log(error);
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/rejected` });
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/rejected` });
return thunkApi.rejectWithValue('Unknown error.');
} }
); );