mirror of
https://github.com/grafana/grafana.git
synced 2025-01-02 04:07:15 -06:00
Connections: Make the "Add new Connection" page work without internet access (#75272)
* fix: make the "Add new Connection" page work without internet access * feat: update error message * fix: update the API url in the error log
This commit is contained in:
parent
b374912937
commit
163b0d182b
@ -39,7 +39,7 @@ export function AddNewConnection() {
|
||||
setSearchTerm(e.currentTarget.value.toLowerCase());
|
||||
};
|
||||
|
||||
const { isLoading, error, plugins } = useGetAll({
|
||||
const { error, plugins, isLoading } = useGetAll({
|
||||
keyword: searchTerm,
|
||||
type: PluginType.datasource,
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { createAction, createAsyncThunk, Update } from '@reduxjs/toolkit';
|
||||
import { from, forkJoin, timeout, lastValueFrom, catchError, throwError } from 'rxjs';
|
||||
|
||||
import { PanelPlugin } from '@grafana/data';
|
||||
import { PanelPlugin, PluginError } from '@grafana/data';
|
||||
import { getBackendSrv, isFetchError } from '@grafana/runtime';
|
||||
import { importPanelPlugin } from 'app/features/plugins/importPanelPlugin';
|
||||
import { StoreState, ThunkResult } from 'app/types';
|
||||
@ -18,16 +19,76 @@ import { STATE_PREFIX } from '../constants';
|
||||
import { mapLocalToCatalog, mergeLocalsAndRemotes, updatePanels } from '../helpers';
|
||||
import { CatalogPlugin, RemotePlugin, LocalPlugin } from '../types';
|
||||
|
||||
// Fetches
|
||||
export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, thunkApi) => {
|
||||
try {
|
||||
const { dispatch } = thunkApi;
|
||||
const [localPlugins, pluginErrors, { payload: remotePlugins }] = await Promise.all([
|
||||
getLocalPlugins(),
|
||||
getPluginErrors(),
|
||||
dispatch(fetchRemotePlugins()),
|
||||
]);
|
||||
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/pending` });
|
||||
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/pending` });
|
||||
|
||||
return mergeLocalsAndRemotes(localPlugins, remotePlugins, pluginErrors);
|
||||
const local$ = from(getLocalPlugins());
|
||||
const remote$ = from(getRemotePlugins());
|
||||
const pluginErrors$ = from(getPluginErrors());
|
||||
|
||||
forkJoin({
|
||||
local: local$,
|
||||
remote: remote$,
|
||||
pluginErrors: pluginErrors$,
|
||||
})
|
||||
.pipe(
|
||||
// Fetching the list of plugins from GCOM is slow / errors out
|
||||
timeout({
|
||||
each: 500,
|
||||
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` });
|
||||
|
||||
if (remote.length > 0) {
|
||||
const local = await lastValueFrom(local$);
|
||||
const pluginErrors = await lastValueFrom(pluginErrors$);
|
||||
|
||||
thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes(local, remote, pluginErrors)));
|
||||
}
|
||||
});
|
||||
|
||||
return forkJoin({ local: local$, pluginErrors: pluginErrors$ });
|
||||
},
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
({
|
||||
local,
|
||||
remote,
|
||||
pluginErrors,
|
||||
}: {
|
||||
local: LocalPlugin[];
|
||||
remote?: RemotePlugin[];
|
||||
pluginErrors: PluginError[];
|
||||
}) => {
|
||||
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/fulfilled` });
|
||||
|
||||
// Both local and remote plugins are loaded
|
||||
if (local && remote) {
|
||||
thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes(local, remote, pluginErrors)));
|
||||
|
||||
// Only remote plugins are loaded (remote timed out)
|
||||
} else if (local) {
|
||||
thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes(local, [], pluginErrors)));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
return thunkApi.rejectWithValue('Unknown error.');
|
||||
}
|
||||
@ -72,6 +133,24 @@ export const fetchDetails = createAsyncThunk<Update<CatalogPlugin>, string>(
|
||||
}
|
||||
);
|
||||
|
||||
export const addPlugins = createAction<CatalogPlugin[]>(`${STATE_PREFIX}/addPlugins`);
|
||||
|
||||
// 1. gets remote equivalents from the store (if there are any)
|
||||
// 2. merges the remote equivalents with the local plugins
|
||||
// 3. updates the the store with the updated CatalogPlugin objects
|
||||
export const addLocalPlugins = createAction<LocalPlugin[]>(`${STATE_PREFIX}/addLocalPlugins`);
|
||||
|
||||
// 1. gets local equivalents from the store (if there are any)
|
||||
// 2. merges the local equivalents with the remote plugins
|
||||
// 3. updates the the store with the updated CatalogPlugin objects
|
||||
export const addRemotePlugins = createAction<RemotePlugin[]>(`${STATE_PREFIX}/addLocalPlugins`);
|
||||
|
||||
// 1. merges the local and remote plugins
|
||||
// 2. updates the store with the CatalogPlugin objects
|
||||
export const addLocalAndRemotePlugins = createAction<{ local: LocalPlugin[]; remote: RemotePlugin[] }>(
|
||||
`${STATE_PREFIX}/addLocalPlugins`
|
||||
);
|
||||
|
||||
// We are also using the install API endpoint to update the plugin
|
||||
export const install = createAsyncThunk<
|
||||
Update<CatalogPlugin>,
|
||||
|
@ -24,7 +24,9 @@ export const useGetAll = (filters: PluginFilters, sortBy: Sorters = Sorters.name
|
||||
|
||||
const selector = useMemo(() => selectPlugins(filters), [filters]);
|
||||
const plugins = useSelector(selector);
|
||||
const { isLoading, error } = useFetchStatus();
|
||||
// As the locally installed plugins load quicker than the remote ones, we only show a loading state until these are being loaded
|
||||
// (In case the remote ones are not loaded within a reasonable timeout, we will merge those with the locally installed plugins once they are loaded)
|
||||
const { isLoading, error } = useLocalFetchStatus();
|
||||
const sortedPlugins = sortPlugins(plugins, sortBy);
|
||||
|
||||
return {
|
||||
@ -74,6 +76,13 @@ export const useIsRemotePluginsAvailable = () => {
|
||||
return error === null;
|
||||
};
|
||||
|
||||
export const useLocalFetchStatus = () => {
|
||||
const isLoading = useSelector(selectIsRequestPending('plugins/fetchLocal'));
|
||||
const error = useSelector(selectRequestError('plugins/fetchLocal'));
|
||||
|
||||
return { isLoading, error };
|
||||
};
|
||||
|
||||
export const useFetchStatus = () => {
|
||||
const isLoading = useSelector(selectIsRequestPending(fetchAll.typePrefix));
|
||||
const error = useSelector(selectRequestError(fetchAll.typePrefix));
|
||||
|
@ -6,13 +6,13 @@ import { STATE_PREFIX } from '../constants';
|
||||
import { CatalogPlugin, PluginListDisplayMode, ReducerState, RequestStatus } from '../types';
|
||||
|
||||
import {
|
||||
fetchAll,
|
||||
fetchDetails,
|
||||
install,
|
||||
uninstall,
|
||||
loadPluginDashboards,
|
||||
panelPluginLoaded,
|
||||
fetchAllLocal,
|
||||
addPlugins,
|
||||
} from './actions';
|
||||
|
||||
export const pluginsAdapter = createEntityAdapter<CatalogPlugin>();
|
||||
@ -58,8 +58,7 @@ const slice = createSlice({
|
||||
},
|
||||
extraReducers: (builder) =>
|
||||
builder
|
||||
// Fetch All
|
||||
.addCase(fetchAll.fulfilled, (state, action) => {
|
||||
.addCase(addPlugins, (state, action: PayloadAction<CatalogPlugin[]>) => {
|
||||
pluginsAdapter.upsertMany(state.items, action.payload);
|
||||
})
|
||||
// Fetch All local
|
||||
|
Loading…
Reference in New Issue
Block a user