diff --git a/public/app/features/manage-dashboards/DashboardImportPage.tsx b/public/app/features/manage-dashboards/DashboardImportPage.tsx index 5d2facd20d0..a8744ae3291 100644 --- a/public/app/features/manage-dashboards/DashboardImportPage.tsx +++ b/public/app/features/manage-dashboards/DashboardImportPage.tsx @@ -1,33 +1,67 @@ import React, { FormEvent, PureComponent } from 'react'; -import { MapDispatchToProps, MapStateToProps } from 'react-redux'; +import { connect, ConnectedProps } from 'react-redux'; import { css } from '@emotion/css'; -import { AppEvents, GrafanaTheme2, NavModel } from '@grafana/data'; +import { AppEvents, GrafanaTheme2, LoadingState } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { Button, stylesFactory, withTheme2, Input, TextArea, Field, Form, FileUpload, Themeable2 } from '@grafana/ui'; +import { + Button, + Field, + FileUpload, + Form, + HorizontalGroup, + Input, + Spinner, + stylesFactory, + TextArea, + Themeable2, + VerticalGroup, + withTheme2, +} from '@grafana/ui'; import Page from 'app/core/components/Page/Page'; -import { connectWithCleanUp } from 'app/core/components/connectWithCleanUp'; import { ImportDashboardOverview } from './components/ImportDashboardOverview'; import { validateDashboardJson, validateGcomDashboard } from './utils/validation'; import { fetchGcomDashboard, importDashboardJson } from './state/actions'; import appEvents from 'app/core/app_events'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { cleanUpAction } from '../../core/actions/cleanUp'; -interface OwnProps extends Themeable2 {} +type DashboardImportPageRouteSearchParams = { + gcomDashboardId?: string; +}; -interface ConnectedProps { - navModel: NavModel; - isLoaded: boolean; -} +type OwnProps = Themeable2 & GrafanaRouteComponentProps<{}, DashboardImportPageRouteSearchParams>; -interface DispatchProps { - fetchGcomDashboard: typeof fetchGcomDashboard; - importDashboardJson: typeof importDashboardJson; -} +const mapStateToProps = (state: StoreState) => ({ + navModel: getNavModel(state.navIndex, 'import', undefined, true), + loadingState: state.importDashboard.state, +}); -type Props = OwnProps & ConnectedProps & DispatchProps; +const mapDispatchToProps = { + fetchGcomDashboard, + importDashboardJson, + cleanUpAction, +}; + +const connector = connect(mapStateToProps, mapDispatchToProps); + +type Props = OwnProps & ConnectedProps; class UnthemedDashboardImport extends PureComponent { + constructor(props: Props) { + super(props); + const { gcomDashboardId } = this.props.queryParams; + if (gcomDashboardId) { + this.getGcomDashboard({ gcomDashboard: gcomDashboardId }); + return; + } + } + + componentWillUnmount() { + this.props.cleanUpAction({ stateSelector: (state: StoreState) => state.importDashboard }); + } + onFileUpload = (event: FormEvent) => { const { importDashboardJson } = this.props; const file = event.currentTarget.files && event.currentTarget.files.length > 0 && event.currentTarget.files[0]; @@ -135,36 +169,30 @@ class UnthemedDashboardImport extends PureComponent { } render() { - const { isLoaded, navModel } = this.props; + const { loadingState, navModel } = this.props; + return ( - {isLoaded ? : this.renderImportForm()} + + {loadingState === LoadingState.Loading && ( + + + + + + )} + {[LoadingState.Error, LoadingState.NotStarted].includes(loadingState) && this.renderImportForm()} + {loadingState === LoadingState.Done && } + ); } } const DashboardImportUnConnected = withTheme2(UnthemedDashboardImport); - -const mapStateToProps: MapStateToProps = (state: StoreState) => ({ - navModel: getNavModel(state.navIndex, 'import', undefined, true), - isLoaded: state.importDashboard.isLoaded, -}); - -const mapDispatchToProps: MapDispatchToProps = { - fetchGcomDashboard, - importDashboardJson, -}; - -export const DashboardImportPage = connectWithCleanUp( - mapStateToProps, - mapDispatchToProps, - (state) => state.importDashboard -)(DashboardImportUnConnected); - -export default DashboardImportPage; - -DashboardImportPage.displayName = 'DashboardImport'; +const DashboardImport = connector(DashboardImportUnConnected); +DashboardImport.displayName = 'DashboardImport'; +export default DashboardImport; const importStyles = stylesFactory((theme: GrafanaTheme2) => { return { diff --git a/public/app/features/manage-dashboards/state/actions.ts b/public/app/features/manage-dashboards/state/actions.ts index de5c8de092e..7c1f977906d 100644 --- a/public/app/features/manage-dashboards/state/actions.ts +++ b/public/app/features/manage-dashboards/state/actions.ts @@ -2,6 +2,8 @@ import { AppEvents, DataSourceInstanceSettings, locationUtil } from '@grafana/da import { getBackendSrv } from 'app/core/services/backend_srv'; import { clearDashboard, + fetchDashboard, + fetchFailed, ImportDashboardDTO, InputType, LibraryPanelInput, @@ -23,11 +25,13 @@ import { LibraryElementExport } from '../../dashboard/components/DashExportModal export function fetchGcomDashboard(id: string): ThunkResult { return async (dispatch) => { try { + dispatch(fetchDashboard()); const dashboard = await getBackendSrv().get(`/api/gnet/dashboards/${id}`); dispatch(setGcomDashboard(dashboard)); dispatch(processInputs(dashboard.json)); dispatch(processElements(dashboard.json)); } catch (error) { + dispatch(fetchFailed()); appEvents.emit(AppEvents.alertError, [error.data.message || error]); } }; diff --git a/public/app/features/manage-dashboards/state/reducers.test.ts b/public/app/features/manage-dashboards/state/reducers.test.ts index 1eb26b944aa..8d845d271f6 100644 --- a/public/app/features/manage-dashboards/state/reducers.test.ts +++ b/public/app/features/manage-dashboards/state/reducers.test.ts @@ -15,6 +15,7 @@ import { setLibraryPanelInputs, } from './reducers'; import { LibraryElementDTO } from '../../library-panels/types'; +import { LoadingState } from '@grafana/data'; describe('importDashboardReducer', () => { describe('when setGcomDashboard action is dispatched', () => { @@ -32,7 +33,7 @@ describe('importDashboardReducer', () => { }, meta: { updatedAt: '2001-01-01', orgName: 'Some Org' }, source: DashboardSource.Gcom, - isLoaded: true, + state: LoadingState.Done, }); }); }); @@ -49,7 +50,7 @@ describe('importDashboardReducer', () => { id: null, }, source: DashboardSource.Json, - isLoaded: true, + state: LoadingState.Done, }); }); }); @@ -63,13 +64,13 @@ describe('importDashboardReducer', () => { title: 'Imported', id: null, }, - isLoaded: true, + state: LoadingState.Done, }) .whenActionIsDispatched(clearDashboard()) .thenStateShouldEqual({ ...initialImportDashboardState, dashboard: {}, - isLoaded: false, + state: LoadingState.NotStarted, }); }); }); diff --git a/public/app/features/manage-dashboards/state/reducers.ts b/public/app/features/manage-dashboards/state/reducers.ts index d4dd557a2ee..89940277dd1 100644 --- a/public/app/features/manage-dashboards/state/reducers.ts +++ b/public/app/features/manage-dashboards/state/reducers.ts @@ -1,5 +1,5 @@ import { createSlice, Draft, PayloadAction } from '@reduxjs/toolkit'; -import { DataSourceInstanceSettings } from '@grafana/data'; +import { DataSourceInstanceSettings, LoadingState } from '@grafana/data'; import { LibraryElementDTO } from '../../library-panels/types'; export enum DashboardSource { @@ -57,7 +57,7 @@ export interface ImportDashboardState { dashboard: any; source: DashboardSource; inputs: DashboardInputs; - isLoaded: boolean; + state: LoadingState; } export const initialImportDashboardState: ImportDashboardState = { @@ -65,7 +65,7 @@ export const initialImportDashboardState: ImportDashboardState = { dashboard: {}, source: DashboardSource.Json, inputs: {} as DashboardInputs, - isLoaded: false, + state: LoadingState.NotStarted, }; const importDashboardSlice = createSlice({ @@ -79,7 +79,7 @@ const importDashboardSlice = createSlice({ }; state.meta = { updatedAt: action.payload.updatedAt, orgName: action.payload.orgName }; state.source = DashboardSource.Gcom; - state.isLoaded = true; + state.state = LoadingState.Done; }, setJsonDashboard: (state: Draft, action: PayloadAction) => { state.dashboard = { @@ -88,11 +88,11 @@ const importDashboardSlice = createSlice({ }; state.meta = initialImportDashboardState.meta; state.source = DashboardSource.Json; - state.isLoaded = true; + state.state = LoadingState.Done; }, clearDashboard: (state: Draft) => { state.dashboard = {}; - state.isLoaded = false; + state.state = LoadingState.NotStarted; }, setInputs: (state: Draft, action: PayloadAction) => { state.inputs = { @@ -104,6 +104,13 @@ const importDashboardSlice = createSlice({ setLibraryPanelInputs: (state: Draft, action: PayloadAction) => { state.inputs.libraryPanels = action.payload; }, + fetchFailed: (state: Draft) => { + state.dashboard = {}; + state.state = LoadingState.Error; + }, + fetchDashboard: (state: Draft) => { + state.state = LoadingState.Loading; + }, }, }); @@ -113,6 +120,8 @@ export const { setGcomDashboard, setJsonDashboard, setLibraryPanelInputs, + fetchFailed, + fetchDashboard, } = importDashboardSlice.actions; export const importDashboardReducer = importDashboardSlice.reducer;