initDashboard.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. import { locationUtil, setWeekStart } from '@grafana/data';
  2. import { config, locationService } from '@grafana/runtime';
  3. import { notifyApp } from 'app/core/actions';
  4. import { createErrorNotification } from 'app/core/copy/appNotification';
  5. import { backendSrv } from 'app/core/services/backend_srv';
  6. import { keybindingSrv } from 'app/core/services/keybindingSrv';
  7. import store from 'app/core/store';
  8. import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
  9. import { DashboardSrv, getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
  10. import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
  11. import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
  12. import { toStateKey } from 'app/features/variables/utils';
  13. import { DashboardDTO, DashboardInitPhase, DashboardRoutes, StoreState, ThunkDispatch, ThunkResult } from 'app/types';
  14. import { createDashboardQueryRunner } from '../../query/state/DashboardQueryRunner/DashboardQueryRunner';
  15. import { initVariablesTransaction } from '../../variables/state/actions';
  16. import { getIfExistsLastKey } from '../../variables/state/selectors';
  17. import { DashboardModel } from './DashboardModel';
  18. import { emitDashboardViewEvent } from './analyticsProcessor';
  19. import { dashboardInitCompleted, dashboardInitFailed, dashboardInitFetching, dashboardInitServices } from './reducers';
  20. export interface InitDashboardArgs {
  21. urlUid?: string;
  22. urlSlug?: string;
  23. urlType?: string;
  24. urlFolderId?: string | null;
  25. routeName?: string;
  26. fixUrl: boolean;
  27. }
  28. async function fetchDashboard(
  29. args: InitDashboardArgs,
  30. dispatch: ThunkDispatch,
  31. getState: () => StoreState
  32. ): Promise<DashboardDTO | null> {
  33. // When creating new or adding panels to a dashboard from explore we load it from local storage
  34. const model = store.getObject<DashboardDTO>(DASHBOARD_FROM_LS_KEY);
  35. if (model) {
  36. removeDashboardToFetchFromLocalStorage();
  37. return model;
  38. }
  39. try {
  40. switch (args.routeName) {
  41. case DashboardRoutes.Home: {
  42. // load home dash
  43. const dashDTO: DashboardDTO = await backendSrv.get('/api/dashboards/home');
  44. // if user specified a custom home dashboard redirect to that
  45. if (dashDTO.redirectUri) {
  46. const newUrl = locationUtil.stripBaseFromUrl(dashDTO.redirectUri);
  47. locationService.replace(newUrl);
  48. return null;
  49. }
  50. // disable some actions on the default home dashboard
  51. dashDTO.meta.canSave = false;
  52. dashDTO.meta.canShare = false;
  53. dashDTO.meta.canStar = false;
  54. return dashDTO;
  55. }
  56. case DashboardRoutes.Normal: {
  57. const dashDTO: DashboardDTO = await dashboardLoaderSrv.loadDashboard(args.urlType, args.urlSlug, args.urlUid);
  58. if (args.fixUrl && dashDTO.meta.url) {
  59. // check if the current url is correct (might be old slug)
  60. const dashboardUrl = locationUtil.stripBaseFromUrl(dashDTO.meta.url);
  61. const currentPath = locationService.getLocation().pathname;
  62. if (dashboardUrl !== currentPath) {
  63. // Spread current location to persist search params used for navigation
  64. locationService.replace({
  65. ...locationService.getLocation(),
  66. pathname: dashboardUrl,
  67. });
  68. console.log('not correct url correcting', dashboardUrl, currentPath);
  69. }
  70. }
  71. return dashDTO;
  72. }
  73. case DashboardRoutes.New: {
  74. return getNewDashboardModelData(args.urlFolderId);
  75. }
  76. default:
  77. throw { message: 'Unknown route ' + args.routeName };
  78. }
  79. } catch (err) {
  80. // Ignore cancelled errors
  81. if (err.cancelled) {
  82. return null;
  83. }
  84. dispatch(dashboardInitFailed({ message: 'Failed to fetch dashboard', error: err }));
  85. console.error(err);
  86. return null;
  87. }
  88. }
  89. /**
  90. * This action (or saga) does everything needed to bootstrap a dashboard & dashboard model.
  91. * First it handles the process of fetching the dashboard, correcting the url if required (causing redirects/url updates)
  92. *
  93. * This is used both for single dashboard & solo panel routes, home & new dashboard routes.
  94. *
  95. * Then it handles the initializing of the old angular services that the dashboard components & panels still depend on
  96. *
  97. */
  98. export function initDashboard(args: InitDashboardArgs): ThunkResult<void> {
  99. return async (dispatch, getState) => {
  100. // set fetching state
  101. dispatch(dashboardInitFetching());
  102. // fetch dashboard data
  103. const dashDTO = await fetchDashboard(args, dispatch, getState);
  104. // returns null if there was a redirect or error
  105. if (!dashDTO) {
  106. return;
  107. }
  108. // set initializing state
  109. dispatch(dashboardInitServices());
  110. // create model
  111. let dashboard: DashboardModel;
  112. try {
  113. dashboard = new DashboardModel(dashDTO.dashboard, dashDTO.meta);
  114. } catch (err) {
  115. dispatch(dashboardInitFailed({ message: 'Failed create dashboard model', error: err }));
  116. console.error(err);
  117. return;
  118. }
  119. // add missing orgId query param
  120. const storeState = getState();
  121. const queryParams = locationService.getSearchObject();
  122. if (!queryParams.orgId) {
  123. // TODO this is currently not possible with the LocationService API
  124. locationService.partial({ orgId: storeState.user.orgId }, true);
  125. }
  126. // init services
  127. const timeSrv: TimeSrv = getTimeSrv();
  128. const dashboardSrv: DashboardSrv = getDashboardSrv();
  129. // legacy srv state, we need this value updated for built-in annotations
  130. dashboardSrv.setCurrent(dashboard);
  131. timeSrv.init(dashboard);
  132. const dashboardUid = toStateKey(args.urlUid ?? dashboard.uid);
  133. // template values service needs to initialize completely before the rest of the dashboard can load
  134. await dispatch(initVariablesTransaction(dashboardUid, dashboard));
  135. // DashboardQueryRunner needs to run after all variables have been resolved so that any annotation query including a variable
  136. // will be correctly resolved
  137. const runner = createDashboardQueryRunner({ dashboard, timeSrv });
  138. runner.run({ dashboard, range: timeSrv.timeRange() });
  139. if (getIfExistsLastKey(getState()) !== dashboardUid) {
  140. // if a previous dashboard has slow running variable queries the batch uid will be the new one
  141. // but the args.urlUid will be the same as before initVariablesTransaction was called so then we can't continue initializing
  142. // the previous dashboard.
  143. return;
  144. }
  145. // If dashboard is in a different init phase it means it cancelled during service init
  146. if (getState().dashboard.initPhase !== DashboardInitPhase.Services) {
  147. return;
  148. }
  149. try {
  150. dashboard.processRepeats();
  151. // handle auto fix experimental feature
  152. if (queryParams.autofitpanels) {
  153. dashboard.autoFitPanels(window.innerHeight, queryParams.kiosk);
  154. }
  155. keybindingSrv.setupDashboardBindings(dashboard);
  156. } catch (err) {
  157. dispatch(notifyApp(createErrorNotification('Dashboard init failed', err)));
  158. console.error(err);
  159. }
  160. // send open dashboard event
  161. if (args.routeName !== DashboardRoutes.New) {
  162. emitDashboardViewEvent(dashboard);
  163. // Listen for changes on the current dashboard
  164. dashboardWatcher.watch(dashboard.uid);
  165. } else {
  166. dashboardWatcher.leave();
  167. }
  168. // set week start
  169. if (dashboard.weekStart !== '') {
  170. setWeekStart(dashboard.weekStart);
  171. } else {
  172. setWeekStart(config.bootData.user.weekStart);
  173. }
  174. // yay we are done
  175. dispatch(dashboardInitCompleted(dashboard));
  176. };
  177. }
  178. export function getNewDashboardModelData(urlFolderId?: string | null): any {
  179. const data = {
  180. meta: {
  181. canStar: false,
  182. canShare: false,
  183. canDelete: false,
  184. isNew: true,
  185. folderId: 0,
  186. },
  187. dashboard: {
  188. title: 'New dashboard',
  189. panels: [
  190. {
  191. type: 'add-panel',
  192. gridPos: { x: 0, y: 0, w: 12, h: 9 },
  193. title: 'Panel Title',
  194. },
  195. ],
  196. },
  197. };
  198. if (urlFolderId) {
  199. data.meta.folderId = parseInt(urlFolderId, 10);
  200. }
  201. return data;
  202. }
  203. const DASHBOARD_FROM_LS_KEY = 'DASHBOARD_FROM_LS_KEY';
  204. export function setDashboardToFetchFromLocalStorage(model: DashboardDTO) {
  205. store.setObject(DASHBOARD_FROM_LS_KEY, model);
  206. }
  207. export function removeDashboardToFetchFromLocalStorage() {
  208. store.delete(DASHBOARD_FROM_LS_KEY);
  209. }