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 08:49:14 +01:00 committed by GitHub
parent ea2b493937
commit ea12eecac5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 44 additions and 23 deletions

View File

@ -30,13 +30,24 @@ export async function getPluginDetails(id: string): Promise<CatalogPluginDetails
}
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.
// (We won't show not installed deprecated plugins in the list)
const { items: remotePlugins }: { items: RemotePlugin[] } = await getBackendSrv().get(`${GCOM_API_ROOT}/plugins`, {
includeDeprecated: true,
});
try {
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.
// (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[]> {

View File

@ -1,5 +1,5 @@
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 { 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}/fetchRemote/pending` });
const local$ = from(getLocalPlugins());
const remote$ = from(getRemotePlugins());
const instance$ =
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
? from(getInstancePlugins())
: of(undefined);
const TIMEOUT = 500;
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({
local: local$,
@ -42,20 +51,14 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t
pluginErrors: pluginErrors$,
})
.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({
each: 500,
each: TIMEOUT,
with: () => {
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
.subscribe(async (remote: RemotePlugin[]) => {
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[];
pluginErrors: PluginError[];
}) => {
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/fulfilled` });
// Both local and remote plugins are loaded
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 })));
// Only remote plugins are loaded (remote timed out)
} else if (local) {
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/fulfilled` });
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.');
}
);