import { createSlice, createEntityAdapter, Reducer, AnyAction, PayloadAction } from '@reduxjs/toolkit'; import { PanelPlugin } from '@grafana/data'; import { STATE_PREFIX } from '../constants'; import { CatalogPlugin, PluginListDisplayMode, ReducerState, RequestStatus } from '../types'; import { fetchAll, fetchDetails, install, uninstall, loadPluginDashboards, panelPluginLoaded } from './actions'; export const pluginsAdapter = createEntityAdapter(); const isPendingRequest = (action: AnyAction) => new RegExp(`${STATE_PREFIX}\/(.*)\/pending`).test(action.type); const isFulfilledRequest = (action: AnyAction) => new RegExp(`${STATE_PREFIX}\/(.*)\/fulfilled`).test(action.type); const isRejectedRequest = (action: AnyAction) => new RegExp(`${STATE_PREFIX}\/(.*)\/rejected`).test(action.type); // Extract the trailing '/pending', '/rejected', or '/fulfilled' const getOriginalActionType = (type: string) => { const separator = type.lastIndexOf('/'); return type.substring(0, separator); }; const slice = createSlice({ name: 'plugins', initialState: { items: pluginsAdapter.getInitialState(), requests: {}, settings: { displayMode: PluginListDisplayMode.Grid, }, // Backwards compatibility // (we need to have the following fields in the store as well to be backwards compatible with other parts of Grafana) // TODO plugins: [], errors: [], searchQuery: '', hasFetched: false, dashboards: [], isLoadingPluginDashboards: false, panels: {}, } as ReducerState, reducers: { setDisplayMode(state, action: PayloadAction) { state.settings.displayMode = action.payload; }, }, extraReducers: (builder) => builder // Fetch All .addCase(fetchAll.fulfilled, (state, action) => { pluginsAdapter.upsertMany(state.items, action.payload); }) // Fetch Details .addCase(fetchDetails.fulfilled, (state, action) => { pluginsAdapter.updateOne(state.items, action.payload); }) // Install .addCase(install.fulfilled, (state, action) => { pluginsAdapter.updateOne(state.items, action.payload); }) // Uninstall .addCase(uninstall.fulfilled, (state, action) => { pluginsAdapter.updateOne(state.items, action.payload); }) // Load a panel plugin (backward-compatibility) // TODO .addCase(panelPluginLoaded, (state, action: PayloadAction) => { state.panels[action.payload.meta.id] = action.payload; }) // Start loading panel dashboards (backward-compatibility) // TODO .addCase(loadPluginDashboards.pending, (state, action) => { state.isLoadingPluginDashboards = true; state.dashboards = []; }) // Load panel dashboards (backward-compatibility) // TODO .addCase(loadPluginDashboards.fulfilled, (state, action) => { state.isLoadingPluginDashboards = false; state.dashboards = action.payload; }) .addMatcher(isPendingRequest, (state, action) => { state.requests[getOriginalActionType(action.type)] = { status: RequestStatus.Pending, }; }) .addMatcher(isFulfilledRequest, (state, action) => { state.requests[getOriginalActionType(action.type)] = { status: RequestStatus.Fulfilled, }; }) .addMatcher(isRejectedRequest, (state, action) => { state.requests[getOriginalActionType(action.type)] = { status: RequestStatus.Rejected, error: action.payload, }; }), }); export const { setDisplayMode } = slice.actions; export const reducer: Reducer = slice.reducer;