From d066da42f86db2d889a8d95ffb7572120c898a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 17 Dec 2020 15:50:11 +0100 Subject: [PATCH] Dashboard: Migrating dashboard settings to react (#27561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Dashboard: Started migrating dashboard settings * Restore general settings from angular * Use react permissions component * feat(dashboard): add react LinksSettings wrapper for dash-links-editor * feat(dashboard): add react VersionsSettings wrapper for gf-dashboard-history * refactor(dashboard): replace DashboardPermissions connectWithStore with connect * chore(dashboard): folderInfo as undefined * feat(dashboard): initial commit of dashboard settings json editor * feat(dashboard): introduce save json functionality * chore(dashboard): delete obsolete imports * feat(dashboard): add save and save as buttons to settings nav * feat(dashboard): add react wrapper for annotations settings * chore(dashboard): put back canDelete for general settings delete button * Make editable * Remove makeEditable from SettingsCtrl * feat(dashboard): show json editor save button if canSave * refactor(dashboard): move hasUnsavedFolderChange to dashboard.meta * feat(dashboard): render hasUnsavedFolderChange view in permissions settings * feat(dashboard): reset hasUnsavedFolderChange on settingg save success * feat(dashboard): refresh route on sucessful settings save * test(dashboard): update snapshots * refactor(dashboard): automatically infer connected props for dashboard permissions * refactor(dashboard): give dashboard versions checkboxes some padding * Update public/app/types/folders.ts Co-authored-by: Hugo Häggmark Co-authored-by: Jack Westbrook Co-authored-by: Hugo Häggmark --- packages/grafana-ui/src/types/icon.ts | 2 + .../grafana-ui/src/utils/standardEditors.tsx | 1 - public/app/core/angular_wrappers.ts | 2 - .../DashboardPermissions.tsx | 184 +++++++++-------- .../DashboardSettings/AnnotationsSettings.tsx | 30 +++ .../DashboardSettings/DashboardSettings.tsx | 183 ++++++++++++++--- .../DashboardSettings/GeneralSettings.tsx | 30 +++ .../DashboardSettings/JsonEditorSettings.tsx | 54 +++++ .../DashboardSettings/LinksSettings.tsx | 30 +++ .../DashboardSettings/SettingsCtrl.ts | 173 +--------------- .../DashboardSettings/VersionsSettings.tsx | 30 +++ .../DashboardSettings/template.html | 189 +++++------------- .../components/VersionHistory/template.html | 1 - .../dashboard/containers/DashboardPage.tsx | 2 +- .../__snapshots__/DashboardPage.test.tsx.snap | 8 + .../__snapshots__/DashboardGrid.test.tsx.snap | 4 + public/app/features/dashboard/index.ts | 2 +- .../dashboard/state/DashboardModel.ts | 1 + .../editor/VariableEditorContainer.tsx | 9 +- public/app/types/dashboard.ts | 1 + public/app/types/folders.ts | 6 +- .../sass/components/_dashboard_settings.scss | 7 +- 22 files changed, 512 insertions(+), 437 deletions(-) create mode 100644 public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx create mode 100644 public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx create mode 100644 public/app/features/dashboard/components/DashboardSettings/JsonEditorSettings.tsx create mode 100644 public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx create mode 100644 public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx diff --git a/packages/grafana-ui/src/types/icon.ts b/packages/grafana-ui/src/types/icon.ts index 801a8b13705..6d8cfecc9a9 100644 --- a/packages/grafana-ui/src/types/icon.ts +++ b/packages/grafana-ui/src/types/icon.ts @@ -111,6 +111,7 @@ export type IconName = | 'search-plus' | 'minus-circle' | 'table' + | 'arrow' | 'plus' | 'heart' | 'heart-break' @@ -229,6 +230,7 @@ export const getAvailableIcons = (): IconName[] => [ 'signal', 'search-plus', 'minus-circle', + 'arrow', 'table', 'plus', 'heart', diff --git a/packages/grafana-ui/src/utils/standardEditors.tsx b/packages/grafana-ui/src/utils/standardEditors.tsx index 3288aef7cdb..e22df9c2972 100644 --- a/packages/grafana-ui/src/utils/standardEditors.tsx +++ b/packages/grafana-ui/src/utils/standardEditors.tsx @@ -139,7 +139,6 @@ export const getStandardFieldConfigs = () => { id: 'thresholds', path: 'thresholds', name: 'Thresholds', - editor: standardEditorsRegistry.get('thresholds').editor as any, override: standardEditorsRegistry.get('thresholds').editor as any, process: thresholdsOverrideProcessor, diff --git a/public/app/core/angular_wrappers.ts b/public/app/core/angular_wrappers.ts index 2a596be7cc4..282f994de3b 100644 --- a/public/app/core/angular_wrappers.ts +++ b/public/app/core/angular_wrappers.ts @@ -28,7 +28,6 @@ import { SaveDashboardAsButtonConnected, SaveDashboardButtonConnected, } from '../features/dashboard/components/SaveDashboard/SaveDashboardButton'; -import { VariableEditorContainer } from '../features/variables/editor/VariableEditorContainer'; import { SearchField, SearchResults, SearchResultsFilter, SearchWrapper } from '../features/search'; import { TimePickerSettings } from 'app/features/dashboard/components/DashboardSettings/TimePickerSettings'; @@ -200,7 +199,6 @@ export function registerAngularDirectives() { ['getDashboard', { watchDepth: 'reference', wrapApply: true }], ['onSaveSuccess', { watchDepth: 'reference', wrapApply: true }], ]); - react2AngularDirective('variableEditorContainer', VariableEditorContainer, []); react2AngularDirective('timePickerSettings', TimePickerSettings, [ 'renderCount', 'refreshIntervals', diff --git a/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx b/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx index 427a5ef2f8a..91419accac6 100644 --- a/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx +++ b/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx @@ -1,7 +1,8 @@ import React, { PureComponent } from 'react'; -import { Tooltip, Icon } from '@grafana/ui'; +import { connect, ConnectedProps } from 'react-redux'; +import { Tooltip, Icon, Button } from '@grafana/ui'; import { SlideDown } from 'app/core/components/Animations/SlideDown'; -import { StoreState, FolderInfo } from 'app/types'; +import { StoreState } from 'app/types'; import { DashboardAcl, PermissionLevel, NewDashboardAclItem } from 'app/types/acl'; import { getDashboardPermissions, @@ -9,90 +10,10 @@ import { removeDashboardPermission, updateDashboardPermission, } from '../../state/actions'; +import { DashboardModel } from '../../state/DashboardModel'; import PermissionList from 'app/core/components/PermissionList/PermissionList'; import AddPermission from 'app/core/components/PermissionList/AddPermission'; import PermissionsInfo from 'app/core/components/PermissionList/PermissionsInfo'; -import { connectWithStore } from 'app/core/utils/connectWithReduxStore'; - -export interface Props { - dashboardId: number; - folder?: FolderInfo; - permissions: DashboardAcl[]; - getDashboardPermissions: typeof getDashboardPermissions; - updateDashboardPermission: typeof updateDashboardPermission; - removeDashboardPermission: typeof removeDashboardPermission; - addDashboardPermission: typeof addDashboardPermission; -} - -export interface State { - isAdding: boolean; -} - -export class DashboardPermissions extends PureComponent { - constructor(props: Props) { - super(props); - - this.state = { - isAdding: false, - }; - } - - componentDidMount() { - this.props.getDashboardPermissions(this.props.dashboardId); - } - - onOpenAddPermissions = () => { - this.setState({ isAdding: true }); - }; - - onRemoveItem = (item: DashboardAcl) => { - this.props.removeDashboardPermission(this.props.dashboardId, item); - }; - - onPermissionChanged = (item: DashboardAcl, level: PermissionLevel) => { - this.props.updateDashboardPermission(this.props.dashboardId, item, level); - }; - - onAddPermission = (newItem: NewDashboardAclItem) => { - return this.props.addDashboardPermission(this.props.dashboardId, newItem); - }; - - onCancelAddPermission = () => { - this.setState({ isAdding: false }); - }; - - render() { - const { permissions, folder } = this.props; - const { isAdding } = this.state; - - return ( -
-
-
-

Permissions

- }> - - -
- -
-
- - - - -
- ); - } -} const mapStateToProps = (state: StoreState) => ({ permissions: state.dashboard.permissions, @@ -105,4 +26,99 @@ const mapDispatchToProps = { updateDashboardPermission, }; -export default connectWithStore(DashboardPermissions, mapStateToProps, mapDispatchToProps); +const connector = connect(mapStateToProps, mapDispatchToProps); + +export interface OwnProps { + dashboard: DashboardModel; +} + +export type Props = OwnProps & ConnectedProps; + +export interface State { + isAdding: boolean; +} + +export class DashboardPermissionsUnconnected extends PureComponent { + constructor(props: Props) { + super(props); + + this.state = { + isAdding: false, + }; + } + + componentDidMount() { + this.props.getDashboardPermissions(this.props.dashboard.id); + } + + onOpenAddPermissions = () => { + this.setState({ isAdding: true }); + }; + + onRemoveItem = (item: DashboardAcl) => { + this.props.removeDashboardPermission(this.props.dashboard.id, item); + }; + + onPermissionChanged = (item: DashboardAcl, level: PermissionLevel) => { + this.props.updateDashboardPermission(this.props.dashboard.id, item, level); + }; + + onAddPermission = (newItem: NewDashboardAclItem) => { + return this.props.addDashboardPermission(this.props.dashboard.id, newItem); + }; + + onCancelAddPermission = () => { + this.setState({ isAdding: false }); + }; + + getFolder() { + const { dashboard } = this.props; + + return { + id: dashboard.meta.folderId, + title: dashboard.meta.folderTitle, + url: dashboard.meta.folderUrl, + }; + } + + render() { + const { + permissions, + dashboard: { + meta: { hasUnsavedFolderChange }, + }, + } = this.props; + const { isAdding } = this.state; + + return hasUnsavedFolderChange ? ( +
You have changed folder, please save to view permissions.
+ ) : ( +
+
+
+

Permissions

+ }> + + +
+ +
+
+ + + + +
+ ); + } +} + +export const DashboardPermissions = connector(DashboardPermissionsUnconnected); diff --git a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx new file mode 100644 index 00000000000..06d6aa0903a --- /dev/null +++ b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx @@ -0,0 +1,30 @@ +import React, { PureComponent } from 'react'; +import { DashboardModel } from '../../state/DashboardModel'; +import { AngularComponent, getAngularLoader } from '@grafana/runtime'; + +interface Props { + dashboard: DashboardModel; +} + +export class AnnotationsSettings extends PureComponent { + element?: HTMLElement | null; + angularCmp?: AngularComponent; + + componentDidMount() { + const loader = getAngularLoader(); + + const template = '
'; + const scopeProps = { dashboard: this.props.dashboard }; + this.angularCmp = loader.load(this.element, scopeProps, template); + } + + componentWillUnmount() { + if (this.angularCmp) { + this.angularCmp.destroy(); + } + } + + render() { + return
(this.element = ref)} />; + } +} diff --git a/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx index 8607b8d9341..ad76668f9e6 100644 --- a/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx @@ -1,39 +1,27 @@ -// Libaries import React, { PureComponent } from 'react'; - -// Utils & Services -import { AngularComponent, getAngularLoader } from '@grafana/runtime'; - -// Types -import { DashboardModel } from '../../state/DashboardModel'; +import { cx } from 'emotion'; +import { selectors } from '@grafana/e2e-selectors'; +import { Button, CustomScrollbar, Icon, IconName } from '@grafana/ui'; +import { contextSrv } from 'app/core/services/context_srv'; import { BackButton } from 'app/core/components/BackButton/BackButton'; +import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { updateLocation } from 'app/core/actions'; -import { CustomScrollbar } from '@grafana/ui'; - +import { DashboardModel } from '../../state/DashboardModel'; +import { SaveDashboardButton, SaveDashboardAsButton } from '../SaveDashboard/SaveDashboardButton'; +import { VariableEditorContainer } from '../../../variables/editor/VariableEditorContainer'; +import { DashboardPermissions } from '../DashboardPermissions/DashboardPermissions'; +import { GeneralSettings } from './GeneralSettings'; +import { AnnotationsSettings } from './AnnotationsSettings'; +import { LinksSettings } from './LinksSettings'; +import { VersionsSettings } from './VersionsSettings'; +import { JsonEditorSettings } from './JsonEditorSettings'; export interface Props { dashboard: DashboardModel; updateLocation: typeof updateLocation; + editview: string; } export class DashboardSettings extends PureComponent { - element?: HTMLElement | null; - angularCmp: AngularComponent; - - componentDidMount() { - const loader = getAngularLoader(); - - const template = ''; - const scopeProps = { dashboard: this.props.dashboard }; - - this.angularCmp = loader.load(this.element, scopeProps, template); - } - - componentWillUnmount() { - if (this.angularCmp) { - this.angularCmp.destroy(); - } - } - onClose = () => { this.props.updateLocation({ query: { editview: null }, @@ -41,10 +29,119 @@ export class DashboardSettings extends PureComponent { }); }; - render() { + onChangePage = (id: string) => { + this.props.updateLocation({ + query: { editview: id }, + partial: true, + }); + }; + + getPages(): SettingsPage[] { const { dashboard } = this.props; + const pages: SettingsPage[] = []; + + if (dashboard.meta.canEdit) { + pages.push(this.getGeneralPage()); + + pages.push({ + title: 'Annotations', + id: 'annotations', + icon: 'comment-alt', + render: () => , + }); + + pages.push({ + title: 'Variables', + id: 'templating', + icon: 'calculator-alt', + render: () => , + }); + + pages.push({ + title: 'Links', + id: 'links', + icon: 'link', + render: () => , + }); + } + + if (dashboard.meta.canMakeEditable) { + pages.push({ + title: 'General', + icon: 'sliders-v-alt', + id: 'settings', + render: () => this.renderMakeEditable(), + }); + } + + if (dashboard.id && dashboard.meta.canSave) { + pages.push({ + title: 'Versions', + id: 'versions', + icon: 'history', + render: () => , + }); + } + + if (dashboard.id && dashboard.meta.canAdmin) { + pages.push({ + title: 'Permissions', + id: 'permissions', + icon: 'lock', + render: () => , + }); + } + + pages.push({ + title: 'JSON Model', + id: 'dashboard_json', + icon: 'arrow', + render: () => , + }); + + return pages; + } + + onMakeEditable = () => { + const { dashboard } = this.props; + dashboard.editable = true; + dashboard.meta.canMakeEditable = false; + dashboard.meta.canEdit = true; + dashboard.meta.canSave = true; + this.forceUpdate(); + }; + + onPostSave = () => { + this.props.dashboard.meta.hasUnsavedFolderChange = false; + dashboardWatcher.reloadPage(); + }; + + renderMakeEditable(): React.ReactNode { + return ( +
+
Dashboard not editable
+ +
+ ); + } + + getGeneralPage(): SettingsPage { + return { + title: 'General', + id: 'settings', + icon: 'sliders-v-alt', + render: () => , + }; + } + + render() { + const { dashboard, editview } = this.props; const folderTitle = dashboard.meta.folderTitle; const haveFolder = (dashboard.meta.folderId ?? 0) > 0; + const pages = this.getPages(); + const currentPage = pages.find(page => page.id === editview) ?? pages[0]; + const canSaveAs = contextSrv.hasEditPermissionInFolders; + const canSave = dashboard.meta.canSave; return (
@@ -58,9 +155,37 @@ export class DashboardSettings extends PureComponent {
-
(this.element = element)} /> +
+ +
{currentPage.render()}
+
); } } + +export interface SettingsPage { + id: string; + title: string; + icon: IconName; + render: () => React.ReactNode; +} diff --git a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx new file mode 100644 index 00000000000..647f610e5a3 --- /dev/null +++ b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx @@ -0,0 +1,30 @@ +import React, { PureComponent } from 'react'; +import { DashboardModel } from '../../state/DashboardModel'; +import { AngularComponent, getAngularLoader } from '@grafana/runtime'; + +interface Props { + dashboard: DashboardModel; +} + +export class GeneralSettings extends PureComponent { + element?: HTMLElement | null; + angularCmp?: AngularComponent; + + componentDidMount() { + const loader = getAngularLoader(); + + const template = ''; + const scopeProps = { dashboard: this.props.dashboard }; + this.angularCmp = loader.load(this.element, scopeProps, template); + } + + componentWillUnmount() { + if (this.angularCmp) { + this.angularCmp.destroy(); + } + } + + render() { + return
(this.element = ref)} />; + } +} diff --git a/public/app/features/dashboard/components/DashboardSettings/JsonEditorSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/JsonEditorSettings.tsx new file mode 100644 index 00000000000..7937e022697 --- /dev/null +++ b/public/app/features/dashboard/components/DashboardSettings/JsonEditorSettings.tsx @@ -0,0 +1,54 @@ +import React, { useState } from 'react'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { Button, CodeEditor } from '@grafana/ui'; +import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; +import { getDashboardSrv } from '../../services/DashboardSrv'; +import { DashboardModel } from '../../state/DashboardModel'; + +interface Props { + dashboard: DashboardModel; +} + +export const JsonEditorSettings: React.FC = ({ dashboard }) => { + const [dashboardJson, setDashboardJson] = useState(JSON.stringify(dashboard.getSaveModelClone(), null, 2)); + const onBlur = (value: string) => { + setDashboardJson(value); + }; + const onClick = () => { + getDashboardSrv() + .saveJSONDashboard(dashboardJson) + .then(() => { + dashboardWatcher.reloadPage(); + }); + }; + + return ( + <> +

JSON Model

+
+ The JSON Model below is data structure that defines the dashboard. Including settings, panel settings & layout, + queries etc. +
+ +
+ + {({ width }) => ( + + )} + +
+ {dashboard.meta.canSave && ( + + )} + + ); +}; diff --git a/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx new file mode 100644 index 00000000000..7f6d64b245f --- /dev/null +++ b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx @@ -0,0 +1,30 @@ +import React, { PureComponent } from 'react'; +import { DashboardModel } from '../../state/DashboardModel'; +import { AngularComponent, getAngularLoader } from '@grafana/runtime'; + +interface Props { + dashboard: DashboardModel; +} + +export class LinksSettings extends PureComponent { + element?: HTMLElement | null; + angularCmp?: AngularComponent; + + componentDidMount() { + const loader = getAngularLoader(); + + const template = ''; + const scopeProps = { dashboard: this.props.dashboard }; + this.angularCmp = loader.load(this.element, scopeProps, template); + } + + componentWillUnmount() { + if (this.angularCmp) { + this.angularCmp.destroy(); + } + } + + render() { + return
(this.element = ref)} />; + } +} diff --git a/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts b/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts index 594bada9245..d173cd18689 100644 --- a/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts +++ b/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts @@ -1,183 +1,32 @@ import _ from 'lodash'; -import angular, { ILocationService, IScope } from 'angular'; +import { ILocationService, IScope } from 'angular'; import { selectors } from '@grafana/e2e-selectors'; -import { appEvents, contextSrv, coreModule } from 'app/core/core'; +import { appEvents, coreModule } from 'app/core/core'; import { DashboardModel } from '../../state/DashboardModel'; -import { DashboardSrv } from '../../services/DashboardSrv'; import { CoreEvents } from 'app/types'; -import { GrafanaRootScope } from 'app/routes/GrafanaCtrl'; -import { AppEvents, locationUtil, TimeZone, urlUtil } from '@grafana/data'; +import { AppEvents, TimeZone } from '@grafana/data'; import { promiseToDigest } from '../../../../core/utils/promiseToDigest'; import { deleteDashboard } from 'app/features/manage-dashboards/state/actions'; export class SettingsCtrl { dashboard: DashboardModel; - isOpen: boolean; - viewId: string; - json: string; - alertCount: number; canSaveAs: boolean; canSave?: boolean; canDelete?: boolean; - sections: any[]; - hasUnsavedFolderChange: boolean; selectors: typeof selectors.pages.Dashboard.Settings.General; renderCount: number; // hack to update React when Angular changes /** @ngInject */ - constructor( - private $scope: IScope & Record, - private $route: any, - private $location: ILocationService, - private $rootScope: GrafanaRootScope, - private dashboardSrv: DashboardSrv - ) { + constructor(private $scope: IScope & Record, private $location: ILocationService) { // temp hack for annotations and variables editors // that rely on inherited scope $scope.dashboard = this.dashboard; - - this.$scope.$on('$destroy', () => { - this.dashboard.updateSubmenuVisibility(); - - setTimeout(() => { - this.dashboard.startRefresh(); - }); - }); - - this.canSaveAs = contextSrv.hasEditPermissionInFolders; - this.canSave = this.dashboard.meta.canSave; this.canDelete = this.dashboard.meta.canSave; - - this.buildSectionList(); - this.onRouteUpdated(); - - this.$rootScope.onAppEvent(CoreEvents.routeUpdated, this.onRouteUpdated.bind(this), $scope); - - appEvents.on(CoreEvents.dashboardSaved, this.onPostSave.bind(this), $scope); - this.selectors = selectors.pages.Dashboard.Settings.General; this.renderCount = 0; } - buildSectionList() { - this.sections = []; - - if (this.dashboard.meta.canEdit) { - this.sections.push({ - title: 'General', - id: 'settings', - icon: 'sliders-v-alt', - }); - this.sections.push({ - title: 'Annotations', - id: 'annotations', - icon: 'comment-alt', - }); - this.sections.push({ - title: 'Variables', - id: 'templating', - icon: 'calculator-alt', - }); - this.sections.push({ - title: 'Links', - id: 'links', - icon: 'link', - }); - } - - if (this.dashboard.id && this.dashboard.meta.canSave) { - this.sections.push({ - title: 'Versions', - id: 'versions', - icon: 'history', - }); - } - - if (this.dashboard.id && this.dashboard.meta.canAdmin) { - this.sections.push({ - title: 'Permissions', - id: 'permissions', - icon: 'lock', - }); - } - - if (this.dashboard.meta.canMakeEditable) { - this.sections.push({ - title: 'General', - icon: 'sliders-v-alt', - id: 'make_editable', - }); - } - - this.sections.push({ - title: 'JSON Model', - id: 'dashboard_json', - icon: 'arrow', - }); - - const params = this.$location.search(); - const url = this.$location.path(); - - for (const section of this.sections) { - section.url = locationUtil.assureBaseUrl(urlUtil.renderUrl(url, { ...params, editview: section.id })); - } - } - - onRouteUpdated() { - this.viewId = this.$location.search().editview; - - if (this.viewId) { - this.json = angular.toJson(this.dashboard.getSaveModelClone(), true); - } - - if (this.viewId === 'settings' && this.dashboard.meta.canMakeEditable) { - this.viewId = 'make_editable'; - } - - const currentSection: any = _.find(this.sections, { id: this.viewId } as any); - if (!currentSection) { - this.sections.unshift({ - title: 'Not found', - id: '404', - icon: 'exclamation-triangle', - }); - this.viewId = '404'; - } - } - saveDashboardJson() { - this.dashboardSrv.saveJSONDashboard(this.json).then(() => { - this.$route.reload(); - }); - } - - onPostSave() { - this.hasUnsavedFolderChange = false; - } - - hideSettings() { - const urlParams = this.$location.search(); - delete urlParams.editview; - setTimeout(() => { - this.$rootScope.$apply(() => { - this.$location.search(urlParams); - }); - }); - } - - makeEditable() { - this.dashboard.editable = true; - this.dashboard.meta.canMakeEditable = false; - this.dashboard.meta.canEdit = true; - this.dashboard.meta.canSave = true; - this.canDelete = true; - this.viewId = 'settings'; - this.buildSectionList(); - - const currentSection: any = _.find(this.sections, { id: this.viewId } as any); - this.$location.url(locationUtil.stripBaseFromUrl(currentSection.url)); - } - deleteDashboard() { let confirmText = ''; let text2 = this.dashboard.title; @@ -237,19 +86,7 @@ export class SettingsCtrl { onFolderChange = (folder: { id: number; title: string }) => { this.dashboard.meta.folderId = folder.id; this.dashboard.meta.folderTitle = folder.title; - this.hasUnsavedFolderChange = true; - }; - - getFolder() { - return { - id: this.dashboard.meta.folderId, - title: this.dashboard.meta.folderTitle, - url: this.dashboard.meta.folderUrl, - }; - } - - getDashboard = () => { - return this.dashboard; + this.dashboard.meta.hasUnsavedFolderChange = true; }; onRefreshIntervalChange = (intervals: string[]) => { diff --git a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx new file mode 100644 index 00000000000..6cde22c21fa --- /dev/null +++ b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx @@ -0,0 +1,30 @@ +import React, { PureComponent } from 'react'; +import { DashboardModel } from '../../state/DashboardModel'; +import { AngularComponent, getAngularLoader } from '@grafana/runtime'; + +interface Props { + dashboard: DashboardModel; +} + +export class VersionsSettings extends PureComponent { + element?: HTMLElement | null; + angularCmp?: AngularComponent; + + componentDidMount() { + const loader = getAngularLoader(); + + const template = ''; + const scopeProps = { dashboard: this.props.dashboard }; + this.angularCmp = loader.load(this.element, scopeProps, template); + } + + componentWillUnmount() { + if (this.angularCmp) { + this.angularCmp.destroy(); + } + } + + render() { + return
(this.element = ref)} />; + } +} diff --git a/public/app/features/dashboard/components/DashboardSettings/template.html b/public/app/features/dashboard/components/DashboardSettings/template.html index b951945faba..c0f989d96fe 100644 --- a/public/app/features/dashboard/components/DashboardSettings/template.html +++ b/public/app/features/dashboard/components/DashboardSettings/template.html @@ -1,143 +1,64 @@ - +

+ General +

-
-

- General -

- -
-
- - -
-
- - -
-
- - - -
- - - - -
- - - - -
Panel Options
+
-
+
+ + +
+
+ -
- -
+ +
-
- + + + + +
+ + + + +
Panel Options
+
+ +
+
- -
-
- -
- -
- -
- -
- -
- -
- -
-

JSON Model

-
- The JSON Model below is data structure that defines the dashboard. Including settings, panel settings & layout, - queries etc. -
- -
- -
- -
- -
-
- -
- -
-
You have changed folder, please save to view permissions.
-
-
- -
-

Settings view not found

- -
-
The settings page could not be found or you do not have permission to access it
-
-
- -
-

Make Editable

- -
diff --git a/public/app/features/dashboard/components/VersionHistory/template.html b/public/app/features/dashboard/components/VersionHistory/template.html index ac42c42c890..363c879e5a9 100644 --- a/public/app/features/dashboard/components/VersionHistory/template.html +++ b/public/app/features/dashboard/components/VersionHistory/template.html @@ -31,7 +31,6 @@ diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 7cfdd9a7020..e93d9ec1dda 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -337,7 +337,7 @@ export class DashboardPage extends PureComponent { {inspectPanel && } {editPanel && } - {editview && } + {editview && }
); } diff --git a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap index 579adc1e802..7f43d1abe91 100644 --- a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap +++ b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap @@ -42,6 +42,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` "canSave": true, "canShare": true, "canStar": true, + "hasUnsavedFolderChange": false, "showSettings": true, }, "originalTemplating": Array [], @@ -169,6 +170,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` "canSave": true, "canShare": true, "canStar": true, + "hasUnsavedFolderChange": false, "showSettings": true, }, "originalTemplating": Array [], @@ -264,6 +266,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` "canSave": true, "canShare": true, "canStar": true, + "hasUnsavedFolderChange": false, "showSettings": true, }, "originalTemplating": Array [], @@ -414,6 +417,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti "canSave": true, "canShare": true, "canStar": true, + "hasUnsavedFolderChange": false, "showSettings": true, }, "originalTemplating": Array [], @@ -541,6 +545,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti "canSave": true, "canShare": true, "canStar": true, + "hasUnsavedFolderChange": false, "showSettings": true, }, "originalTemplating": Array [], @@ -636,6 +641,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti "canSave": true, "canShare": true, "canStar": true, + "hasUnsavedFolderChange": false, "showSettings": true, }, "originalTemplating": Array [], @@ -736,6 +742,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti "canSave": true, "canShare": true, "canStar": true, + "hasUnsavedFolderChange": false, "showSettings": true, }, "originalTemplating": Array [], @@ -792,6 +799,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti "version": 0, } } + editview="settings" updateLocation={[MockFunction]} />
diff --git a/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap b/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap index c1150ebde3f..bd55179e3ac 100644 --- a/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap +++ b/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap @@ -92,6 +92,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` "canSave": true, "canShare": true, "canStar": true, + "hasUnsavedFolderChange": false, "showSettings": true, }, "originalTemplating": Array [], @@ -314,6 +315,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` "canSave": true, "canShare": true, "canStar": true, + "hasUnsavedFolderChange": false, "showSettings": true, }, "originalTemplating": Array [], @@ -536,6 +538,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` "canSave": true, "canShare": true, "canStar": true, + "hasUnsavedFolderChange": false, "showSettings": true, }, "originalTemplating": Array [], @@ -758,6 +761,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` "canSave": true, "canShare": true, "canStar": true, + "hasUnsavedFolderChange": false, "showSettings": true, }, "originalTemplating": Array [], diff --git a/public/app/features/dashboard/index.ts b/public/app/features/dashboard/index.ts index f589fb331d0..ebc3370ca87 100644 --- a/public/app/features/dashboard/index.ts +++ b/public/app/features/dashboard/index.ts @@ -9,7 +9,7 @@ import './components/DashNav'; import './components/VersionHistory'; import './components/DashboardSettings'; -import DashboardPermissions from './components/DashboardPermissions/DashboardPermissions'; +import { DashboardPermissions } from './components/DashboardPermissions/DashboardPermissions'; // angular wrappers import { react2AngularDirective } from 'app/core/utils/react2angular'; diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 947933bb9bd..798833ca764 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -170,6 +170,7 @@ export class DashboardModel { meta.canEdit = meta.canEdit !== false; meta.showSettings = meta.canEdit; meta.canMakeEditable = meta.canSave && !this.editable; + meta.hasUnsavedFolderChange = false; if (!this.editable) { meta.canEdit = false; diff --git a/public/app/features/variables/editor/VariableEditorContainer.tsx b/public/app/features/variables/editor/VariableEditorContainer.tsx index 4182c07affd..4637bbccb1a 100644 --- a/public/app/features/variables/editor/VariableEditorContainer.tsx +++ b/public/app/features/variables/editor/VariableEditorContainer.tsx @@ -5,8 +5,7 @@ import { selectors } from '@grafana/e2e-selectors'; import { toVariableIdentifier, toVariablePayload, VariableIdentifier } from '../state/types'; import { StoreState } from '../../../types'; import { VariableEditorEditor } from './VariableEditorEditor'; -import { MapDispatchToProps, MapStateToProps } from 'react-redux'; -import { connectWithStore } from '../../../core/utils/connectWithReduxStore'; +import { connect, MapStateToProps, MapDispatchToProps } from 'react-redux'; import { getEditorVariables } from '../state/selectors'; import { VariableModel } from '../types'; import { switchToEditMode, switchToListMode, switchToNewMode } from './actions'; @@ -140,8 +139,4 @@ const mapDispatchToProps: MapDispatchToProps = { switchToListMode, }; -export const VariableEditorContainer = connectWithStore( - VariableEditorContainerUnconnected, - mapStateToProps, - mapDispatchToProps -); +export const VariableEditorContainer = connect(mapStateToProps, mapDispatchToProps)(VariableEditorContainerUnconnected); diff --git a/public/app/types/dashboard.ts b/public/app/types/dashboard.ts index d83d5de3f03..90900dc1d11 100644 --- a/public/app/types/dashboard.ts +++ b/public/app/types/dashboard.ts @@ -34,6 +34,7 @@ export interface DashboardMeta { createdBy?: string; updated?: string; updatedBy?: string; + hasUnsavedFolderChange?: boolean; } export interface DashboardDataDTO { diff --git a/public/app/types/folders.ts b/public/app/types/folders.ts index 88ddead0cad..e55a53faa25 100644 --- a/public/app/types/folders.ts +++ b/public/app/types/folders.ts @@ -23,7 +23,7 @@ export interface FolderState { } export interface FolderInfo { - id: number; - title: string; - url: string; + id?: number; + title?: string; + url?: string; } diff --git a/public/sass/components/_dashboard_settings.scss b/public/sass/components/_dashboard_settings.scss index 8dc375f674c..6d46a7c469f 100644 --- a/public/sass/components/_dashboard_settings.scss +++ b/public/sass/components/_dashboard_settings.scss @@ -12,12 +12,7 @@ flex-direction: column; } -.dashboard-settings__body1 { - min-height: 100%; - width: 100%; -} - -.dashboard-settings__body2 { +.dashboard-settings__body { min-height: 100%; width: 100%; display: flex;