Browse Source

tech: remove all mobx stuff

Torkel Ödegaard 7 years ago
parent
commit
e58c2ebc1c

+ 0 - 4
package.json

@@ -60,7 +60,6 @@
     "lint-staged": "^6.0.0",
     "load-grunt-tasks": "3.5.2",
     "mini-css-extract-plugin": "^0.4.0",
-    "mobx-react-devtools": "^4.2.15",
     "mocha": "^4.0.1",
     "ng-annotate-loader": "^0.6.1",
     "ng-annotate-webpack-plugin": "^0.3.0",
@@ -146,9 +145,6 @@
     "immutable": "^3.8.2",
     "jquery": "^3.2.1",
     "lodash": "^4.17.10",
-    "mobx": "^3.4.1",
-    "mobx-react": "^4.3.5",
-    "mobx-state-tree": "^1.3.1",
     "moment": "^2.22.2",
     "mousetrap": "^1.6.0",
     "mousetrap-global-bind": "^1.1.0",

+ 1 - 4
public/app/core/components/PageHeader/PageHeader.tsx

@@ -1,9 +1,7 @@
 import React from 'react';
-import { observer } from 'mobx-react';
 import { NavModel, NavModelItem } from 'app/types';
 import classNames from 'classnames';
 import appEvents from 'app/core/app_events';
-import { toJS } from 'mobx';
 
 export interface Props {
   model: NavModel;
@@ -81,7 +79,6 @@ const Navigation = ({ main }: { main: NavModelItem }) => {
   );
 };
 
-@observer
 export default class PageHeader extends React.Component<Props, any> {
   constructor(props) {
     super(props);
@@ -148,7 +145,7 @@ export default class PageHeader extends React.Component<Props, any> {
       return null;
     }
 
-    const main = toJS(model.main); // Convert to JS if its a mobx observable
+    const main = model.main;
 
     return (
       <div className="page-header-canvas">

+ 2 - 2
public/app/core/components/PermissionList/DisabledPermissionListItem.tsx

@@ -1,6 +1,6 @@
 import React, { Component } from 'react';
 import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker';
-import { permissionOptions } from 'app/stores/PermissionsStore/PermissionsStore';
+import { dashboardPermissionLevels } from 'app/types/acl';
 
 export interface Props {
   item: any;
@@ -24,7 +24,7 @@ export default class DisabledPermissionListItem extends Component<Props, any> {
         <td>
           <div className="gf-form">
             <DescriptionPicker
-              optionsWithDesc={permissionOptions}
+              optionsWithDesc={dashboardPermissionLevels}
               onSelected={() => {}}
               value={item.permission}
               disabled={true}

+ 1 - 3
public/app/core/components/grafana_app.ts

@@ -6,11 +6,10 @@ import coreModule from 'app/core/core_module';
 import { profiler } from 'app/core/profiler';
 import appEvents from 'app/core/app_events';
 import Drop from 'tether-drop';
-import { createStore } from 'app/stores/store';
 import colors from 'app/core/utils/colors';
 import { BackendSrv, setBackendSrv } from 'app/core/services/backend_srv';
 import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
-import { configureStore } from 'app/stores/configureStore';
+import { configureStore } from 'app/store/configureStore';
 
 export class GrafanaCtrl {
   /** @ngInject */
@@ -28,7 +27,6 @@ export class GrafanaCtrl {
     // sets singleston instances for angular services so react components can access them
     configureStore();
     setBackendSrv(backendSrv);
-    createStore({ backendSrv, datasourceSrv });
 
     $scope.init = () => {
       $scope.contextSrv = contextSrv;

+ 6 - 27
public/app/core/services/bridge_srv.ts

@@ -1,8 +1,6 @@
 import coreModule from 'app/core/core_module';
 import appEvents from 'app/core/app_events';
-import { store } from 'app/stores/store';
-import { store as reduxStore } from 'app/stores/configureStore';
-import { reaction } from 'mobx';
+import { store } from 'app/store/configureStore';
 import locationUtil from 'app/core/utils/location_util';
 import { updateLocation } from 'app/core/actions';
 
@@ -18,12 +16,9 @@ export class BridgeSrv {
   init() {
     this.$rootScope.$on('$routeUpdate', (evt, data) => {
       const angularUrl = this.$location.url();
-      if (store.view.currentUrl !== angularUrl) {
-        store.view.updatePathAndQuery(this.$location.path(), this.$location.search(), this.$route.current.params);
-      }
-      const state = reduxStore.getState();
+      const state = store.getState();
       if (state.location.url !== angularUrl) {
-        reduxStore.dispatch(
+        store.dispatch(
           updateLocation({
             path: this.$location.path(),
             query: this.$location.search(),
@@ -34,8 +29,7 @@ export class BridgeSrv {
     });
 
     this.$rootScope.$on('$routeChangeSuccess', (evt, data) => {
-      store.view.updatePathAndQuery(this.$location.path(), this.$location.search(), this.$route.current.params);
-      reduxStore.dispatch(
+      store.dispatch(
         updateLocation({
           path: this.$location.path(),
           query: this.$location.search(),
@@ -44,24 +38,9 @@ export class BridgeSrv {
       );
     });
 
-    // listen for mobx store changes and update angular
-    reaction(
-      () => store.view.currentUrl,
-      currentUrl => {
-        const angularUrl = this.$location.url();
-        const url = locationUtil.stripBaseFromUrl(currentUrl);
-        if (angularUrl !== url) {
-          this.$timeout(() => {
-            this.$location.url(url);
-          });
-          console.log('store updating angular $location.url', url);
-        }
-      }
-    );
-
     // Listen for changes in redux location -> update angular location
-    reduxStore.subscribe(() => {
-      const state = reduxStore.getState();
+    store.subscribe(() => {
+      const state = store.getState();
       const angularUrl = this.$location.url();
       const url = locationUtil.stripBaseFromUrl(state.location.url);
       if (angularUrl !== url) {

+ 1 - 2
public/app/features/dashboard/DashboardPermissions/DashboardPermissions.tsx

@@ -13,7 +13,7 @@ import {
 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 { store } from 'app/stores/configureStore';
+import { store } from 'app/store/configureStore';
 
 export interface Props {
   dashboardId: number;
@@ -65,7 +65,6 @@ export class DashboardPermissions extends PureComponent<Props, State> {
   render() {
     const { permissions, folder } = this.props;
     const { isAdding } = this.state;
-    console.log('DashboardPermissions', this.props);
 
     return (
       <div>

+ 6 - 9
public/app/features/plugins/ds_dashboards_ctrl.ts

@@ -1,6 +1,7 @@
-import { toJS } from 'mobx';
 import { coreModule } from 'app/core/core';
-import { store } from 'app/stores/store';
+import { store } from 'app/store/configureStore';
+import { getNavModel } from 'app/core/selectors/navModel';
+import { buildNavModel } from './state/navModel';
 
 export class DataSourceDashboardsCtrl {
   datasourceMeta: any;
@@ -9,11 +10,8 @@ export class DataSourceDashboardsCtrl {
 
   /** @ngInject */
   constructor(private backendSrv, private $routeParams) {
-    if (store.nav.main === null) {
-      store.nav.load('cfg', 'datasources');
-    }
-
-    this.navModel = toJS(store.nav);
+    const state = store.getState();
+    this.navModel = getNavModel(state.navIndex, 'datasources');
 
     if (this.$routeParams.id) {
       this.getDatasourceById(this.$routeParams.id);
@@ -30,8 +28,7 @@ export class DataSourceDashboardsCtrl {
   }
 
   updateNav() {
-    store.nav.initDatasourceEditNav(this.current, this.datasourceMeta, 'datasource-dashboards');
-    this.navModel = toJS(store.nav);
+    this.navModel = buildNavModel(this.current, this.datasourceMeta, 'datasource-dashboards');
   }
 
   getPluginInfo() {

+ 6 - 9
public/app/features/plugins/ds_edit_ctrl.ts

@@ -1,8 +1,9 @@
 import _ from 'lodash';
-import { toJS } from 'mobx';
 import config from 'app/core/config';
 import { coreModule, appEvents } from 'app/core/core';
-import { store } from 'app/stores/store';
+import { store } from 'app/store/configureStore';
+import { getNavModel } from 'app/core/selectors/navModel';
+import { buildNavModel } from './state/navModel';
 
 let datasourceTypes = [];
 
@@ -31,11 +32,8 @@ export class DataSourceEditCtrl {
 
   /** @ngInject */
   constructor(private $q, private backendSrv, private $routeParams, private $location, private datasourceSrv) {
-    if (store.nav.main === null) {
-      store.nav.load('cfg', 'datasources');
-    }
-
-    this.navModel = toJS(store.nav);
+    const state = store.getState();
+    this.navModel = getNavModel(state.navIndex, 'datasources');
     this.datasources = [];
 
     this.loadDatasourceTypes().then(() => {
@@ -101,8 +99,7 @@ export class DataSourceEditCtrl {
   }
 
   updateNav() {
-    store.nav.initDatasourceEditNav(this.current, this.datasourceMeta, 'datasource-settings');
-    this.navModel = toJS(store.nav);
+    this.navModel = buildNavModel(this.current, this.datasourceMeta, 'datasource-settings');
   }
 
   typeChanged() {

+ 45 - 0
public/app/features/plugins/state/navModel.ts

@@ -0,0 +1,45 @@
+import _ from 'lodash';
+import { DataSource, PluginMeta, NavModel } from 'app/types';
+
+export function buildNavModel(ds: DataSource, plugin: PluginMeta, currentPage: string): NavModel {
+  let title = 'New';
+  const subTitle = `Type: ${plugin.name}`;
+
+  if (ds.id) {
+    title = ds.name;
+  }
+
+  const main = {
+    img: plugin.info.logos.large,
+    id: 'ds-edit-' + plugin.id,
+    subTitle: subTitle,
+    url: '',
+    text: title,
+    breadcrumbs: [{ title: 'Data Sources', url: 'datasources' }],
+    children: [
+      {
+        active: currentPage === 'datasource-settings',
+        icon: 'fa fa-fw fa-sliders',
+        id: 'datasource-settings',
+        text: 'Settings',
+        url: `datasources/edit/${ds.id}`,
+      },
+    ],
+  };
+
+  const hasDashboards = _.find(plugin.includes, { type: 'dashboard' }) !== undefined;
+  if (hasDashboards && ds.id) {
+    main.children.push({
+      active: currentPage === 'datasource-dashboards',
+      icon: 'fa fa-fw fa-th-large',
+      id: 'datasource-dashboards',
+      text: 'Dashboards',
+      url: `datasources/edit/${ds.id}/dashboards`,
+    });
+  }
+
+  return {
+    main: main,
+    node: _.find(main.children, { active: true }),
+  };
+}

+ 5 - 9
public/app/routes/ReactContainer.tsx

@@ -1,22 +1,18 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
-import { Provider } from 'mobx-react';
-import { Provider as ReduxProvider } from 'react-redux';
+import { Provider } from 'react-redux';
 
 import coreModule from 'app/core/core_module';
-import { store } from 'app/stores/store';
-import { store as reduxStore } from 'app/stores/configureStore';
+import { store } from 'app/store/configureStore';
 import { BackendSrv } from 'app/core/services/backend_srv';
 import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
 import { ContextSrv } from 'app/core/services/context_srv';
 
 function WrapInProvider(store, Component, props) {
   return (
-    <ReduxProvider store={reduxStore}>
-      <Provider {...store}>
-        <Component {...props} />
-      </Provider>
-    </ReduxProvider>
+    <Provider store={store}>
+      <Component {...props} />
+    </Provider>
   );
 }
 

+ 0 - 0
public/app/stores/configureStore.ts → public/app/store/configureStore.ts


+ 0 - 19
public/app/stores/NavStore/NavItem.ts

@@ -1,19 +0,0 @@
-import { types } from 'mobx-state-tree';
-
-export const NavItem = types.model('NavItem', {
-  id: types.identifier(types.string),
-  text: types.string,
-  url: types.optional(types.string, ''),
-  subTitle: types.optional(types.string, ''),
-  icon: types.optional(types.string, ''),
-  img: types.optional(types.string, ''),
-  active: types.optional(types.boolean, false),
-  hideFromTabs: types.optional(types.boolean, false),
-  breadcrumbs: types.optional(types.array(types.late(() => Breadcrumb)), []),
-  children: types.optional(types.array(types.late(() => NavItem)), []),
-});
-
-export const Breadcrumb = types.model('Breadcrumb', {
-  title: types.string,
-  url: types.string,
-});

+ 0 - 47
public/app/stores/NavStore/NavStore.test.ts

@@ -1,47 +0,0 @@
-import { NavStore } from './NavStore';
-
-describe('NavStore', () => {
-  const folderId = 1;
-  const folderTitle = 'Folder Name';
-  const folderUrl = '/dashboards/f/uid/folder-name';
-  const canAdmin = true;
-
-  const folder = {
-    id: folderId,
-    url: folderUrl,
-    title: folderTitle,
-    canAdmin: canAdmin,
-  };
-
-  let store;
-
-  beforeEach(() => {
-    store = NavStore.create();
-    store.initFolderNav(folder, 'manage-folder-settings');
-  });
-
-  it('Should set text', () => {
-    expect(store.main.text).toBe(folderTitle);
-  });
-
-  it('Should load nav with tabs', () => {
-    expect(store.main.children.length).toBe(3);
-    expect(store.main.children[0].id).toBe('manage-folder-dashboards');
-    expect(store.main.children[1].id).toBe('manage-folder-permissions');
-    expect(store.main.children[2].id).toBe('manage-folder-settings');
-  });
-
-  it('Should set correct urls for each tab', () => {
-    expect(store.main.children.length).toBe(3);
-    expect(store.main.children[0].url).toBe(folderUrl);
-    expect(store.main.children[1].url).toBe(`${folderUrl}/permissions`);
-    expect(store.main.children[2].url).toBe(`${folderUrl}/settings`);
-  });
-
-  it('Should set active tab', () => {
-    expect(store.main.children.length).toBe(3);
-    expect(store.main.children[0].active).toBe(false);
-    expect(store.main.children[1].active).toBe(false);
-    expect(store.main.children[2].active).toBe(true);
-  });
-});

+ 0 - 118
public/app/stores/NavStore/NavStore.ts

@@ -1,118 +0,0 @@
-import _ from 'lodash';
-import { types, getEnv } from 'mobx-state-tree';
-import { NavItem } from './NavItem';
-
-export const NavStore = types
-  .model('NavStore', {
-    main: types.maybe(NavItem),
-    node: types.maybe(NavItem),
-  })
-  .actions(self => ({
-    load(...args) {
-      let children = getEnv(self).navTree;
-      let main, node;
-      const parents = [];
-
-      for (const id of args) {
-        node = children.find(el => el.id === id);
-
-        if (!node) {
-          throw new Error(`NavItem with id ${id} not found`);
-        }
-
-        children = node.children;
-        parents.push(node);
-      }
-
-      main = parents[parents.length - 2];
-
-      if (main.children) {
-        for (const item of main.children) {
-          item.active = false;
-
-          if (item.url === node.url) {
-            item.active = true;
-          }
-        }
-      }
-
-      self.main = NavItem.create(main);
-      self.node = NavItem.create(node);
-    },
-
-    initFolderNav(folder: any, activeChildId: string) {
-      const main = {
-        icon: 'fa fa-folder-open',
-        id: 'manage-folder',
-        subTitle: 'Manage folder dashboards & permissions',
-        url: '',
-        text: folder.title,
-        breadcrumbs: [{ title: 'Dashboards', url: 'dashboards' }],
-        children: [
-          {
-            active: activeChildId === 'manage-folder-dashboards',
-            icon: 'fa fa-fw fa-th-large',
-            id: 'manage-folder-dashboards',
-            text: 'Dashboards',
-            url: folder.url,
-          },
-          {
-            active: activeChildId === 'manage-folder-permissions',
-            icon: 'fa fa-fw fa-lock',
-            id: 'manage-folder-permissions',
-            text: 'Permissions',
-            url: `${folder.url}/permissions`,
-          },
-          {
-            active: activeChildId === 'manage-folder-settings',
-            icon: 'fa fa-fw fa-cog',
-            id: 'manage-folder-settings',
-            text: 'Settings',
-            url: `${folder.url}/settings`,
-          },
-        ],
-      };
-
-      self.main = NavItem.create(main);
-    },
-
-    initDatasourceEditNav(ds: any, plugin: any, currentPage: string) {
-      let title = 'New';
-      const subTitle = `Type: ${plugin.name}`;
-
-      if (ds.id) {
-        title = ds.name;
-      }
-
-      const main = {
-        img: plugin.info.logos.large,
-        id: 'ds-edit-' + plugin.id,
-        subTitle: subTitle,
-        url: '',
-        text: title,
-        breadcrumbs: [{ title: 'Data Sources', url: 'datasources' }],
-        children: [
-          {
-            active: currentPage === 'datasource-settings',
-            icon: 'fa fa-fw fa-sliders',
-            id: 'datasource-settings',
-            text: 'Settings',
-            url: `datasources/edit/${ds.id}`,
-          },
-        ],
-      };
-
-      const hasDashboards = _.find(plugin.includes, { type: 'dashboard' }) !== undefined;
-      if (hasDashboards && ds.id) {
-        main.children.push({
-          active: currentPage === 'datasource-dashboards',
-          icon: 'fa fa-fw fa-th-large',
-          id: 'datasource-dashboards',
-          text: 'Dashboards',
-          url: `datasources/edit/${ds.id}/dashboards`,
-        });
-      }
-
-      self.main = NavItem.create(main);
-    },
-  }));

+ 0 - 116
public/app/stores/PermissionsStore/PermissionsStore.test.ts

@@ -1,116 +0,0 @@
-import { PermissionsStore } from './PermissionsStore';
-import { backendSrv } from 'test/mocks/common';
-
-describe('PermissionsStore', () => {
-  let store;
-
-  beforeEach(async () => {
-    backendSrv.get.mockReturnValue(
-      Promise.resolve([
-        { id: 2, dashboardId: 1, role: 'Viewer', permission: 1, permissionName: 'View' },
-        { id: 3, dashboardId: 1, role: 'Editor', permission: 1, permissionName: 'Edit' },
-        {
-          id: 4,
-          dashboardId: 10,
-          permission: 1,
-          permissionName: 'View',
-          teamId: 1,
-          team: 'MyTestTeam',
-          inherited: true,
-        },
-        {
-          id: 5,
-          dashboardId: 1,
-          permission: 1,
-          permissionName: 'View',
-          userId: 1,
-          userLogin: 'MyTestUser',
-        },
-        {
-          id: 6,
-          dashboardId: 1,
-          permission: 1,
-          permissionName: 'Edit',
-          teamId: 2,
-          team: 'MyTestTeam2',
-        },
-      ])
-    );
-
-    backendSrv.post = jest.fn(() => Promise.resolve({}));
-
-    store = PermissionsStore.create(
-      {
-        fetching: false,
-        items: [],
-      },
-      {
-        backendSrv: backendSrv,
-      }
-    );
-
-    await store.load(1, false, false);
-  });
-
-  it('should save update on permission change', async () => {
-    expect(store.items[0].permission).toBe(1);
-    expect(store.items[0].permissionName).toBe('View');
-
-    await store.updatePermissionOnIndex(0, 2, 'Edit');
-
-    expect(store.items[0].permission).toBe(2);
-    expect(store.items[0].permissionName).toBe('Edit');
-    expect(backendSrv.post.mock.calls.length).toBe(1);
-    expect(backendSrv.post.mock.calls[0][0]).toBe('/api/dashboards/id/1/permissions');
-  });
-
-  it('should save removed permissions automatically', async () => {
-    expect(store.items.length).toBe(5);
-
-    await store.removeStoreItem(2);
-
-    expect(store.items.length).toBe(4);
-    expect(backendSrv.post.mock.calls.length).toBe(1);
-    expect(backendSrv.post.mock.calls[0][0]).toBe('/api/dashboards/id/1/permissions');
-  });
-
-  it('should be sorted by sort rank and alphabetically', async () => {
-    expect(store.items[0].name).toBe('MyTestTeam');
-    expect(store.items[0].dashboardId).toBe(10);
-    expect(store.items[1].name).toBe('Editor');
-    expect(store.items[2].name).toBe('Viewer');
-    expect(store.items[3].name).toBe('MyTestTeam2');
-    expect(store.items[4].name).toBe('MyTestUser');
-  });
-
-  describe('when one inherited and one not inherited team permission are added', () => {
-    beforeEach(async () => {
-      const overridingItemForChildDashboard = {
-        team: 'MyTestTeam',
-        dashboardId: 1,
-        teamId: 1,
-        permission: 2,
-      };
-
-      store.resetNewType();
-      store.newItem.setTeam(overridingItemForChildDashboard.teamId, overridingItemForChildDashboard.team);
-      store.newItem.setPermission(overridingItemForChildDashboard.permission);
-      await store.addStoreItem();
-    });
-
-    it('should add new overriding permission', () => {
-      expect(store.items.length).toBe(6);
-    });
-
-    it('should be sorted by sort rank and alphabetically', async () => {
-      expect(store.items[0].name).toBe('MyTestTeam');
-      expect(store.items[0].dashboardId).toBe(10);
-      expect(store.items[1].name).toBe('Editor');
-      expect(store.items[2].name).toBe('Viewer');
-      expect(store.items[3].name).toBe('MyTestTeam');
-      expect(store.items[3].dashboardId).toBe(1);
-      expect(store.items[4].name).toBe('MyTestTeam2');
-      expect(store.items[5].name).toBe('MyTestUser');
-    });
-  });
-});

+ 0 - 259
public/app/stores/PermissionsStore/PermissionsStore.ts

@@ -1,259 +0,0 @@
-import { types, getEnv, flow } from 'mobx-state-tree';
-import { PermissionsStoreItem } from './PermissionsStoreItem';
-
-export const permissionOptions = [
-  { value: 1, label: 'View', description: 'Can view dashboards.' },
-  { value: 2, label: 'Edit', description: 'Can add, edit and delete dashboards.' },
-  {
-    value: 4,
-    label: 'Admin',
-    description: 'Can add/remove permissions and can add, edit and delete dashboards.',
-  },
-];
-
-export const aclTypeValues = {
-  GROUP: { value: 'Group', text: 'Team' },
-  USER: { value: 'User', text: 'User' },
-  VIEWER: { value: 'Viewer', text: 'Everyone With Viewer Role' },
-  EDITOR: { value: 'Editor', text: 'Everyone With Editor Role' },
-};
-
-export const aclTypes = Object.keys(aclTypeValues).map(item => aclTypeValues[item]);
-
-const defaultNewType = aclTypes[0].value;
-
-export const NewPermissionsItem = types
-  .model('NewPermissionsItem', {
-    type: types.optional(
-      types.enumeration(Object.keys(aclTypeValues).map(item => aclTypeValues[item].value)),
-      defaultNewType
-    ),
-    userId: types.maybe(types.number),
-    userLogin: types.maybe(types.string),
-    userAvatarUrl: types.maybe(types.string),
-    teamAvatarUrl: types.maybe(types.string),
-    teamId: types.maybe(types.number),
-    team: types.maybe(types.string),
-    permission: types.optional(types.number, 1),
-  })
-  .views(self => ({
-    isValid: () => {
-      switch (self.type) {
-        case aclTypeValues.GROUP.value:
-          return self.teamId && self.team;
-        case aclTypeValues.USER.value:
-          return !!self.userId && !!self.userLogin;
-        case aclTypeValues.VIEWER.value:
-        case aclTypeValues.EDITOR.value:
-          return true;
-        default:
-          return false;
-      }
-    },
-  }))
-  .actions(self => ({
-    setUser(userId: number, userLogin: string, userAvatarUrl: string) {
-      self.userId = userId;
-      self.userLogin = userLogin;
-      self.userAvatarUrl = userAvatarUrl;
-      self.teamId = null;
-      self.team = null;
-    },
-    setTeam(teamId: number, team: string, teamAvatarUrl: string) {
-      self.userId = null;
-      self.userLogin = null;
-      self.teamId = teamId;
-      self.team = team;
-      self.teamAvatarUrl = teamAvatarUrl;
-    },
-    setPermission(permission: number) {
-      self.permission = permission;
-    },
-  }));
-
-export const PermissionsStore = types
-  .model('PermissionsStore', {
-    fetching: types.boolean,
-    isFolder: types.maybe(types.boolean),
-    dashboardId: types.maybe(types.number),
-    items: types.optional(types.array(PermissionsStoreItem), []),
-    originalItems: types.optional(types.array(PermissionsStoreItem), []),
-    newType: types.optional(types.string, defaultNewType),
-    newItem: types.maybe(NewPermissionsItem),
-    isAddPermissionsVisible: types.optional(types.boolean, false),
-    isInRoot: types.maybe(types.boolean),
-  })
-  .views(self => ({
-    isValid: item => {
-      const dupe = self.items.find(it => {
-        return isDuplicate(it, item);
-      });
-      if (dupe) {
-        return false;
-      }
-
-      return true;
-    },
-  }))
-  .actions(self => {
-    const resetNewTypeInternal = () => {
-      self.newItem = NewPermissionsItem.create();
-    };
-
-    return {
-      load: flow(function* load(dashboardId: number, isFolder: boolean, isInRoot: boolean) {
-        const backendSrv = getEnv(self).backendSrv;
-        self.fetching = true;
-        self.isFolder = isFolder;
-        self.isInRoot = isInRoot;
-        self.dashboardId = dashboardId;
-        self.items.clear();
-
-        const res = yield backendSrv.get(`/api/dashboards/id/${dashboardId}/permissions`);
-        const items = prepareServerResponse(res, dashboardId, isFolder, isInRoot);
-        self.items = items;
-        self.originalItems = items;
-        self.fetching = false;
-      }),
-
-      addStoreItem: flow(function* addStoreItem() {
-        const item = {
-          type: self.newItem.type,
-          permission: self.newItem.permission,
-          dashboardId: self.dashboardId,
-          team: undefined,
-          teamId: undefined,
-          userLogin: undefined,
-          userId: undefined,
-          userAvatarUrl: undefined,
-          teamAvatarUrl: undefined,
-          role: undefined,
-        };
-        switch (self.newItem.type) {
-          case aclTypeValues.GROUP.value:
-            item.team = self.newItem.team;
-            item.teamId = self.newItem.teamId;
-            item.teamAvatarUrl = self.newItem.teamAvatarUrl;
-            break;
-          case aclTypeValues.USER.value:
-            item.userLogin = self.newItem.userLogin;
-            item.userId = self.newItem.userId;
-            item.userAvatarUrl = self.newItem.userAvatarUrl;
-            break;
-          case aclTypeValues.VIEWER.value:
-          case aclTypeValues.EDITOR.value:
-            item.role = self.newItem.type;
-            break;
-          default:
-            throw Error('Unknown type: ' + self.newItem.type);
-        }
-
-        const updatedItems = self.items.peek();
-        const newItem = prepareItem(item, self.dashboardId, self.isFolder, self.isInRoot);
-        updatedItems.push(newItem);
-
-        try {
-          yield updateItems(self, updatedItems);
-          self.items.push(newItem);
-          const sortedItems = self.items.sort((a, b) => b.sortRank - a.sortRank || a.name.localeCompare(b.name));
-          self.items = sortedItems;
-          resetNewTypeInternal();
-        } catch {}
-        yield Promise.resolve();
-      }),
-
-      removeStoreItem: flow(function* removeStoreItem(idx: number) {
-        self.items.splice(idx, 1);
-        yield updateItems(self, self.items.peek());
-      }),
-
-      updatePermissionOnIndex: flow(function* updatePermissionOnIndex(
-        idx: number,
-        permission: number,
-        permissionName: string
-      ) {
-        self.items[idx].updatePermission(permission, permissionName);
-        yield updateItems(self, self.items.peek());
-      }),
-
-      setNewType(newType: string) {
-        self.newItem = NewPermissionsItem.create({ type: newType });
-      },
-
-      resetNewType() {
-        resetNewTypeInternal();
-      },
-
-      toggleAddPermissions() {
-        self.isAddPermissionsVisible = !self.isAddPermissionsVisible;
-      },
-
-      hideAddPermissions() {
-        self.isAddPermissionsVisible = false;
-      },
-    };
-  });
-
-const updateItems = (self, items) => {
-  const backendSrv = getEnv(self).backendSrv;
-  const updated = [];
-  for (const item of items) {
-    if (item.inherited) {
-      continue;
-    }
-    updated.push({
-      id: item.id,
-      userId: item.userId,
-      teamId: item.teamId,
-      role: item.role,
-      permission: item.permission,
-    });
-  }
-
-  return backendSrv.post(`/api/dashboards/id/${self.dashboardId}/permissions`, {
-    items: updated,
-  });
-};
-
-const prepareServerResponse = (response, dashboardId: number, isFolder: boolean, isInRoot: boolean) => {
-  return response
-    .map(item => {
-      return prepareItem(item, dashboardId, isFolder, isInRoot);
-    })
-    .sort((a, b) => b.sortRank - a.sortRank || a.name.localeCompare(b.name));
-};
-
-const prepareItem = (item, dashboardId: number, isFolder: boolean, isInRoot: boolean) => {
-  item.sortRank = 0;
-  if (item.userId > 0) {
-    item.name = item.userLogin;
-    item.sortRank = 10;
-  } else if (item.teamId > 0) {
-    item.name = item.team;
-    item.sortRank = 20;
-  } else if (item.role) {
-    item.icon = 'fa fa-fw fa-street-view';
-    item.name = item.role;
-    item.sortRank = 30;
-    if (item.role === 'Editor') {
-      item.sortRank += 1;
-    }
-  }
-
-  if (item.inherited) {
-    item.sortRank += 100;
-  }
-  return item;
-};
-
-const isDuplicate = (origItem, newItem) => {
-  if (origItem.inherited) {
-    return false;
-  }
-
-  return (
-    (origItem.role && newItem.role && origItem.role === newItem.role) ||
-    (origItem.userId && newItem.userId && origItem.userId === newItem.userId) ||
-    (origItem.teamId && newItem.teamId && origItem.teamId === newItem.teamId)
-  );
-};

+ 0 - 29
public/app/stores/PermissionsStore/PermissionsStoreItem.ts

@@ -1,29 +0,0 @@
-import { types } from 'mobx-state-tree';
-
-export const PermissionsStoreItem = types
-  .model('PermissionsStoreItem', {
-    dashboardId: types.optional(types.number, -1),
-    permission: types.number,
-    permissionName: types.maybe(types.string),
-    role: types.maybe(types.string),
-    team: types.optional(types.string, ''),
-    teamId: types.optional(types.number, 0),
-    userEmail: types.optional(types.string, ''),
-    userId: types.optional(types.number, 0),
-    userLogin: types.optional(types.string, ''),
-    inherited: types.maybe(types.boolean),
-    sortRank: types.maybe(types.number),
-    icon: types.maybe(types.string),
-    name: types.maybe(types.string),
-    teamAvatarUrl: types.maybe(types.string),
-    userAvatarUrl: types.maybe(types.string),
-  })
-  .actions(self => ({
-    updateRole: role => {
-      self.role = role;
-    },
-    updatePermission(permission: number, permissionName: string) {
-      self.permission = permission;
-      self.permissionName = permissionName;
-    },
-  }));

+ 0 - 20
public/app/stores/RootStore/RootStore.ts

@@ -1,20 +0,0 @@
-import { types } from 'mobx-state-tree';
-import { NavStore } from './../NavStore/NavStore';
-import { ViewStore } from './../ViewStore/ViewStore';
-import { PermissionsStore } from './../PermissionsStore/PermissionsStore';
-
-export const RootStore = types.model({
-  nav: types.optional(NavStore, {}),
-  permissions: types.optional(PermissionsStore, {
-    fetching: false,
-    items: [],
-  }),
-  view: types.optional(ViewStore, {
-    path: '',
-    query: {},
-    routeParams: {},
-  }),
-});
-
-type RootStoreType = typeof RootStore.Type;
-export interface RootStoreInterface extends RootStoreType {}

+ 0 - 33
public/app/stores/ViewStore/ViewStore.test.ts

@@ -1,33 +0,0 @@
-import { ViewStore } from './ViewStore';
-import { toJS } from 'mobx';
-
-describe('ViewStore', () => {
-  let store;
-
-  beforeAll(() => {
-    store = ViewStore.create({
-      path: '',
-      query: {},
-      routeParams: {},
-    });
-  });
-
-  it('Can update path and query', () => {
-    store.updatePathAndQuery('/hello', { key: 1, otherParam: 'asd' }, { key: 1, otherParam: 'asd' });
-    expect(store.path).toBe('/hello');
-    expect(store.query.get('key')).toBe(1);
-    expect(store.currentUrl).toBe('/hello?key=1&otherParam=asd');
-  });
-
-  it('Query can contain arrays', () => {
-    store.updatePathAndQuery('/hello', { values: ['A', 'B'] }, { key: 1, otherParam: 'asd' });
-    expect(toJS(store.query.get('values'))).toMatchObject(['A', 'B']);
-    expect(store.currentUrl).toBe('/hello?values=A&values=B');
-  });
-
-  it('Query can contain boolean', () => {
-    store.updatePathAndQuery('/hello', { abool: true }, { abool: true });
-    expect(toJS(store.query.get('abool'))).toBe(true);
-    expect(store.currentUrl).toBe('/hello?abool');
-  });
-});

+ 0 - 55
public/app/stores/ViewStore/ViewStore.ts

@@ -1,55 +0,0 @@
-import { types } from 'mobx-state-tree';
-import { toJS } from 'mobx';
-import { toUrlParams } from 'app/core/utils/url';
-
-const QueryInnerValueType = types.union(types.string, types.boolean, types.number);
-const QueryValueType = types.union(QueryInnerValueType, types.array(QueryInnerValueType));
-
-export const ViewStore = types
-  .model({
-    path: types.string,
-    query: types.map(QueryValueType),
-    routeParams: types.map(QueryValueType),
-  })
-  .views(self => ({
-    get currentUrl() {
-      let path = self.path;
-
-      if (self.query.size) {
-        path += '?' + toUrlParams(toJS(self.query));
-      }
-      return path;
-    },
-  }))
-  .actions(self => {
-    // querystring only
-    function updateQuery(query: any) {
-      self.query.clear();
-      for (const key of Object.keys(query)) {
-        if (query[key]) {
-          self.query.set(key, query[key]);
-        }
-      }
-    }
-
-    // needed to get route parameters like slug from the url
-    function updateRouteParams(routeParams: any) {
-      self.routeParams.clear();
-      for (const key of Object.keys(routeParams)) {
-        if (routeParams[key]) {
-          self.routeParams.set(key, routeParams[key]);
-        }
-      }
-    }
-
-    function updatePathAndQuery(path: string, query: any, routeParams: any) {
-      self.path = path;
-      updateQuery(query);
-      updateRouteParams(routeParams);
-    }
-
-    return {
-      updateQuery,
-      updatePathAndQuery,
-    };
-  });

+ 0 - 16
public/app/stores/store.ts

@@ -1,16 +0,0 @@
-import { RootStore, RootStoreInterface } from './RootStore/RootStore';
-import config from 'app/core/config';
-
-export let store: RootStoreInterface;
-
-export function createStore(services) {
-  store = RootStore.create(
-    {},
-    {
-      ...services,
-      navTree: config.bootData.navTree,
-    }
-  );
-
-  return store;
-}

+ 7 - 0
public/app/types/datasources.ts

@@ -0,0 +1,7 @@
+export interface DataSource {
+  id: number;
+  orgId: number;
+  name: string;
+  typeLogoUrl: string;
+  type: string;
+}

+ 0 - 0
public/app/types/folder.ts → public/app/types/folders.ts


+ 5 - 1
public/app/types/index.ts

@@ -2,9 +2,11 @@ import { Team, TeamsState, TeamState, TeamGroup, TeamMember } from './teams';
 import { AlertRuleDTO, AlertRule, AlertRulesState } from './alerting';
 import { LocationState, LocationUpdate, UrlQueryMap, UrlQueryValue } from './location';
 import { NavModel, NavModelItem, NavIndex } from './navModel';
-import { FolderDTO, FolderState, FolderInfo } from './folder';
+import { FolderDTO, FolderState, FolderInfo } from './folders';
 import { DashboardState } from './dashboard';
 import { DashboardAcl, OrgRole, PermissionLevel } from './acl';
+import { DataSource } from './datasources';
+import { PluginMeta } from './plugins';
 
 export {
   Team,
@@ -29,6 +31,8 @@ export {
   DashboardAcl,
   OrgRole,
   PermissionLevel,
+  DataSource,
+  PluginMeta,
 };
 
 export interface StoreState {

+ 19 - 0
public/app/types/plugins.ts

@@ -0,0 +1,19 @@
+export interface PluginMeta {
+  id: string;
+  name: string;
+  info: PluginMetaInfo;
+  includes: PluginInclude[];
+}
+
+export interface PluginInclude {
+  type: string;
+  name: string;
+  path: string;
+}
+
+export interface PluginMetaInfo {
+  logos: {
+    large: string;
+    small: string;
+  };
+}

+ 1 - 19
yarn.lock

@@ -5258,7 +5258,7 @@ hmac-drbg@^1.0.0:
     minimalistic-assert "^1.0.0"
     minimalistic-crypto-utils "^1.0.1"
 
-hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0:
+hoist-non-react-statics@^2.5.0:
   version "2.5.5"
   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
 
@@ -7593,24 +7593,6 @@ mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdi
   dependencies:
     minimist "0.0.8"
 
-mobx-react-devtools@^4.2.15:
-  version "4.2.15"
-  resolved "https://registry.yarnpkg.com/mobx-react-devtools/-/mobx-react-devtools-4.2.15.tgz#881c038fb83db4dffd1e72bbaf5374d26b2fdebb"
-
-mobx-react@^4.3.5:
-  version "4.4.3"
-  resolved "http://registry.npmjs.org/mobx-react/-/mobx-react-4.4.3.tgz#baa9ec41165ee35ae7b9df19bca10190f36f117e"
-  dependencies:
-    hoist-non-react-statics "^2.3.1"
-
-mobx-state-tree@^1.3.1:
-  version "1.4.0"
-  resolved "http://registry.npmjs.org/mobx-state-tree/-/mobx-state-tree-1.4.0.tgz#c914c855d5ec5c1c16e4ba6d6925679df42c8110"
-
-mobx@^3.4.1:
-  version "3.6.2"
-  resolved "https://registry.yarnpkg.com/mobx/-/mobx-3.6.2.tgz#fb9f5ff5090539a1ad54e75dc4c098b602693320"
-
 mocha@^4.0.1:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794"