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:
@@ -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.');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user