diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f5fed293d5..5a691465626 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ * **Auth**: Prevent password reset when login form is disabled or either LDAP or Auth Proxy is enabled [#14246](https://github.com/grafana/grafana/issues/14246), thx [@SilverFire](https://github.com/SilverFire) * **Dataproxy**: Override incoming Authorization header [#13815](https://github.com/grafana/grafana/issues/13815), thx [@kornholi](https://github.com/kornholi) * **Admin**: Fix prevent removing last grafana admin permissions [#11067](https://github.com/grafana/grafana/issues/11067), thx [@danielbh](https://github.com/danielbh) -* **Templating**: Escaping "Custom" template variables [#13754](https://github.com/grafana/grafana/issues/13754), thx [@IntegersOfK]req(https://github.com/IntegersOfK) +* **Templating**: Escaping "Custom" template variables [#13754](https://github.com/grafana/grafana/issues/13754), thx [@IntegersOfK](https://github.com/IntegersOfK) * **Admin**: When multiple user invitations, all links are the same as the first user who was invited [#14483](https://github.com/grafana/grafana/issues/14483) * **LDAP**: Upgrade go-ldap to v3 [#14548](https://github.com/grafana/grafana/issues/14548) * **Proxy whitelist**: Add CIDR capability to auth_proxy whitelist [#14546](https://github.com/grafana/grafana/issues/14546), thx [@jacobrichard](https://github.com/jacobrichard) diff --git a/ROADMAP.md b/ROADMAP.md index 891bc9f790b..b5e62578475 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -5,18 +5,22 @@ But it will give you an idea of our current vision and plan. ### Short term (1-2 months) - PRs & Bugs - - Multi-Stat panel + - React Panel Support + - React Query Editor Support - Metrics & Log Explore UI - + - Grafana UI library shared between grafana & plugins + - Seperate visualization from panels + - More reuse between Explore & dashboard + - Explore logging support for more data sources + ### Mid term (2-4 months) - - React Panels - - Change visualization (panel type) on the fly. - - Templating Query Editor UI Plugin hook - - Backend plugins + - Drilldown links + - Dashboards as code workflows + - React migration + - New panels ### Long term (4 - 8 months) - Alerting improvements (silence, per series tracking, etc) - - Progress on React migration ### In a distant future far far away - Meta queries diff --git a/docs/sources/http_api/data_source.md b/docs/sources/http_api/data_source.md index 9aaf29ec5f4..364b55b0cfc 100644 --- a/docs/sources/http_api/data_source.md +++ b/docs/sources/http_api/data_source.md @@ -188,8 +188,8 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk "defaultRegion": "us-west-1" }, "secureJsonData": { - "accessKey": "Ol4pIDpeKSA6XikgOl4p", //should not be encoded - "secretKey": "dGVzdCBrZXkgYmxlYXNlIGRvbid0IHN0ZWFs" //should be Base-64 encoded + "accessKey": "Ol4pIDpeKSA6XikgOl4p", + "secretKey": "dGVzdCBrZXkgYmxlYXNlIGRvbid0IHN0ZWFs" } } ``` diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index 8fa51c88554..46bab83654e 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -391,6 +391,12 @@ value is `true`. If you want to track Grafana usage via Google analytics specify *your* Universal Analytics ID here. By default this feature is disabled. +### check_for_updates + +Set to false to disable all checks to https://grafana.com for new versions of Grafana and installed plugins. Check is used +in some UI views to notify that a Grafana or plugin update exists. This option does not cause any auto updates, nor +send any sensitive information. +
## [dashboards] diff --git a/docs/sources/reference/templating.md b/docs/sources/reference/templating.md index 71ce6bdd2ae..3ef32b1b10f 100644 --- a/docs/sources/reference/templating.md +++ b/docs/sources/reference/templating.md @@ -52,6 +52,7 @@ Filter Option | Example | Raw | Interpolated | Description `csv`| ${servers:csv} | `'test1', 'test2'` | `test1,test2` | Formats multi-value variable as a comma-separated string `distributed`| ${servers:distributed} | `'test1', 'test2'` | `test1,servers=test2` | Formats multi-value variable in custom format for OpenTSDB. `lucene`| ${servers:lucene} | `'test', 'test2'` | `("test" OR "test2")` | Formats multi-value variable as a lucene expression. +`percentencode` | ${servers:percentencode} | `'foo()bar BAZ', 'test2'` | `{foo%28%29bar%20BAZ%2Ctest2}` | Formats multi-value variable into a glob, percent-encoded. Test the formatting options on the [Grafana Play site](http://play.grafana.org/d/cJtIfcWiz/template-variable-formatting-options?orgId=1). diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 2789b0bf51e..5959c230fb9 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -336,7 +336,7 @@ func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) { "id": 123123, "gridPos": map[string]interface{}{ "x": 0, - "y": 3, + "y": 0, "w": 24, "h": 4, }, diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index 9e128c449a6..989746fd067 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -236,7 +236,7 @@ export class KeybindingSrv { shareScope.dashboard = dashboard; appEvents.emit('show-modal', { - src: 'public/app/features/dashboard/partials/shareModal.html', + src: 'public/app/features/dashboard/components/ShareModal/template.html', scope: shareScope, }); } diff --git a/public/app/core/utils/text.ts b/public/app/core/utils/text.ts index 9f4f1c41716..427b0102c95 100644 --- a/public/app/core/utils/text.ts +++ b/public/app/core/utils/text.ts @@ -44,9 +44,25 @@ export function findMatchesInText(haystack: string, needle: string): TextMatch[] return matches; } +const XSSWL = Object.keys(xss.whiteList).reduce((acc, element) => { + acc[element] = xss.whiteList[element].concat(['class', 'style']); + return acc; +}, {}); + +const sanitizeXSS = new xss.FilterXSS({ + whiteList: XSSWL +}); + +/** + * Returns string safe from XSS attacks. + * + * Even though we allow the style-attribute, there's still default filtering applied to it + * Info: https://github.com/leizongmin/js-xss#customize-css-filter + * Whitelist: https://github.com/leizongmin/js-css-filter/blob/master/lib/default.js + */ export function sanitize (unsanitizedString: string): string { try { - return xss(unsanitizedString); + return sanitizeXSS.process(unsanitizedString); } catch (error) { console.log('String could not be sanitized', unsanitizedString); return unsanitizedString; diff --git a/public/app/features/all.ts b/public/app/features/all.ts index 1ba6a85899c..83146596ea0 100644 --- a/public/app/features/all.ts +++ b/public/app/features/all.ts @@ -1,7 +1,7 @@ import './annotations/all'; import './templating/all'; import './plugins/all'; -import './dashboard/all'; +import './dashboard'; import './playlist/all'; import './panel/all'; import './org/all'; diff --git a/public/app/features/dashboard/alerting_srv.ts b/public/app/features/dashboard/alerting_srv.ts deleted file mode 100644 index 446c3218f79..00000000000 --- a/public/app/features/dashboard/alerting_srv.ts +++ /dev/null @@ -1,13 +0,0 @@ -import coreModule from 'app/core/core_module'; - -export class AlertingSrv { - dashboard: any; - alerts: any[]; - - init(dashboard, alerts) { - this.dashboard = dashboard; - this.alerts = alerts || []; - } -} - -coreModule.service('alertingSrv', AlertingSrv); diff --git a/public/app/features/dashboard/all.ts b/public/app/features/dashboard/all.ts deleted file mode 100644 index 5ec4e5e3929..00000000000 --- a/public/app/features/dashboard/all.ts +++ /dev/null @@ -1,45 +0,0 @@ -import './dashboard_ctrl'; -import './alerting_srv'; -import './history/history'; -import './dashboard_loader_srv'; -import './dashnav/dashnav'; -import './submenu/submenu'; -import './save_as_modal'; -import './save_modal'; -import './save_provisioned_modal'; -import './shareModalCtrl'; -import './share_snapshot_ctrl'; -import './dashboard_srv'; -import './view_state_srv'; -import './validation_srv'; -import './time_srv'; -import './unsaved_changes_srv'; -import './unsaved_changes_modal'; -import './timepicker/timepicker'; -import './upload'; -import './export/export_modal'; -import './export_data/export_data_modal'; -import './ad_hoc_filters'; -import './repeat_option/repeat_option'; -import './dashgrid/DashboardGridDirective'; -import './dashgrid/RowOptions'; -import './folder_picker/folder_picker'; -import './move_to_folder_modal/move_to_folder'; -import './settings/settings'; -import './panellinks/module'; -import './dashlinks/module'; - -// angular wrappers -import { react2AngularDirective } from 'app/core/utils/react2angular'; -import DashboardPermissions from './permissions/DashboardPermissions'; - -react2AngularDirective('dashboardPermissions', DashboardPermissions, ['dashboardId', 'folder']); - -import coreModule from 'app/core/core_module'; -import { FolderDashboardsCtrl } from './folder_dashboards_ctrl'; -import { DashboardImportCtrl } from './dashboard_import_ctrl'; -import { CreateFolderCtrl } from './create_folder_ctrl'; - -coreModule.controller('FolderDashboardsCtrl', FolderDashboardsCtrl); -coreModule.controller('DashboardImportCtrl', DashboardImportCtrl); -coreModule.controller('CreateFolderCtrl', CreateFolderCtrl); diff --git a/public/app/features/dashboard/ad_hoc_filters.ts b/public/app/features/dashboard/components/AdHocFilters/AdHocFiltersCtrl.ts similarity index 100% rename from public/app/features/dashboard/ad_hoc_filters.ts rename to public/app/features/dashboard/components/AdHocFilters/AdHocFiltersCtrl.ts diff --git a/public/app/features/dashboard/components/AdHocFilters/index.ts b/public/app/features/dashboard/components/AdHocFilters/index.ts new file mode 100644 index 00000000000..522b564d004 --- /dev/null +++ b/public/app/features/dashboard/components/AdHocFilters/index.ts @@ -0,0 +1 @@ +export { AdHocFiltersCtrl } from './AdHocFiltersCtrl'; diff --git a/public/app/features/dashboard/export/export_modal.ts b/public/app/features/dashboard/components/DashExportModal/DashExportCtrl.ts similarity index 92% rename from public/app/features/dashboard/export/export_modal.ts rename to public/app/features/dashboard/components/DashExportModal/DashExportCtrl.ts index 8136c77cd8f..7769bdf114a 100644 --- a/public/app/features/dashboard/export/export_modal.ts +++ b/public/app/features/dashboard/components/DashExportModal/DashExportCtrl.ts @@ -2,7 +2,7 @@ import angular from 'angular'; import { saveAs } from 'file-saver'; import coreModule from 'app/core/core_module'; -import { DashboardExporter } from './exporter'; +import { DashboardExporter } from './DashboardExporter'; export class DashExportCtrl { dash: any; @@ -66,7 +66,7 @@ export class DashExportCtrl { export function dashExportDirective() { return { restrict: 'E', - templateUrl: 'public/app/features/dashboard/export/export_modal.html', + templateUrl: 'public/app/features/dashboard/components/DashExportModal/template.html', controller: DashExportCtrl, bindToController: true, controllerAs: 'ctrl', diff --git a/public/app/features/dashboard/specs/exporter.test.ts b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts similarity index 98% rename from public/app/features/dashboard/specs/exporter.test.ts rename to public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts index eac6b0b272a..20ab21541a5 100644 --- a/public/app/features/dashboard/specs/exporter.test.ts +++ b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts @@ -6,8 +6,8 @@ jest.mock('app/core/store', () => { import _ from 'lodash'; import config from 'app/core/config'; -import { DashboardExporter } from '../export/exporter'; -import { DashboardModel } from '../dashboard_model'; +import { DashboardExporter } from './DashboardExporter'; +import { DashboardModel } from '../../dashboard_model'; describe('given dashboard with repeated panels', () => { let dash, exported; diff --git a/public/app/features/dashboard/export/exporter.ts b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts similarity index 98% rename from public/app/features/dashboard/export/exporter.ts rename to public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts index 7aecb5c384f..22b93b767d6 100644 --- a/public/app/features/dashboard/export/exporter.ts +++ b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts @@ -1,6 +1,6 @@ import config from 'app/core/config'; import _ from 'lodash'; -import { DashboardModel } from '../dashboard_model'; +import { DashboardModel } from '../../dashboard_model'; export class DashboardExporter { constructor(private datasourceSrv) {} diff --git a/public/app/features/dashboard/components/DashExportModal/index.ts b/public/app/features/dashboard/components/DashExportModal/index.ts new file mode 100644 index 00000000000..6529cf07ad9 --- /dev/null +++ b/public/app/features/dashboard/components/DashExportModal/index.ts @@ -0,0 +1,2 @@ +export { DashboardExporter } from './DashboardExporter'; +export { DashExportCtrl } from './DashExportCtrl'; diff --git a/public/app/features/dashboard/export/export_modal.html b/public/app/features/dashboard/components/DashExportModal/template.html similarity index 100% rename from public/app/features/dashboard/export/export_modal.html rename to public/app/features/dashboard/components/DashExportModal/template.html diff --git a/public/app/features/dashboard/dashlinks/module.ts b/public/app/features/dashboard/components/DashLinks/DashLinksContainerCtrl.ts similarity index 99% rename from public/app/features/dashboard/dashlinks/module.ts rename to public/app/features/dashboard/components/DashLinks/DashLinksContainerCtrl.ts index c951538d45d..a08e438a46c 100644 --- a/public/app/features/dashboard/dashlinks/module.ts +++ b/public/app/features/dashboard/components/DashLinks/DashLinksContainerCtrl.ts @@ -1,6 +1,6 @@ import angular from 'angular'; import _ from 'lodash'; -import { iconMap } from './editor'; +import { iconMap } from './DashLinksEditorCtrl'; function dashLinksContainer() { return { diff --git a/public/app/features/dashboard/dashlinks/editor.ts b/public/app/features/dashboard/components/DashLinks/DashLinksEditorCtrl.ts similarity index 90% rename from public/app/features/dashboard/dashlinks/editor.ts rename to public/app/features/dashboard/components/DashLinks/DashLinksEditorCtrl.ts index 482052469db..398ad757bf3 100644 --- a/public/app/features/dashboard/dashlinks/editor.ts +++ b/public/app/features/dashboard/components/DashLinks/DashLinksEditorCtrl.ts @@ -11,7 +11,7 @@ export let iconMap = { cloud: 'fa-cloud', }; -export class DashLinkEditorCtrl { +export class DashLinksEditorCtrl { dashboard: any; iconMap: any; mode: any; @@ -65,8 +65,8 @@ export class DashLinkEditorCtrl { function dashLinksEditor() { return { restrict: 'E', - controller: DashLinkEditorCtrl, - templateUrl: 'public/app/features/dashboard/dashlinks/editor.html', + controller: DashLinksEditorCtrl, + templateUrl: 'public/app/features/dashboard/components/DashLinks/editor.html', bindToController: true, controllerAs: 'ctrl', scope: { diff --git a/public/app/features/dashboard/dashlinks/editor.html b/public/app/features/dashboard/components/DashLinks/editor.html similarity index 100% rename from public/app/features/dashboard/dashlinks/editor.html rename to public/app/features/dashboard/components/DashLinks/editor.html diff --git a/public/app/features/dashboard/components/DashLinks/index.ts b/public/app/features/dashboard/components/DashLinks/index.ts new file mode 100644 index 00000000000..ef118d4a84c --- /dev/null +++ b/public/app/features/dashboard/components/DashLinks/index.ts @@ -0,0 +1,2 @@ +export { DashLinksContainerCtrl } from './DashLinksContainerCtrl'; +export { DashLinksEditorCtrl } from './DashLinksEditorCtrl'; diff --git a/public/app/features/dashboard/dashnav/dashnav.ts b/public/app/features/dashboard/components/DashNav/DashNavCtrl.ts similarity index 92% rename from public/app/features/dashboard/dashnav/dashnav.ts rename to public/app/features/dashboard/components/DashNav/DashNavCtrl.ts index 1c83b2d0bdb..d7305b948dc 100644 --- a/public/app/features/dashboard/dashnav/dashnav.ts +++ b/public/app/features/dashboard/components/DashNav/DashNavCtrl.ts @@ -1,7 +1,7 @@ import moment from 'moment'; import angular from 'angular'; import { appEvents, NavModel } from 'app/core/core'; -import { DashboardModel } from '../dashboard_model'; +import { DashboardModel } from '../../dashboard_model'; export class DashNavCtrl { dashboard: DashboardModel; @@ -60,7 +60,7 @@ export class DashNavCtrl { modalScope.dashboard = this.dashboard; appEvents.emit('show-modal', { - src: 'public/app/features/dashboard/partials/shareModal.html', + src: 'public/app/features/dashboard/components/ShareModal/template.html', scope: modalScope, }); } @@ -107,7 +107,7 @@ export class DashNavCtrl { export function dashNavDirective() { return { restrict: 'E', - templateUrl: 'public/app/features/dashboard/dashnav/dashnav.html', + templateUrl: 'public/app/features/dashboard/components/DashNav/template.html', controller: DashNavCtrl, bindToController: true, controllerAs: 'ctrl', diff --git a/public/app/features/dashboard/components/DashNav/index.ts b/public/app/features/dashboard/components/DashNav/index.ts new file mode 100644 index 00000000000..854e32b24d2 --- /dev/null +++ b/public/app/features/dashboard/components/DashNav/index.ts @@ -0,0 +1 @@ +export { DashNavCtrl } from './DashNavCtrl'; diff --git a/public/app/features/dashboard/dashnav/dashnav.html b/public/app/features/dashboard/components/DashNav/template.html similarity index 100% rename from public/app/features/dashboard/dashnav/dashnav.html rename to public/app/features/dashboard/components/DashNav/template.html diff --git a/public/app/features/dashboard/permissions/DashboardPermissions.tsx b/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx similarity index 97% rename from public/app/features/dashboard/permissions/DashboardPermissions.tsx rename to public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx index 96d0e23adcd..506709fad75 100644 --- a/public/app/features/dashboard/permissions/DashboardPermissions.tsx +++ b/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx @@ -8,11 +8,11 @@ import { addDashboardPermission, removeDashboardPermission, updateDashboardPermission, -} from '../state/actions'; +} from '../../state/actions'; 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 '../../../core/utils/connectWithReduxStore'; +import { connectWithStore } from 'app/core/utils/connectWithReduxStore'; export interface Props { dashboardId: number; diff --git a/public/app/features/dashboard/settings/settings.ts b/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts similarity index 97% rename from public/app/features/dashboard/settings/settings.ts rename to public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts index 1e8d96a54cb..a0eb5c8c6b3 100755 --- a/public/app/features/dashboard/settings/settings.ts +++ b/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts @@ -1,5 +1,5 @@ import { coreModule, appEvents, contextSrv } from 'app/core/core'; -import { DashboardModel } from '../dashboard_model'; +import { DashboardModel } from '../../dashboard_model'; import $ from 'jquery'; import _ from 'lodash'; import angular from 'angular'; @@ -230,7 +230,7 @@ export class SettingsCtrl { export function dashboardSettings() { return { restrict: 'E', - templateUrl: 'public/app/features/dashboard/settings/settings.html', + templateUrl: 'public/app/features/dashboard/components/DashboardSettings/template.html', controller: SettingsCtrl, bindToController: true, controllerAs: 'ctrl', diff --git a/public/app/features/dashboard/components/DashboardSettings/index.ts b/public/app/features/dashboard/components/DashboardSettings/index.ts new file mode 100644 index 00000000000..f81b8cdbc67 --- /dev/null +++ b/public/app/features/dashboard/components/DashboardSettings/index.ts @@ -0,0 +1 @@ +export { SettingsCtrl } from './SettingsCtrl'; diff --git a/public/app/features/dashboard/settings/settings.html b/public/app/features/dashboard/components/DashboardSettings/template.html similarity index 98% rename from public/app/features/dashboard/settings/settings.html rename to public/app/features/dashboard/components/DashboardSettings/template.html index 46d84a7a2fd..97002f7bf92 100644 --- a/public/app/features/dashboard/settings/settings.html +++ b/public/app/features/dashboard/components/DashboardSettings/template.html @@ -51,7 +51,8 @@ on-change="ctrl.onFolderChange($folder)" enable-create-new="true" is-valid-selection="true" - label-class="width-7"> + label-class="width-7" + dashboard-id="ctrl.dashboard.id"> diff --git a/public/app/features/dashboard/export_data/export_data_modal.ts b/public/app/features/dashboard/components/ExportDataModal/ExportDataModalCtrl.ts similarity index 92% rename from public/app/features/dashboard/export_data/export_data_modal.ts rename to public/app/features/dashboard/components/ExportDataModal/ExportDataModalCtrl.ts index 460f80079d9..f87daa94ee7 100644 --- a/public/app/features/dashboard/export_data/export_data_modal.ts +++ b/public/app/features/dashboard/components/ExportDataModal/ExportDataModalCtrl.ts @@ -31,7 +31,7 @@ export class ExportDataModalCtrl { export function exportDataModal() { return { restrict: 'E', - templateUrl: 'public/app/features/dashboard/export_data/export_data_modal.html', + templateUrl: 'public/app/features/dashboard/components/ExportDataModal/template.html', controller: ExportDataModalCtrl, controllerAs: 'ctrl', scope: { diff --git a/public/app/features/dashboard/components/ExportDataModal/index.ts b/public/app/features/dashboard/components/ExportDataModal/index.ts new file mode 100644 index 00000000000..6df4fd00434 --- /dev/null +++ b/public/app/features/dashboard/components/ExportDataModal/index.ts @@ -0,0 +1 @@ +export { ExportDataModalCtrl } from './ExportDataModalCtrl'; diff --git a/public/app/features/dashboard/export_data/export_data_modal.html b/public/app/features/dashboard/components/ExportDataModal/template.html similarity index 100% rename from public/app/features/dashboard/export_data/export_data_modal.html rename to public/app/features/dashboard/components/ExportDataModal/template.html diff --git a/public/app/features/dashboard/folder_picker/folder_picker.ts b/public/app/features/dashboard/components/FolderPicker/FolderPickerCtrl.ts similarity index 90% rename from public/app/features/dashboard/folder_picker/folder_picker.ts rename to public/app/features/dashboard/components/FolderPicker/FolderPickerCtrl.ts index 80651fecb7e..93d43d36038 100644 --- a/public/app/features/dashboard/folder_picker/folder_picker.ts +++ b/public/app/features/dashboard/components/FolderPicker/FolderPickerCtrl.ts @@ -21,6 +21,7 @@ export class FolderPickerCtrl { hasValidationError: boolean; validationError: any; isEditor: boolean; + dashboardId?: number; /** @ngInject */ constructor(private backendSrv, private validationSrv, private contextSrv) { @@ -144,7 +145,13 @@ export class FolderPickerCtrl { if (this.isEditor) { folder = rootFolder; } else { - folder = result.length > 0 ? result[0] : resetFolder; + // We shouldn't assign a random folder without the user actively choosing it on a persisted dashboard + const isPersistedDashBoard = this.dashboardId ? true : false; + if (isPersistedDashBoard) { + folder = resetFolder; + } else { + folder = result.length > 0 ? result[0] : resetFolder; + } } } @@ -161,7 +168,7 @@ export class FolderPickerCtrl { export function folderPicker() { return { restrict: 'E', - templateUrl: 'public/app/features/dashboard/folder_picker/folder_picker.html', + templateUrl: 'public/app/features/dashboard/components/FolderPicker/template.html', controller: FolderPickerCtrl, bindToController: true, controllerAs: 'ctrl', @@ -176,6 +183,7 @@ export function folderPicker() { exitFolderCreation: '&', enableCreateNew: '@', enableReset: '@', + dashboardId: ' { diff --git a/public/app/features/dashboard/save_as_modal.ts b/public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts similarity index 96% rename from public/app/features/dashboard/save_as_modal.ts rename to public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts index 4649bc18f9f..6a470785fdb 100644 --- a/public/app/features/dashboard/save_as_modal.ts +++ b/public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts @@ -25,7 +25,8 @@ const template = ` enter-folder-creation="ctrl.onEnterFolderCreation()" exit-folder-creation="ctrl.onExitFolderCreation()" enable-create-new="true" - label-class="width-7"> + label-class="width-7" + dashboard-id="ctrl.clone.id"> diff --git a/public/app/features/dashboard/specs/save_modal.test.ts b/public/app/features/dashboard/components/SaveModals/SaveDashboardModalCtrl.test.ts similarity index 97% rename from public/app/features/dashboard/specs/save_modal.test.ts rename to public/app/features/dashboard/components/SaveModals/SaveDashboardModalCtrl.test.ts index 669ae43a0ff..f973c1b8e63 100644 --- a/public/app/features/dashboard/specs/save_modal.test.ts +++ b/public/app/features/dashboard/components/SaveModals/SaveDashboardModalCtrl.test.ts @@ -1,4 +1,4 @@ -import { SaveDashboardModalCtrl } from '../save_modal'; +import { SaveDashboardModalCtrl } from './SaveDashboardModalCtrl'; const setup = (timeChanged, variableValuesChanged, cb) => { const dash = { diff --git a/public/app/features/dashboard/save_modal.ts b/public/app/features/dashboard/components/SaveModals/SaveDashboardModalCtrl.ts similarity index 100% rename from public/app/features/dashboard/save_modal.ts rename to public/app/features/dashboard/components/SaveModals/SaveDashboardModalCtrl.ts diff --git a/public/app/features/dashboard/specs/save_provisioned_modal.test.ts b/public/app/features/dashboard/components/SaveModals/SaveProvisionedDashboardModalCtrl.test.ts similarity index 87% rename from public/app/features/dashboard/specs/save_provisioned_modal.test.ts rename to public/app/features/dashboard/components/SaveModals/SaveProvisionedDashboardModalCtrl.test.ts index a3ab27a984f..86048e861bd 100644 --- a/public/app/features/dashboard/specs/save_provisioned_modal.test.ts +++ b/public/app/features/dashboard/components/SaveModals/SaveProvisionedDashboardModalCtrl.test.ts @@ -1,4 +1,4 @@ -import { SaveProvisionedDashboardModalCtrl } from '../save_provisioned_modal'; +import { SaveProvisionedDashboardModalCtrl } from './SaveProvisionedDashboardModalCtrl'; describe('SaveProvisionedDashboardModalCtrl', () => { const json = { diff --git a/public/app/features/dashboard/save_provisioned_modal.ts b/public/app/features/dashboard/components/SaveModals/SaveProvisionedDashboardModalCtrl.ts similarity index 100% rename from public/app/features/dashboard/save_provisioned_modal.ts rename to public/app/features/dashboard/components/SaveModals/SaveProvisionedDashboardModalCtrl.ts diff --git a/public/app/features/dashboard/components/SaveModals/index.ts b/public/app/features/dashboard/components/SaveModals/index.ts new file mode 100644 index 00000000000..afab0796d28 --- /dev/null +++ b/public/app/features/dashboard/components/SaveModals/index.ts @@ -0,0 +1,2 @@ +export { SaveDashboardAsModalCtrl } from './SaveDashboardAsModalCtrl'; +export { SaveDashboardModalCtrl } from './SaveDashboardModalCtrl'; diff --git a/public/app/features/dashboard/specs/share_modal_ctrl.test.ts b/public/app/features/dashboard/components/ShareModal/ShareModalCtrl.test.ts similarity index 98% rename from public/app/features/dashboard/specs/share_modal_ctrl.test.ts rename to public/app/features/dashboard/components/ShareModal/ShareModalCtrl.test.ts index 70d301ed5ff..dd8dac31dde 100644 --- a/public/app/features/dashboard/specs/share_modal_ctrl.test.ts +++ b/public/app/features/dashboard/components/ShareModal/ShareModalCtrl.test.ts @@ -1,7 +1,6 @@ -import '../shareModalCtrl'; -import { ShareModalCtrl } from '../shareModalCtrl'; import config from 'app/core/config'; import { LinkSrv } from 'app/features/dashboard/panellinks/link_srv'; +import { ShareModalCtrl } from './ShareModalCtrl'; describe('ShareModalCtrl', () => { const ctx = { diff --git a/public/app/features/dashboard/shareModalCtrl.ts b/public/app/features/dashboard/components/ShareModal/ShareModalCtrl.ts similarity index 100% rename from public/app/features/dashboard/shareModalCtrl.ts rename to public/app/features/dashboard/components/ShareModal/ShareModalCtrl.ts diff --git a/public/app/features/dashboard/share_snapshot_ctrl.ts b/public/app/features/dashboard/components/ShareModal/ShareSnapshotCtrl.ts similarity index 100% rename from public/app/features/dashboard/share_snapshot_ctrl.ts rename to public/app/features/dashboard/components/ShareModal/ShareSnapshotCtrl.ts diff --git a/public/app/features/dashboard/components/ShareModal/index.ts b/public/app/features/dashboard/components/ShareModal/index.ts new file mode 100644 index 00000000000..3f27d5a1ba3 --- /dev/null +++ b/public/app/features/dashboard/components/ShareModal/index.ts @@ -0,0 +1,2 @@ +export { ShareModalCtrl } from './ShareModalCtrl'; +export { ShareSnapshotCtrl } from './ShareSnapshotCtrl'; diff --git a/public/app/features/dashboard/partials/shareModal.html b/public/app/features/dashboard/components/ShareModal/template.html similarity index 100% rename from public/app/features/dashboard/partials/shareModal.html rename to public/app/features/dashboard/components/ShareModal/template.html diff --git a/public/app/features/dashboard/submenu/submenu.ts b/public/app/features/dashboard/components/SubMenu/SubMenuCtrl.ts similarity index 86% rename from public/app/features/dashboard/submenu/submenu.ts rename to public/app/features/dashboard/components/SubMenu/SubMenuCtrl.ts index 184d29facee..502e467ad2b 100644 --- a/public/app/features/dashboard/submenu/submenu.ts +++ b/public/app/features/dashboard/components/SubMenu/SubMenuCtrl.ts @@ -1,7 +1,7 @@ import angular from 'angular'; import _ from 'lodash'; -export class SubmenuCtrl { +export class SubMenuCtrl { annotations: any; variables: any; dashboard: any; @@ -29,8 +29,8 @@ export class SubmenuCtrl { export function submenuDirective() { return { restrict: 'E', - templateUrl: 'public/app/features/dashboard/submenu/submenu.html', - controller: SubmenuCtrl, + templateUrl: 'public/app/features/dashboard/components/SubMenu/template.html', + controller: SubMenuCtrl, bindToController: true, controllerAs: 'ctrl', scope: { diff --git a/public/app/features/dashboard/components/SubMenu/index.ts b/public/app/features/dashboard/components/SubMenu/index.ts new file mode 100644 index 00000000000..1790aa66782 --- /dev/null +++ b/public/app/features/dashboard/components/SubMenu/index.ts @@ -0,0 +1 @@ +export { SubMenuCtrl } from './SubMenuCtrl'; diff --git a/public/app/features/dashboard/submenu/submenu.html b/public/app/features/dashboard/components/SubMenu/template.html similarity index 100% rename from public/app/features/dashboard/submenu/submenu.html rename to public/app/features/dashboard/components/SubMenu/template.html diff --git a/public/app/features/dashboard/timepicker/timepicker.ts b/public/app/features/dashboard/components/TimePicker/TimePickerCtrl.ts similarity index 95% rename from public/app/features/dashboard/timepicker/timepicker.ts rename to public/app/features/dashboard/components/TimePicker/TimePickerCtrl.ts index c89e49b54b3..0c388c27f8d 100644 --- a/public/app/features/dashboard/timepicker/timepicker.ts +++ b/public/app/features/dashboard/components/TimePicker/TimePickerCtrl.ts @@ -159,7 +159,7 @@ export class TimePickerCtrl { export function settingsDirective() { return { restrict: 'E', - templateUrl: 'public/app/features/dashboard/timepicker/settings.html', + templateUrl: 'public/app/features/dashboard/components/TimePicker/settings.html', controller: TimePickerCtrl, bindToController: true, controllerAs: 'ctrl', @@ -172,7 +172,7 @@ export function settingsDirective() { export function timePickerDirective() { return { restrict: 'E', - templateUrl: 'public/app/features/dashboard/timepicker/timepicker.html', + templateUrl: 'public/app/features/dashboard/components/TimePicker/template.html', controller: TimePickerCtrl, bindToController: true, controllerAs: 'ctrl', @@ -185,5 +185,5 @@ export function timePickerDirective() { angular.module('grafana.directives').directive('gfTimePickerSettings', settingsDirective); angular.module('grafana.directives').directive('gfTimePicker', timePickerDirective); -import { inputDateDirective } from './input_date'; +import { inputDateDirective } from './validation'; angular.module('grafana.directives').directive('inputDatetime', inputDateDirective); diff --git a/public/app/features/dashboard/components/TimePicker/index.ts b/public/app/features/dashboard/components/TimePicker/index.ts new file mode 100644 index 00000000000..ca6e2792c43 --- /dev/null +++ b/public/app/features/dashboard/components/TimePicker/index.ts @@ -0,0 +1 @@ +export { TimePickerCtrl } from './TimePickerCtrl'; diff --git a/public/app/features/dashboard/timepicker/settings.html b/public/app/features/dashboard/components/TimePicker/settings.html similarity index 100% rename from public/app/features/dashboard/timepicker/settings.html rename to public/app/features/dashboard/components/TimePicker/settings.html diff --git a/public/app/features/dashboard/timepicker/timepicker.html b/public/app/features/dashboard/components/TimePicker/template.html similarity index 100% rename from public/app/features/dashboard/timepicker/timepicker.html rename to public/app/features/dashboard/components/TimePicker/template.html diff --git a/public/app/features/dashboard/timepicker/input_date.ts b/public/app/features/dashboard/components/TimePicker/validation.ts similarity index 100% rename from public/app/features/dashboard/timepicker/input_date.ts rename to public/app/features/dashboard/components/TimePicker/validation.ts diff --git a/public/app/features/dashboard/unsaved_changes_modal.ts b/public/app/features/dashboard/components/UnsavedChangesModal/UnsavedChangesModalCtrl.ts similarity index 100% rename from public/app/features/dashboard/unsaved_changes_modal.ts rename to public/app/features/dashboard/components/UnsavedChangesModal/UnsavedChangesModalCtrl.ts diff --git a/public/app/features/dashboard/components/UnsavedChangesModal/index.ts b/public/app/features/dashboard/components/UnsavedChangesModal/index.ts new file mode 100644 index 00000000000..43943f06694 --- /dev/null +++ b/public/app/features/dashboard/components/UnsavedChangesModal/index.ts @@ -0,0 +1 @@ +export { UnsavedChangesModalCtrl } from './UnsavedChangesModalCtrl'; diff --git a/public/app/features/dashboard/specs/history_ctrl.test.ts b/public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.test.ts similarity index 98% rename from public/app/features/dashboard/specs/history_ctrl.test.ts rename to public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.test.ts index 632f3489dae..2b257e148f5 100644 --- a/public/app/features/dashboard/specs/history_ctrl.test.ts +++ b/public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.test.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import { HistoryListCtrl } from 'app/features/dashboard/history/history'; -import { versions, compare, restore } from './history_mocks'; +import { HistoryListCtrl } from './HistoryListCtrl'; +import { versions, compare, restore } from './__mocks__/history'; import $q from 'q'; describe('HistoryListCtrl', () => { diff --git a/public/app/features/dashboard/history/history.ts b/public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.ts similarity index 96% rename from public/app/features/dashboard/history/history.ts rename to public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.ts index 3563ccc7766..b8632e2eeae 100644 --- a/public/app/features/dashboard/history/history.ts +++ b/public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.ts @@ -1,12 +1,10 @@ -import './history_srv'; - import _ from 'lodash'; import angular from 'angular'; import moment from 'moment'; import locationUtil from 'app/core/utils/location_util'; -import { DashboardModel } from '../dashboard_model'; -import { HistoryListOpts, RevisionsModel, CalculateDiffOptions, HistorySrv } from './history_srv'; +import { DashboardModel } from '../../dashboard_model'; +import { HistoryListOpts, RevisionsModel, CalculateDiffOptions, HistorySrv } from './HistorySrv'; export class HistoryListCtrl { appending: boolean; @@ -200,7 +198,7 @@ export class HistoryListCtrl { export function dashboardHistoryDirective() { return { restrict: 'E', - templateUrl: 'public/app/features/dashboard/history/history.html', + templateUrl: 'public/app/features/dashboard/components/VersionHistory/template.html', controller: HistoryListCtrl, bindToController: true, controllerAs: 'ctrl', diff --git a/public/app/features/dashboard/specs/history_srv.test.ts b/public/app/features/dashboard/components/VersionHistory/HistorySrv.test.ts similarity index 90% rename from public/app/features/dashboard/specs/history_srv.test.ts rename to public/app/features/dashboard/components/VersionHistory/HistorySrv.test.ts index 1e2bd57a221..75766060e7f 100644 --- a/public/app/features/dashboard/specs/history_srv.test.ts +++ b/public/app/features/dashboard/components/VersionHistory/HistorySrv.test.ts @@ -1,7 +1,6 @@ -import '../history/history_srv'; -import { versions, restore } from './history_mocks'; -import { HistorySrv } from '../history/history_srv'; -import { DashboardModel } from '../dashboard_model'; +import { versions, restore } from './__mocks__/history'; +import { HistorySrv } from './HistorySrv'; +import { DashboardModel } from '../../dashboard_model'; jest.mock('app/core/store'); describe('historySrv', () => { diff --git a/public/app/features/dashboard/history/history_srv.ts b/public/app/features/dashboard/components/VersionHistory/HistorySrv.ts similarity index 96% rename from public/app/features/dashboard/history/history_srv.ts rename to public/app/features/dashboard/components/VersionHistory/HistorySrv.ts index 7f7dc950de3..d52f3ab879c 100644 --- a/public/app/features/dashboard/history/history_srv.ts +++ b/public/app/features/dashboard/components/VersionHistory/HistorySrv.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; import coreModule from 'app/core/core_module'; -import { DashboardModel } from '../dashboard_model'; +import { DashboardModel } from '../../dashboard_model'; export interface HistoryListOpts { limit: number; diff --git a/public/app/features/dashboard/specs/history_mocks.ts b/public/app/features/dashboard/components/VersionHistory/__mocks__/history.ts similarity index 100% rename from public/app/features/dashboard/specs/history_mocks.ts rename to public/app/features/dashboard/components/VersionHistory/__mocks__/history.ts diff --git a/public/app/features/dashboard/components/VersionHistory/index.ts b/public/app/features/dashboard/components/VersionHistory/index.ts new file mode 100644 index 00000000000..138de434bf3 --- /dev/null +++ b/public/app/features/dashboard/components/VersionHistory/index.ts @@ -0,0 +1,2 @@ +export { HistoryListCtrl } from './HistoryListCtrl'; +export { HistorySrv } from './HistorySrv'; diff --git a/public/app/features/dashboard/history/history.html b/public/app/features/dashboard/components/VersionHistory/template.html similarity index 100% rename from public/app/features/dashboard/history/history.html rename to public/app/features/dashboard/components/VersionHistory/template.html diff --git a/public/app/features/dashboard/dashboard_ctrl.ts b/public/app/features/dashboard/dashboard_ctrl.ts index 6611a728803..5c4480dbad5 100644 --- a/public/app/features/dashboard/dashboard_ctrl.ts +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -22,7 +22,6 @@ export class DashboardCtrl { private keybindingSrv, private timeSrv, private variableSrv, - private alertingSrv, private dashboardSrv, private unsavedChangesSrv, private dashboardViewStateSrv, @@ -54,7 +53,6 @@ export class DashboardCtrl { // init services this.timeSrv.init(dashboard); - this.alertingSrv.init(dashboard, data.alerts); this.annotationsSrv.init(dashboard); // template values service needs to initialize completely before diff --git a/public/app/features/dashboard/folder_permissions_ctrl.ts b/public/app/features/dashboard/folder_permissions_ctrl.ts deleted file mode 100644 index 4ab91acb3d9..00000000000 --- a/public/app/features/dashboard/folder_permissions_ctrl.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { FolderPageLoader } from './folder_page_loader'; - -export class FolderPermissionsCtrl { - navModel: any; - folderId: number; - uid: string; - dashboard: any; - meta: any; - - /** @ngInject */ - constructor(private backendSrv, navModelSrv, private $routeParams, $location) { - if (this.$routeParams.uid) { - this.uid = $routeParams.uid; - - new FolderPageLoader(this.backendSrv).load(this, this.uid, 'manage-folder-permissions').then(folder => { - if ($location.path() !== folder.meta.url) { - $location.path(`${folder.meta.url}/permissions`).replace(); - } - - this.dashboard = folder.dashboard; - this.meta = folder.meta; - }); - } - } -} diff --git a/public/app/features/dashboard/index.ts b/public/app/features/dashboard/index.ts new file mode 100644 index 00000000000..79831cd4015 --- /dev/null +++ b/public/app/features/dashboard/index.ts @@ -0,0 +1,35 @@ +import './dashboard_ctrl'; +import './time_srv'; +import './repeat_option/repeat_option'; +import './dashgrid/DashboardGridDirective'; +import './dashgrid/RowOptions'; +import './panellinks/module'; + +// Services +import './services/DashboardViewStateSrv'; +import './services/UnsavedChangesSrv'; +import './services/DashboardLoaderSrv'; +import './services/DashboardSrv'; + +// Components +import './components/DashLinks'; +import './components/DashExportModal'; +import './components/DashNav'; +import './components/ExportDataModal'; +import './components/FolderPicker'; +import './components/VersionHistory'; +import './components/DashboardSettings'; +import './components/SubMenu'; +import './components/TimePicker'; +import './components/UnsavedChangesModal'; +import './components/SaveModals'; +import './components/ShareModal'; +import './components/AdHocFilters'; + +import DashboardPermissions from './components/DashboardPermissions/DashboardPermissions'; + +// angular wrappers +import { react2AngularDirective } from 'app/core/utils/react2angular'; + +react2AngularDirective('dashboardPermissions', DashboardPermissions, ['dashboardId', 'folder']); + diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx index 540b6a8353e..2651ab0608c 100644 --- a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx +++ b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx @@ -166,6 +166,7 @@ export class QueryEditorRow extends PureComponent { onDisableQuery = () => { this.props.query.hide = !this.props.query.hide; + this.onExecuteQuery(); this.forceUpdate(); }; diff --git a/public/app/features/dashboard/specs/change_tracker.test.ts b/public/app/features/dashboard/services/ChangeTracker.test.ts similarity index 97% rename from public/app/features/dashboard/specs/change_tracker.test.ts rename to public/app/features/dashboard/services/ChangeTracker.test.ts index e7f8ce977b1..dfc9b3fa03f 100644 --- a/public/app/features/dashboard/specs/change_tracker.test.ts +++ b/public/app/features/dashboard/services/ChangeTracker.test.ts @@ -1,4 +1,4 @@ -import { ChangeTracker } from 'app/features/dashboard/change_tracker'; +import { ChangeTracker } from './ChangeTracker'; import { contextSrv } from 'app/core/services/context_srv'; import { DashboardModel } from '../dashboard_model'; import { PanelModel } from '../panel_model'; diff --git a/public/app/features/dashboard/change_tracker.ts b/public/app/features/dashboard/services/ChangeTracker.ts similarity index 98% rename from public/app/features/dashboard/change_tracker.ts rename to public/app/features/dashboard/services/ChangeTracker.ts index aa71ac2e306..ef3d456db48 100644 --- a/public/app/features/dashboard/change_tracker.ts +++ b/public/app/features/dashboard/services/ChangeTracker.ts @@ -1,6 +1,6 @@ import angular from 'angular'; import _ from 'lodash'; -import { DashboardModel } from './dashboard_model'; +import { DashboardModel } from '../dashboard_model'; export class ChangeTracker { current: any; diff --git a/public/app/features/dashboard/dashboard_loader_srv.ts b/public/app/features/dashboard/services/DashboardLoaderSrv.ts similarity index 100% rename from public/app/features/dashboard/dashboard_loader_srv.ts rename to public/app/features/dashboard/services/DashboardLoaderSrv.ts diff --git a/public/app/features/dashboard/dashboard_srv.ts b/public/app/features/dashboard/services/DashboardSrv.ts similarity index 98% rename from public/app/features/dashboard/dashboard_srv.ts rename to public/app/features/dashboard/services/DashboardSrv.ts index d5695a577c5..67a4938c6aa 100644 --- a/public/app/features/dashboard/dashboard_srv.ts +++ b/public/app/features/dashboard/services/DashboardSrv.ts @@ -1,5 +1,5 @@ import coreModule from 'app/core/core_module'; -import { DashboardModel } from './dashboard_model'; +import { DashboardModel } from '../dashboard_model'; import locationUtil from 'app/core/utils/location_util'; export class DashboardSrv { diff --git a/public/app/features/dashboard/specs/viewstate_srv.test.ts b/public/app/features/dashboard/services/DashboardViewStateSrv.test.ts similarity index 82% rename from public/app/features/dashboard/specs/viewstate_srv.test.ts rename to public/app/features/dashboard/services/DashboardViewStateSrv.test.ts index f9963afbf85..aee6746ff36 100644 --- a/public/app/features/dashboard/specs/viewstate_srv.test.ts +++ b/public/app/features/dashboard/services/DashboardViewStateSrv.test.ts @@ -1,7 +1,5 @@ -//import { describe, beforeEach, it, expect, angularMocks } from 'test/lib/common'; -import 'app/features/dashboard/view_state_srv'; import config from 'app/core/config'; -import { DashboardViewState } from '../view_state_srv'; +import { DashboardViewStateSrv } from './DashboardViewStateSrv'; import { DashboardModel } from '../dashboard_model'; describe('when updating view state', () => { @@ -33,7 +31,7 @@ describe('when updating view state', () => { location.search = jest.fn(() => { return { fullscreen: true, edit: true, panelId: 1 }; }); - viewState = new DashboardViewState($scope, location, {}); + viewState = new DashboardViewStateSrv($scope, location, {}); }); it('should update querystring and view state', () => { @@ -55,7 +53,7 @@ describe('when updating view state', () => { describe('to fullscreen false', () => { beforeEach(() => { - viewState = new DashboardViewState($scope, location, {}); + viewState = new DashboardViewStateSrv($scope, location, {}); }); it('should remove params from query string', () => { viewState.update({ fullscreen: true, panelId: 1, edit: true }); diff --git a/public/app/features/dashboard/view_state_srv.ts b/public/app/features/dashboard/services/DashboardViewStateSrv.ts similarity index 96% rename from public/app/features/dashboard/view_state_srv.ts rename to public/app/features/dashboard/services/DashboardViewStateSrv.ts index ff12d26233d..cb9794d6abb 100644 --- a/public/app/features/dashboard/view_state_srv.ts +++ b/public/app/features/dashboard/services/DashboardViewStateSrv.ts @@ -2,11 +2,11 @@ import angular from 'angular'; import _ from 'lodash'; import config from 'app/core/config'; import appEvents from 'app/core/app_events'; -import { DashboardModel } from './dashboard_model'; +import { DashboardModel } from '../dashboard_model'; // represents the transient view state // like fullscreen panel & edit -export class DashboardViewState { +export class DashboardViewStateSrv { state: any; panelScopes: any; $scope: any; @@ -168,7 +168,7 @@ export class DashboardViewState { export function dashboardViewStateSrv($location, $timeout) { return { create: $scope => { - return new DashboardViewState($scope, $location, $timeout); + return new DashboardViewStateSrv($scope, $location, $timeout); }, }; } diff --git a/public/app/features/dashboard/unsaved_changes_srv.ts b/public/app/features/dashboard/services/UnsavedChangesSrv.ts similarity index 89% rename from public/app/features/dashboard/unsaved_changes_srv.ts rename to public/app/features/dashboard/services/UnsavedChangesSrv.ts index f0a8bf40501..2691cc6ebf8 100644 --- a/public/app/features/dashboard/unsaved_changes_srv.ts +++ b/public/app/features/dashboard/services/UnsavedChangesSrv.ts @@ -1,5 +1,5 @@ import angular from 'angular'; -import { ChangeTracker } from './change_tracker'; +import { ChangeTracker } from './ChangeTracker'; /** @ngInject */ export function unsavedChangesSrv(this: any, $rootScope, $q, $location, $timeout, contextSrv, dashboardSrv, $window) { diff --git a/public/app/features/dashboard/utils/panel.ts b/public/app/features/dashboard/utils/panel.ts index 00c960bdfaa..cfbe094125f 100644 --- a/public/app/features/dashboard/utils/panel.ts +++ b/public/app/features/dashboard/utils/panel.ts @@ -80,7 +80,7 @@ export const editPanelJson = (dashboard: DashboardModel, panel: PanelModel) => { export const sharePanel = (dashboard: DashboardModel, panel: PanelModel) => { appEvents.emit('show-modal', { - src: 'public/app/features/dashboard/partials/shareModal.html', + src: 'public/app/features/dashboard/components/ShareModal/template.html', model: { dashboard: dashboard, panel: panel, diff --git a/public/app/features/explore/TableContainer.tsx b/public/app/features/explore/TableContainer.tsx index 1d00a441e14..ed26ce5147f 100644 --- a/public/app/features/explore/TableContainer.tsx +++ b/public/app/features/explore/TableContainer.tsx @@ -5,7 +5,7 @@ import { connect } from 'react-redux'; import { ExploreId, ExploreItemState } from 'app/types/explore'; import { StoreState } from 'app/types'; -import { toggleGraph } from './state/actions'; +import { toggleTable } from './state/actions'; import Table from './Table'; import Panel from './Panel'; import TableModel from 'app/core/table_model'; @@ -16,12 +16,12 @@ interface TableContainerProps { onClickCell: (key: string, value: string) => void; showingTable: boolean; tableResult?: TableModel; - toggleGraph: typeof toggleGraph; + toggleTable: typeof toggleTable; } export class TableContainer extends PureComponent { onClickTableButton = () => { - this.props.toggleGraph(this.props.exploreId); + this.props.toggleTable(this.props.exploreId); }; render() { @@ -43,7 +43,7 @@ function mapStateToProps(state: StoreState, { exploreId }) { } const mapDispatchToProps = { - toggleGraph, + toggleTable, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(TableContainer)); diff --git a/public/app/features/explore/state/reducers.test.ts b/public/app/features/explore/state/reducers.test.ts new file mode 100644 index 00000000000..8227a947c5b --- /dev/null +++ b/public/app/features/explore/state/reducers.test.ts @@ -0,0 +1,42 @@ +import { Action, ActionTypes } from './actionTypes'; +import { itemReducer, makeExploreItemState } from './reducers'; +import { ExploreId } from 'app/types/explore'; + +describe('Explore item reducer', () => { + describe('scanning', () => { + test('should start scanning', () => { + let state = makeExploreItemState(); + const action: Action = { + type: ActionTypes.ScanStart, + payload: { + exploreId: ExploreId.left, + scanner: jest.fn(), + }, + }; + state = itemReducer(state, action); + expect(state.scanning).toBeTruthy(); + expect(state.scanner).toBe(action.payload.scanner); + }); + test('should stop scanning', () => { + let state = makeExploreItemState(); + const start: Action = { + type: ActionTypes.ScanStart, + payload: { + exploreId: ExploreId.left, + scanner: jest.fn(), + }, + }; + state = itemReducer(state, start); + expect(state.scanning).toBeTruthy(); + const action: Action = { + type: ActionTypes.ScanStop, + payload: { + exploreId: ExploreId.left, + }, + }; + state = itemReducer(state, action); + expect(state.scanning).toBeFalsy(); + expect(state.scanner).toBeUndefined(); + }); + }); +}); diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 8acf52340c9..8885f972d06 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -20,7 +20,7 @@ const DEFAULT_GRAPH_INTERVAL = 15 * 1000; /** * Returns a fresh Explore area state */ -const makeExploreItemState = (): ExploreItemState => ({ +export const makeExploreItemState = (): ExploreItemState => ({ StartPage: undefined, containerWidth: 0, datasourceInstance: null, @@ -48,7 +48,7 @@ const makeExploreItemState = (): ExploreItemState => ({ /** * Global Explore state that handles multiple Explore areas and the split state */ -const initialExploreState: ExploreState = { +export const initialExploreState: ExploreState = { split: null, left: makeExploreItemState(), right: makeExploreItemState(), @@ -57,7 +57,7 @@ const initialExploreState: ExploreState = { /** * Reducer for an Explore area, to be used by the global Explore reducer. */ -const itemReducer = (state, action: Action): ExploreItemState => { +export const itemReducer = (state, action: Action): ExploreItemState => { switch (action.type) { case ActionTypes.AddQueryRow: { const { initialQueries, modifiedQueries, queryTransactions } = state; @@ -360,13 +360,19 @@ const itemReducer = (state, action: Action): ExploreItemState => { } case ActionTypes.ScanStart: { - return { ...state, scanning: true }; + return { ...state, scanning: true, scanner: action.payload.scanner }; } case ActionTypes.ScanStop: { const { queryTransactions } = state; const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done); - return { ...state, queryTransactions: nextQueryTransactions, scanning: false, scanRange: undefined }; + return { + ...state, + queryTransactions: nextQueryTransactions, + scanning: false, + scanRange: undefined, + scanner: undefined, + }; } case ActionTypes.SetQueries: { diff --git a/public/app/features/dashboard/create_folder_ctrl.ts b/public/app/features/manage-dashboards/CreateFolderCtrl.ts similarity index 100% rename from public/app/features/dashboard/create_folder_ctrl.ts rename to public/app/features/manage-dashboards/CreateFolderCtrl.ts diff --git a/public/app/features/dashboard/specs/dashboard_import_ctrl.test.ts b/public/app/features/manage-dashboards/DashboardImportCtrl.test.ts similarity index 95% rename from public/app/features/dashboard/specs/dashboard_import_ctrl.test.ts rename to public/app/features/manage-dashboards/DashboardImportCtrl.test.ts index bcde009cb3a..c9037c0a62d 100644 --- a/public/app/features/dashboard/specs/dashboard_import_ctrl.test.ts +++ b/public/app/features/manage-dashboards/DashboardImportCtrl.test.ts @@ -1,5 +1,5 @@ -import { DashboardImportCtrl } from '../dashboard_import_ctrl'; -import config from '../../../core/config'; +import { DashboardImportCtrl } from './DashboardImportCtrl'; +import config from 'app/core/config'; describe('DashboardImportCtrl', () => { const ctx: any = {}; diff --git a/public/app/features/dashboard/dashboard_import_ctrl.ts b/public/app/features/manage-dashboards/DashboardImportCtrl.ts similarity index 100% rename from public/app/features/dashboard/dashboard_import_ctrl.ts rename to public/app/features/manage-dashboards/DashboardImportCtrl.ts diff --git a/public/app/features/dashboard/folder_dashboards_ctrl.ts b/public/app/features/manage-dashboards/FolderDashboardsCtrl.ts similarity index 90% rename from public/app/features/dashboard/folder_dashboards_ctrl.ts rename to public/app/features/manage-dashboards/FolderDashboardsCtrl.ts index 05cc420c489..6241472432c 100644 --- a/public/app/features/dashboard/folder_dashboards_ctrl.ts +++ b/public/app/features/manage-dashboards/FolderDashboardsCtrl.ts @@ -1,4 +1,4 @@ -import { FolderPageLoader } from './folder_page_loader'; +import { FolderPageLoader } from './services/FolderPageLoader'; import locationUtil from 'app/core/utils/location_util'; export class FolderDashboardsCtrl { diff --git a/public/app/features/dashboard/move_to_folder_modal/move_to_folder.ts b/public/app/features/manage-dashboards/components/MoveToFolderModal/MoveToFolderCtrl.ts similarity index 93% rename from public/app/features/dashboard/move_to_folder_modal/move_to_folder.ts rename to public/app/features/manage-dashboards/components/MoveToFolderModal/MoveToFolderCtrl.ts index 075583b971b..c183f38d92a 100644 --- a/public/app/features/dashboard/move_to_folder_modal/move_to_folder.ts +++ b/public/app/features/manage-dashboards/components/MoveToFolderModal/MoveToFolderCtrl.ts @@ -46,7 +46,7 @@ export class MoveToFolderCtrl { export function moveToFolderModal() { return { restrict: 'E', - templateUrl: 'public/app/features/dashboard/move_to_folder_modal/move_to_folder.html', + templateUrl: 'public/app/features/manage-dashboards/components/MoveToFolderModal/template.html', controller: MoveToFolderCtrl, bindToController: true, controllerAs: 'ctrl', diff --git a/public/app/features/manage-dashboards/components/MoveToFolderModal/index.ts b/public/app/features/manage-dashboards/components/MoveToFolderModal/index.ts new file mode 100644 index 00000000000..df0553aedb9 --- /dev/null +++ b/public/app/features/manage-dashboards/components/MoveToFolderModal/index.ts @@ -0,0 +1 @@ +export { MoveToFolderCtrl } from './MoveToFolderCtrl'; diff --git a/public/app/features/dashboard/move_to_folder_modal/move_to_folder.html b/public/app/features/manage-dashboards/components/MoveToFolderModal/template.html similarity index 100% rename from public/app/features/dashboard/move_to_folder_modal/move_to_folder.html rename to public/app/features/manage-dashboards/components/MoveToFolderModal/template.html diff --git a/public/app/features/manage-dashboards/components/UploadDashboard/index.ts b/public/app/features/manage-dashboards/components/UploadDashboard/index.ts new file mode 100644 index 00000000000..828b4f76982 --- /dev/null +++ b/public/app/features/manage-dashboards/components/UploadDashboard/index.ts @@ -0,0 +1 @@ +export { uploadDashboardDirective } from './uploadDashboardDirective'; diff --git a/public/app/features/dashboard/upload.ts b/public/app/features/manage-dashboards/components/UploadDashboard/uploadDashboardDirective.ts similarity index 96% rename from public/app/features/dashboard/upload.ts rename to public/app/features/manage-dashboards/components/UploadDashboard/uploadDashboardDirective.ts index ec4ad9a03cb..0c38a1247f1 100644 --- a/public/app/features/dashboard/upload.ts +++ b/public/app/features/manage-dashboards/components/UploadDashboard/uploadDashboardDirective.ts @@ -11,7 +11,7 @@ const template = ` `; /** @ngInject */ -function uploadDashboardDirective(timer, $location) { +export function uploadDashboardDirective(timer, $location) { return { restrict: 'E', template: template, diff --git a/public/app/features/manage-dashboards/index.ts b/public/app/features/manage-dashboards/index.ts index 046740904e1..c3830ee6546 100644 --- a/public/app/features/manage-dashboards/index.ts +++ b/public/app/features/manage-dashboards/index.ts @@ -1,7 +1,21 @@ -import coreModule from 'app/core/core_module'; +// Services +export { ValidationSrv } from './services/ValidationSrv'; +// Components +export * from './components/MoveToFolderModal'; +export * from './components/UploadDashboard'; + +// Controllers import { DashboardListCtrl } from './DashboardListCtrl'; import { SnapshotListCtrl } from './SnapshotListCtrl'; +import { FolderDashboardsCtrl } from './FolderDashboardsCtrl'; +import { DashboardImportCtrl } from './DashboardImportCtrl'; +import { CreateFolderCtrl } from './CreateFolderCtrl'; + +import coreModule from 'app/core/core_module'; coreModule.controller('DashboardListCtrl', DashboardListCtrl); coreModule.controller('SnapshotListCtrl', SnapshotListCtrl); +coreModule.controller('FolderDashboardsCtrl', FolderDashboardsCtrl); +coreModule.controller('DashboardImportCtrl', DashboardImportCtrl); +coreModule.controller('CreateFolderCtrl', CreateFolderCtrl); diff --git a/public/app/features/dashboard/folder_page_loader.ts b/public/app/features/manage-dashboards/services/FolderPageLoader.ts similarity index 100% rename from public/app/features/dashboard/folder_page_loader.ts rename to public/app/features/manage-dashboards/services/FolderPageLoader.ts diff --git a/public/app/features/dashboard/validation_srv.ts b/public/app/features/manage-dashboards/services/ValidationSrv.ts similarity index 100% rename from public/app/features/dashboard/validation_srv.ts rename to public/app/features/manage-dashboards/services/ValidationSrv.ts diff --git a/public/app/features/playlist/playlist_srv.ts b/public/app/features/playlist/playlist_srv.ts index 9d3b635a1e5..0a80ce0cdf0 100644 --- a/public/app/features/playlist/playlist_srv.ts +++ b/public/app/features/playlist/playlist_srv.ts @@ -4,12 +4,13 @@ import appEvents from 'app/core/app_events'; import _ from 'lodash'; import { toUrlParams } from 'app/core/utils/url'; -class PlaylistSrv { +export class PlaylistSrv { private cancelPromise: any; - private dashboards: any; + private dashboards: Array<{ uri: string }>; private index: number; - private interval: any; + private interval: number; private startUrl: string; + private numberOfLoops = 0; isPlaying: boolean; /** @ngInject */ @@ -20,8 +21,15 @@ class PlaylistSrv { const playedAllDashboards = this.index > this.dashboards.length - 1; if (playedAllDashboards) { - window.location.href = this.startUrl; - return; + this.numberOfLoops++; + + // This does full reload of the playlist to keep memory in check due to existing leaks but at the same time + // we do not want page to flicker after each full loop. + if (this.numberOfLoops >= 3) { + window.location.href = this.startUrl; + return; + } + this.index = 0; } const dash = this.dashboards[this.index]; @@ -46,8 +54,8 @@ class PlaylistSrv { this.index = 0; this.isPlaying = true; - this.backendSrv.get(`/api/playlists/${playlistId}`).then(playlist => { - this.backendSrv.get(`/api/playlists/${playlistId}/dashboards`).then(dashboards => { + return this.backendSrv.get(`/api/playlists/${playlistId}`).then(playlist => { + return this.backendSrv.get(`/api/playlists/${playlistId}/dashboards`).then(dashboards => { this.dashboards = dashboards; this.interval = kbn.interval_to_ms(playlist.interval); this.next(); diff --git a/public/app/features/playlist/specs/playlist_srv.test.ts b/public/app/features/playlist/specs/playlist_srv.test.ts new file mode 100644 index 00000000000..e6b7671c964 --- /dev/null +++ b/public/app/features/playlist/specs/playlist_srv.test.ts @@ -0,0 +1,103 @@ +import { PlaylistSrv } from '../playlist_srv'; + +const dashboards = [{ uri: 'dash1' }, { uri: 'dash2' }]; + +const createPlaylistSrv = (): [PlaylistSrv, { url: jest.MockInstance }] => { + const mockBackendSrv = { + get: jest.fn(url => { + switch (url) { + case '/api/playlists/1': + return Promise.resolve({ interval: '1s' }); + case '/api/playlists/1/dashboards': + return Promise.resolve(dashboards); + default: + throw new Error(`Unexpected url=${url}`); + } + }), + }; + + const mockLocation = { + url: jest.fn(), + search: () => ({}), + }; + + const mockTimeout = jest.fn(); + (mockTimeout as any).cancel = jest.fn(); + + return [new PlaylistSrv(mockLocation, mockTimeout, mockBackendSrv), mockLocation]; +}; + +const mockWindowLocation = (): [jest.MockInstance, () => void] => { + const oldLocation = window.location; + const hrefMock = jest.fn(); + + // JSDom defines window in a way that you cannot tamper with location so this seems to be the only way to change it. + // https://github.com/facebook/jest/issues/5124#issuecomment-446659510 + delete window.location; + window.location = {} as any; + + // Only mocking href as that is all this test needs, but otherwise there is lots of things missing, so keep that + // in mind if this is reused. + Object.defineProperty(window.location, 'href', { + set: hrefMock, + get: hrefMock, + }); + const unmock = () => { + window.location = oldLocation; + }; + return [hrefMock, unmock]; +}; + +describe('PlaylistSrv', () => { + let srv: PlaylistSrv; + let mockLocationService: { url: jest.MockInstance }; + let hrefMock: jest.MockInstance; + let unmockLocation: () => void; + const initialUrl = 'http://localhost/playlist'; + + beforeEach(() => { + [srv, mockLocationService] = createPlaylistSrv(); + [hrefMock, unmockLocation] = mockWindowLocation(); + + // This will be cached in the srv when start() is called + hrefMock.mockReturnValue(initialUrl); + }); + + afterEach(() => { + unmockLocation(); + }); + + it('runs all dashboards in cycle and reloads page after 3 cycles', async () => { + await srv.start(1); + + for (let i = 0; i < 6; i++) { + expect(mockLocationService.url).toHaveBeenLastCalledWith(`dashboard/${dashboards[i % 2].uri}?`); + srv.next(); + } + + expect(hrefMock).toHaveBeenCalledTimes(2); + expect(hrefMock).toHaveBeenLastCalledWith(initialUrl); + }); + + it('keeps the refresh counter value after restarting', async () => { + await srv.start(1); + + // 1 complete loop + for (let i = 0; i < 3; i++) { + expect(mockLocationService.url).toHaveBeenLastCalledWith(`dashboard/${dashboards[i % 2].uri}?`); + srv.next(); + } + + srv.stop(); + await srv.start(1); + + // Another 2 loops + for (let i = 0; i < 4; i++) { + expect(mockLocationService.url).toHaveBeenLastCalledWith(`dashboard/${dashboards[i % 2].uri}?`); + srv.next(); + } + + expect(hrefMock).toHaveBeenCalledTimes(3); + expect(hrefMock).toHaveBeenLastCalledWith(initialUrl); + }); +}); diff --git a/public/app/features/templating/specs/template_srv.test.ts b/public/app/features/templating/specs/template_srv.test.ts index 7805341d1a2..4288b5f3928 100644 --- a/public/app/features/templating/specs/template_srv.test.ts +++ b/public/app/features/templating/specs/template_srv.test.ts @@ -275,6 +275,11 @@ describe('templateSrv', () => { expect(result).toBe('test,test2'); }); + it('multi value and percentencode format should render percent-encoded string', () => { + const result = _templateSrv.formatValue(['foo()bar BAZ', 'test2'], 'percentencode'); + expect(result).toBe('%7Bfoo%28%29bar%20BAZ%2Ctest2%7D'); + }); + it('slash should be properly escaped in regex format', () => { const result = _templateSrv.formatValue('Gi3/14', 'regex'); expect(result).toBe('Gi3\\/14'); diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index 74da017bb93..07656924c9c 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -77,6 +77,15 @@ export class TemplateSrv { return '(' + quotedValues.join(' OR ') + ')'; } + // encode string according to RFC 3986; in contrast to encodeURIComponent() + // also the sub-delims "!", "'", "(", ")" and "*" are encoded; + // unicode handling uses UTF-8 as in ECMA-262. + encodeURIComponentStrict(str) { + return encodeURIComponent(str).replace(/[!'()*]/g, (c) => { + return '%' + c.charCodeAt(0).toString(16).toUpperCase(); + }); + } + formatValue(value, format, variable) { // for some scopedVars there is no variable variable = variable || {}; @@ -118,6 +127,13 @@ export class TemplateSrv { } return value; } + case 'percentencode': { + // like glob, but url escaped + if (_.isArray(value)) { + return this.encodeURIComponentStrict('{' + value.join(',') + '}'); + } + return this.encodeURIComponentStrict(value); + } default: { if (_.isArray(value)) { return '{' + value.join(',') + '}'; diff --git a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx index 49f6b74e8b6..a7b865cde3f 100644 --- a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx +++ b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx @@ -26,7 +26,7 @@ export default (props: any) => (

Loki Cheat Sheet

{CHEAT_SHEET_ITEMS.map(item => ( -
+
{item.title}
{item.expression && (
\n Home Dashboard\n
", - "editable": true, - "id": 1, - "links": [], - "mode": "html", - "style": {}, - "title": "", - "transparent": true, - "type": "text", - "gridPos": { - "w": 24, - "h": 3, - "x": 0, - "y": 0 - } - }, { "folderId": 0, "headings": true, @@ -45,7 +28,7 @@ "w": 12, "h": 17, "x": 0, - "y": 6 + "y": 1 } }, { @@ -60,7 +43,7 @@ "w": 12, "h": 17, "x": 12, - "y": 6 + "y": 1 } } ], diff --git a/style_guides/frontend.md b/style_guides/frontend.md new file mode 100644 index 00000000000..8d0849506a3 --- /dev/null +++ b/style_guides/frontend.md @@ -0,0 +1,62 @@ +# Frontend Style Guide + +Generally we follow the Airbnb [React Style Guide](https://github.com/airbnb/javascript/tree/master/react). + +## Table of Contents + + 1. [Basic Rules](#basic-rules) + 1. [File & Component Organization](#Organization) + 1. [Naming](#naming) + 1. [Declaration](#declaration) + 1. [Props](#props) + 1. [Refs](#refs) + 1. [Methods](#methods) + 1. [Ordering](#ordering) + +## Basic rules + +* Try to keep files small and focused and break large components up into sub components. + +## Organization + +* Components and types that needs to be used by external plugins needs to go into @grafana/ui +* Components should get their own folder under features/xxx/components + * Sub components can live in that component folders, so not small component needs their own folder + * Place test next to their component file (same dir) + * Mocks in __mocks__ dir + * Test utils in __tests__ dir + * Component sass should live in the same folder as component code +* State logic & domain models should live in features/xxx/state +* Containers (pages) can live in feature root features/xxx + * up for debate? + +## Props + +* Name callback props & handlers with a "on" prefix. + +```tsx +// good +onChange = () => { + +}; + +render() { + return ( + + ); +} + +// bad +handleChange = () => { + +}; + +render() { + return ( + + ); +} +``` + + +