Browse Source

Revert "Org users to react"

Torkel Ödegaard 7 years ago
parent
commit
fe4f2f71c3
39 changed files with 440 additions and 1860 deletions
  1. 0 23
      public/app/core/components/OrgActionBar/OrgActionBar.test.tsx
  2. 0 38
      public/app/core/components/OrgActionBar/OrgActionBar.tsx
  3. 23 0
      public/app/features/datasources/DataSourcesActionBar.test.tsx
  4. 62 0
      public/app/features/datasources/DataSourcesActionBar.tsx
  5. 0 3
      public/app/features/datasources/DataSourcesListPage.test.tsx
  6. 7 38
      public/app/features/datasources/DataSourcesListPage.tsx
  7. 42 0
      public/app/features/datasources/__snapshots__/DataSourcesActionBar.test.tsx.snap
  8. 1 11
      public/app/features/datasources/__snapshots__/DataSourcesListPage.test.tsx.snap
  9. 2 0
      public/app/features/org/all.ts
  10. 87 0
      public/app/features/org/org_users_ctrl.ts
  11. 105 0
      public/app/features/org/partials/orgUsers.html
  12. 31 0
      public/app/features/plugins/PluginActionBar.test.tsx
  13. 62 0
      public/app/features/plugins/PluginActionBar.tsx
  14. 0 3
      public/app/features/plugins/PluginListPage.test.tsx
  15. 7 23
      public/app/features/plugins/PluginListPage.tsx
  16. 3 2
      public/app/features/plugins/__snapshots__/PluginActionBar.test.tsx.snap
  17. 1 12
      public/app/features/plugins/__snapshots__/PluginListPage.test.tsx.snap
  18. 1 1
      public/app/features/plugins/state/actions.ts
  19. 0 32
      public/app/features/users/InviteesTable.test.tsx
  20. 0 64
      public/app/features/users/InviteesTable.tsx
  21. 0 51
      public/app/features/users/UsersActionBar.test.tsx
  22. 0 80
      public/app/features/users/UsersActionBar.tsx
  23. 0 55
      public/app/features/users/UsersListPage.test.tsx
  24. 0 125
      public/app/features/users/UsersListPage.tsx
  25. 0 33
      public/app/features/users/UsersTable.test.tsx
  26. 0 67
      public/app/features/users/UsersTable.tsx
  27. 0 56
      public/app/features/users/__mocks__/userMocks.ts
  28. 0 318
      public/app/features/users/__snapshots__/InviteesTable.test.tsx.snap
  29. 0 141
      public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap
  30. 0 21
      public/app/features/users/__snapshots__/UsersListPage.test.tsx.snap
  31. 0 444
      public/app/features/users/__snapshots__/UsersTable.test.tsx.snap
  32. 0 79
      public/app/features/users/state/actions.ts
  33. 0 32
      public/app/features/users/state/reducers.ts
  34. 0 18
      public/app/features/users/state/selectors.ts
  35. 3 5
      public/app/routes/routes.ts
  36. 0 2
      public/app/store/configureStore.ts
  37. 2 7
      public/app/types/index.ts
  38. 1 39
      public/app/types/user.ts
  39. 0 37
      public/app/types/users.ts

+ 0 - 23
public/app/core/components/OrgActionBar/OrgActionBar.test.tsx

@@ -1,23 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import OrgActionBar, { Props } from './OrgActionBar';
-
-const setup = (propOverrides?: object) => {
-  const props: Props = {
-    searchQuery: '',
-    setSearchQuery: jest.fn(),
-    linkButton: { href: 'some/url', title: 'test' },
-  };
-
-  Object.assign(props, propOverrides);
-
-  return shallow(<OrgActionBar {...props} />);
-};
-
-describe('Render', () => {
-  it('should render component', () => {
-    const wrapper = setup();
-
-    expect(wrapper).toMatchSnapshot();
-  });
-});

+ 0 - 38
public/app/core/components/OrgActionBar/OrgActionBar.tsx

@@ -1,38 +0,0 @@
-import React, { PureComponent } from 'react';
-import LayoutSelector, { LayoutMode } from '../LayoutSelector/LayoutSelector';
-
-export interface Props {
-  searchQuery: string;
-  layoutMode?: LayoutMode;
-  setLayoutMode?: (mode: LayoutMode) => {};
-  setSearchQuery: (value: string) => {};
-  linkButton: { href: string; title: string };
-}
-
-export default class OrgActionBar extends PureComponent<Props> {
-  render() {
-    const { searchQuery, layoutMode, setLayoutMode, linkButton, setSearchQuery } = this.props;
-
-    return (
-      <div className="page-action-bar">
-        <div className="gf-form gf-form--grow">
-          <label className="gf-form--has-input-icon">
-            <input
-              type="text"
-              className="gf-form-input width-20"
-              value={searchQuery}
-              onChange={event => setSearchQuery(event.target.value)}
-              placeholder="Filter by name or type"
-            />
-            <i className="gf-form-input-icon fa fa-search" />
-          </label>
-          <LayoutSelector mode={layoutMode} onLayoutModeChanged={(mode: LayoutMode) => setLayoutMode(mode)} />
-        </div>
-        <div className="page-action-bar__spacer" />
-        <a className="btn btn-success" href={linkButton.href} target="_blank">
-          {linkButton.title}
-        </a>
-      </div>
-    );
-  }
-}

+ 23 - 0
public/app/features/datasources/DataSourcesActionBar.test.tsx

@@ -0,0 +1,23 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import { DataSourcesActionBar, Props } from './DataSourcesActionBar';
+import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
+
+const setup = (propOverrides?: object) => {
+  const props: Props = {
+    layoutMode: LayoutModes.Grid,
+    searchQuery: '',
+    setDataSourcesLayoutMode: jest.fn(),
+    setDataSourcesSearchQuery: jest.fn(),
+  };
+
+  return shallow(<DataSourcesActionBar {...props} />);
+};
+
+describe('Render', () => {
+  it('should render component', () => {
+    const wrapper = setup();
+
+    expect(wrapper).toMatchSnapshot();
+  });
+});

+ 62 - 0
public/app/features/datasources/DataSourcesActionBar.tsx

@@ -0,0 +1,62 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'react-redux';
+import LayoutSelector, { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
+import { setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/actions';
+import { getDataSourcesLayoutMode, getDataSourcesSearchQuery } from './state/selectors';
+
+export interface Props {
+  searchQuery: string;
+  layoutMode: LayoutMode;
+  setDataSourcesLayoutMode: typeof setDataSourcesLayoutMode;
+  setDataSourcesSearchQuery: typeof setDataSourcesSearchQuery;
+}
+
+export class DataSourcesActionBar extends PureComponent<Props> {
+  onSearchQueryChange = event => {
+    this.props.setDataSourcesSearchQuery(event.target.value);
+  };
+
+  render() {
+    const { searchQuery, layoutMode, setDataSourcesLayoutMode } = this.props;
+
+    return (
+      <div className="page-action-bar">
+        <div className="gf-form gf-form--grow">
+          <label className="gf-form--has-input-icon">
+            <input
+              type="text"
+              className="gf-form-input width-20"
+              value={searchQuery}
+              onChange={this.onSearchQueryChange}
+              placeholder="Filter by name or type"
+            />
+            <i className="gf-form-input-icon fa fa-search" />
+          </label>
+          <LayoutSelector
+            mode={layoutMode}
+            onLayoutModeChanged={(mode: LayoutMode) => setDataSourcesLayoutMode(mode)}
+          />
+        </div>
+        <div className="page-action-bar__spacer" />
+        <a className="page-header__cta btn btn-success" href="datasources/new">
+          <i className="fa fa-plus" />
+          Add data source
+        </a>
+      </div>
+    );
+  }
+}
+
+function mapStateToProps(state) {
+  return {
+    searchQuery: getDataSourcesSearchQuery(state.dataSources),
+    layoutMode: getDataSourcesLayoutMode(state.dataSources),
+  };
+}
+
+const mapDispatchToProps = {
+  setDataSourcesLayoutMode,
+  setDataSourcesSearchQuery,
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(DataSourcesActionBar);

+ 0 - 3
public/app/features/datasources/DataSourcesListPage.test.tsx

@@ -12,9 +12,6 @@ const setup = (propOverrides?: object) => {
     loadDataSources: jest.fn(),
     loadDataSources: jest.fn(),
     navModel: {} as NavModel,
     navModel: {} as NavModel,
     dataSourcesCount: 0,
     dataSourcesCount: 0,
-    searchQuery: '',
-    setDataSourcesSearchQuery: jest.fn(),
-    setDataSourcesLayoutMode: jest.fn(),
   };
   };
 
 
   Object.assign(props, propOverrides);
   Object.assign(props, propOverrides);

+ 7 - 38
public/app/features/datasources/DataSourcesListPage.tsx

@@ -2,29 +2,21 @@ import React, { PureComponent } from 'react';
 import { connect } from 'react-redux';
 import { connect } from 'react-redux';
 import { hot } from 'react-hot-loader';
 import { hot } from 'react-hot-loader';
 import PageHeader from '../../core/components/PageHeader/PageHeader';
 import PageHeader from '../../core/components/PageHeader/PageHeader';
-import OrgActionBar from '../../core/components/OrgActionBar/OrgActionBar';
-import EmptyListCTA from '../../core/components/EmptyListCTA/EmptyListCTA';
+import DataSourcesActionBar from './DataSourcesActionBar';
 import DataSourcesList from './DataSourcesList';
 import DataSourcesList from './DataSourcesList';
+import { loadDataSources } from './state/actions';
+import { getDataSources, getDataSourcesCount, getDataSourcesLayoutMode } from './state/selectors';
+import { getNavModel } from '../../core/selectors/navModel';
 import { DataSource, NavModel } from 'app/types';
 import { DataSource, NavModel } from 'app/types';
 import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
 import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
-import { loadDataSources, setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/actions';
-import { getNavModel } from '../../core/selectors/navModel';
-import {
-  getDataSources,
-  getDataSourcesCount,
-  getDataSourcesLayoutMode,
-  getDataSourcesSearchQuery,
-} from './state/selectors';
+import EmptyListCTA from '../../core/components/EmptyListCTA/EmptyListCTA';
 
 
 export interface Props {
 export interface Props {
   navModel: NavModel;
   navModel: NavModel;
   dataSources: DataSource[];
   dataSources: DataSource[];
   dataSourcesCount: number;
   dataSourcesCount: number;
   layoutMode: LayoutMode;
   layoutMode: LayoutMode;
-  searchQuery: string;
   loadDataSources: typeof loadDataSources;
   loadDataSources: typeof loadDataSources;
-  setDataSourcesLayoutMode: typeof setDataSourcesLayoutMode;
-  setDataSourcesSearchQuery: typeof setDataSourcesSearchQuery;
 }
 }
 
 
 const emptyListModel = {
 const emptyListModel = {
@@ -48,20 +40,7 @@ export class DataSourcesListPage extends PureComponent<Props> {
   }
   }
 
 
   render() {
   render() {
-    const {
-      dataSources,
-      dataSourcesCount,
-      navModel,
-      layoutMode,
-      searchQuery,
-      setDataSourcesSearchQuery,
-      setDataSourcesLayoutMode,
-    } = this.props;
-
-    const linkButton = {
-      href: 'datasources/new',
-      title: 'Add data source',
-    };
+    const { dataSources, dataSourcesCount, navModel, layoutMode } = this.props;
 
 
     return (
     return (
       <div>
       <div>
@@ -71,14 +50,7 @@ export class DataSourcesListPage extends PureComponent<Props> {
             <EmptyListCTA model={emptyListModel} />
             <EmptyListCTA model={emptyListModel} />
           ) : (
           ) : (
             [
             [
-              <OrgActionBar
-                layoutMode={layoutMode}
-                searchQuery={searchQuery}
-                setLayoutMode={mode => setDataSourcesLayoutMode(mode)}
-                setSearchQuery={query => setDataSourcesSearchQuery(query)}
-                linkButton={linkButton}
-                key="action-bar"
-              />,
+              <DataSourcesActionBar key="action-bar" />,
               <DataSourcesList dataSources={dataSources} layoutMode={layoutMode} key="list" />,
               <DataSourcesList dataSources={dataSources} layoutMode={layoutMode} key="list" />,
             ]
             ]
           )}
           )}
@@ -94,14 +66,11 @@ function mapStateToProps(state) {
     dataSources: getDataSources(state.dataSources),
     dataSources: getDataSources(state.dataSources),
     layoutMode: getDataSourcesLayoutMode(state.dataSources),
     layoutMode: getDataSourcesLayoutMode(state.dataSources),
     dataSourcesCount: getDataSourcesCount(state.dataSources),
     dataSourcesCount: getDataSourcesCount(state.dataSources),
-    searchQuery: getDataSourcesSearchQuery(state.dataSources),
   };
   };
 }
 }
 
 
 const mapDispatchToProps = {
 const mapDispatchToProps = {
   loadDataSources,
   loadDataSources,
-  setDataSourcesSearchQuery,
-  setDataSourcesLayoutMode,
 };
 };
 
 
 export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DataSourcesListPage));
 export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DataSourcesListPage));

+ 42 - 0
public/app/features/datasources/__snapshots__/DataSourcesActionBar.test.tsx.snap

@@ -0,0 +1,42 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Render should render component 1`] = `
+<div
+  className="page-action-bar"
+>
+  <div
+    className="gf-form gf-form--grow"
+  >
+    <label
+      className="gf-form--has-input-icon"
+    >
+      <input
+        className="gf-form-input width-20"
+        onChange={[Function]}
+        placeholder="Filter by name or type"
+        type="text"
+        value=""
+      />
+      <i
+        className="gf-form-input-icon fa fa-search"
+      />
+    </label>
+    <LayoutSelector
+      mode="grid"
+      onLayoutModeChanged={[Function]}
+    />
+  </div>
+  <div
+    className="page-action-bar__spacer"
+  />
+  <a
+    className="page-header__cta btn btn-success"
+    href="datasources/new"
+  >
+    <i
+      className="fa fa-plus"
+    />
+    Add data source
+  </a>
+</div>
+`;

+ 1 - 11
public/app/features/datasources/__snapshots__/DataSourcesListPage.test.tsx.snap

@@ -8,18 +8,8 @@ exports[`Render should render action bar and datasources 1`] = `
   <div
   <div
     className="page-container page-body"
     className="page-container page-body"
   >
   >
-    <OrgActionBar
+    <Connect(DataSourcesActionBar)
       key="action-bar"
       key="action-bar"
-      layoutMode="grid"
-      linkButton={
-        Object {
-          "href": "datasources/new",
-          "title": "Add data source",
-        }
-      }
-      searchQuery=""
-      setLayoutMode={[Function]}
-      setSearchQuery={[Function]}
     />
     />
     <DataSourcesList
     <DataSourcesList
       dataSources={
       dataSources={

+ 2 - 0
public/app/features/org/all.ts

@@ -1,4 +1,6 @@
+import './org_users_ctrl';
 import './profile_ctrl';
 import './profile_ctrl';
+import './org_users_ctrl';
 import './select_org_ctrl';
 import './select_org_ctrl';
 import './change_password_ctrl';
 import './change_password_ctrl';
 import './new_org_ctrl';
 import './new_org_ctrl';

+ 87 - 0
public/app/features/org/org_users_ctrl.ts

@@ -0,0 +1,87 @@
+import config from 'app/core/config';
+import coreModule from 'app/core/core_module';
+import Remarkable from 'remarkable';
+import _ from 'lodash';
+
+export class OrgUsersCtrl {
+  unfiltered: any;
+  users: any;
+  pendingInvites: any;
+  editor: any;
+  navModel: any;
+  externalUserMngLinkUrl: string;
+  externalUserMngLinkName: string;
+  externalUserMngInfo: string;
+  canInvite: boolean;
+  searchQuery: string;
+  showInvites: boolean;
+
+  /** @ngInject */
+  constructor(private $scope, private backendSrv, navModelSrv, $sce) {
+    this.navModel = navModelSrv.getNav('cfg', 'users', 0);
+
+    this.get();
+    this.externalUserMngLinkUrl = config.externalUserMngLinkUrl;
+    this.externalUserMngLinkName = config.externalUserMngLinkName;
+    this.canInvite = !config.disableLoginForm && !config.externalUserMngLinkName;
+
+    // render external user management info markdown
+    if (config.externalUserMngInfo) {
+      this.externalUserMngInfo = new Remarkable({
+        linkTarget: '__blank',
+      }).render(config.externalUserMngInfo);
+    }
+  }
+
+  get() {
+    this.backendSrv.get('/api/org/users').then(users => {
+      this.users = users;
+      this.unfiltered = users;
+    });
+    this.backendSrv.get('/api/org/invites').then(pendingInvites => {
+      this.pendingInvites = pendingInvites;
+    });
+  }
+
+  onQueryUpdated() {
+    const regex = new RegExp(this.searchQuery, 'ig');
+    this.users = _.filter(this.unfiltered, item => {
+      return regex.test(item.email) || regex.test(item.login);
+    });
+  }
+
+  updateOrgUser(user) {
+    this.backendSrv.patch('/api/org/users/' + user.userId, user);
+  }
+
+  removeUser(user) {
+    this.$scope.appEvent('confirm-modal', {
+      title: 'Delete',
+      text: 'Are you sure you want to delete user ' + user.login + '?',
+      yesText: 'Delete',
+      icon: 'fa-warning',
+      onConfirm: () => {
+        this.removeUserConfirmed(user);
+      },
+    });
+  }
+
+  removeUserConfirmed(user) {
+    this.backendSrv.delete('/api/org/users/' + user.userId).then(this.get.bind(this));
+  }
+
+  revokeInvite(invite, evt) {
+    evt.stopPropagation();
+    this.backendSrv.patch('/api/org/invites/' + invite.code + '/revoke').then(this.get.bind(this));
+  }
+
+  copyInviteToClipboard(evt) {
+    evt.stopPropagation();
+  }
+
+  getInviteUrl(invite) {
+    return invite.url;
+  }
+}
+
+coreModule.controller('OrgUsersCtrl', OrgUsersCtrl);

+ 105 - 0
public/app/features/org/partials/orgUsers.html

@@ -0,0 +1,105 @@
+<page-header model="ctrl.navModel"></page-header>
+
+<div class="page-container page-body">
+  <div class="page-action-bar">
+    <label class="gf-form gf-form--has-input-icon">
+      <input type="text" class="gf-form-input width-20" ng-model="ctrl.searchQuery" ng-change="ctrl.onQueryUpdated()" placeholder="Filter by username or email" />
+      <i class="gf-form-input-icon fa fa-search"></i>
+    </label>
+
+    <div ng-if="ctrl.pendingInvites.length" style="margin-left: 1rem">
+      <button class="btn toggle-btn active" ng-if="!ctrl.showInvites">
+        Users
+      </button><button class="btn toggle-btn" ng-if="!ctrl.showInvites" ng-click="ctrl.showInvites = true">
+        Pending Invites ({{ctrl.pendingInvites.length}})
+      </button>
+      <button class="btn toggle-btn" ng-if="ctrl.showInvites" ng-click="ctrl.showInvites = false">
+        Users
+      </button><button class="btn toggle-btn active" ng-if="ctrl.showInvites">
+        Pending Invites ({{ctrl.pendingInvites.length}})
+      </button>
+    </div>
+
+    <div class="page-action-bar__spacer"></div>
+
+    <a class="btn btn-success" href="org/users/invite" ng-show="ctrl.canInvite">
+      <i class="fa fa-plus"></i>
+      <span>Invite</span>
+    </a>
+
+    <a class="btn btn-success" ng-href="{{ctrl.externalUserMngLinkUrl}}" target="_blank" ng-if="ctrl.externalUserMngLinkUrl">
+      <i class="fa fa-external-link-square"></i>
+      {{ctrl.externalUserMngLinkName}}
+    </a>
+  </div>
+
+  <div class="grafana-info-box" ng-if="ctrl.externalUserMngInfo">
+    <span ng-bind-html="ctrl.externalUserMngInfo"></span>
+  </div>
+
+  <div ng-hide="ctrl.showInvites">
+    <table class="filter-table form-inline">
+      <thead>
+        <tr>
+          <th></th>
+          <th>Login</th>
+          <th>Email</th>
+          <th>
+            Seen
+            <tip>Time since user was seen using Grafana</tip>
+          </th>
+          <th>Role</th>
+          <th style="width: 34px;"></th>
+        </tr>
+      </thead>
+      <tr ng-repeat="user in ctrl.users">
+        <td class="width-4 text-center">
+          <img class="filter-table__avatar" ng-src="{{user.avatarUrl}}"></img>
+        </td>
+        <td>{{user.login}}</td>
+        <td><span class="ellipsis">{{user.email}}</span></td>
+        <td>{{user.lastSeenAtAge}}</td>
+        <td>
+          <div class="gf-form-select-wrapper width-12">
+            <select type="text" ng-model="user.role" class="gf-form-input" ng-options="f for f in ['Viewer', 'Editor', 'Admin']" ng-change="ctrl.updateOrgUser(user)">
+            </select>
+          </div>
+        </td>
+        <td>
+          <a ng-click="ctrl.removeUser(user)" class="btn btn-danger btn-mini">
+            <i class="fa fa-remove"></i>
+          </a>
+        </td>
+      </tr>
+    </table>
+  </div>
+
+  <div ng-if="ctrl.showInvites">
+    <table class="filter-table form-inline">
+      <thead>
+        <tr>
+          <th>Email</th>
+          <th>Name</th>
+          <th></th>
+          <th style="width: 34px;"></th>
+        </tr>
+      </thead>
+      <tr ng-repeat="invite in ctrl.pendingInvites">
+        <td>{{invite.email}}</td>
+        <td>{{invite.name}}</td>
+        <td class="text-right">
+          <button class="btn btn-inverse btn-mini" clipboard-button="ctrl.getInviteUrl(invite)" ng-click="ctrl.copyInviteToClipboard($event)">
+            <i class="fa fa-clipboard"></i> Copy Invite
+          </button>
+          &nbsp;
+        </td>
+        <td>
+          <button class="btn btn-danger btn-mini" ng-click="ctrl.revokeInvite(invite, $event)">
+            <i class="fa fa-remove"></i>
+          </button>
+        </td>
+      </tr>
+    </table>
+  </div>
+</div>
+

+ 31 - 0
public/app/features/plugins/PluginActionBar.test.tsx

@@ -0,0 +1,31 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import { PluginActionBar, Props } from './PluginActionBar';
+import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
+
+const setup = (propOverrides?: object) => {
+  const props: Props = {
+    searchQuery: '',
+    layoutMode: LayoutModes.Grid,
+    setLayoutMode: jest.fn(),
+    setPluginsSearchQuery: jest.fn(),
+  };
+
+  Object.assign(props, propOverrides);
+
+  const wrapper = shallow(<PluginActionBar {...props} />);
+  const instance = wrapper.instance() as PluginActionBar;
+
+  return {
+    wrapper,
+    instance,
+  };
+};
+
+describe('Render', () => {
+  it('should render component', () => {
+    const { wrapper } = setup();
+
+    expect(wrapper).toMatchSnapshot();
+  });
+});

+ 62 - 0
public/app/features/plugins/PluginActionBar.tsx

@@ -0,0 +1,62 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'react-redux';
+import LayoutSelector, { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
+import { setLayoutMode, setPluginsSearchQuery } from './state/actions';
+import { getPluginsSearchQuery, getLayoutMode } from './state/selectors';
+
+export interface Props {
+  searchQuery: string;
+  layoutMode: LayoutMode;
+  setLayoutMode: typeof setLayoutMode;
+  setPluginsSearchQuery: typeof setPluginsSearchQuery;
+}
+
+export class PluginActionBar extends PureComponent<Props> {
+  onSearchQueryChange = event => {
+    this.props.setPluginsSearchQuery(event.target.value);
+  };
+
+  render() {
+    const { searchQuery, layoutMode, setLayoutMode } = this.props;
+
+    return (
+      <div className="page-action-bar">
+        <div className="gf-form gf-form--grow">
+          <label className="gf-form--has-input-icon">
+            <input
+              type="text"
+              className="gf-form-input width-20"
+              value={searchQuery}
+              onChange={this.onSearchQueryChange}
+              placeholder="Filter by name or type"
+            />
+            <i className="gf-form-input-icon fa fa-search" />
+          </label>
+          <LayoutSelector mode={layoutMode} onLayoutModeChanged={(mode: LayoutMode) => setLayoutMode(mode)} />
+        </div>
+        <div className="page-action-bar__spacer" />
+        <a
+          className="btn btn-success"
+          href="https://grafana.com/plugins?utm_source=grafana_plugin_list"
+          target="_blank"
+        >
+          Find more plugins on Grafana.com
+        </a>
+      </div>
+    );
+  }
+}
+
+function mapStateToProps(state) {
+  return {
+    searchQuery: getPluginsSearchQuery(state.plugins),
+    layoutMode: getLayoutMode(state.plugins),
+  };
+}
+
+const mapDispatchToProps = {
+  setPluginsSearchQuery,
+  setLayoutMode,
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(PluginActionBar);

+ 0 - 3
public/app/features/plugins/PluginListPage.test.tsx

@@ -8,9 +8,6 @@ const setup = (propOverrides?: object) => {
   const props: Props = {
   const props: Props = {
     navModel: {} as NavModel,
     navModel: {} as NavModel,
     plugins: [] as Plugin[],
     plugins: [] as Plugin[],
-    searchQuery: '',
-    setPluginsSearchQuery: jest.fn(),
-    setPluginsLayoutMode: jest.fn(),
     layoutMode: LayoutModes.Grid,
     layoutMode: LayoutModes.Grid,
     loadPlugins: jest.fn(),
     loadPlugins: jest.fn(),
   };
   };

+ 7 - 23
public/app/features/plugins/PluginListPage.tsx

@@ -1,23 +1,20 @@
 import React, { PureComponent } from 'react';
 import React, { PureComponent } from 'react';
 import { hot } from 'react-hot-loader';
 import { hot } from 'react-hot-loader';
 import { connect } from 'react-redux';
 import { connect } from 'react-redux';
-import PageHeader from 'app/core/components/PageHeader/PageHeader';
-import OrgActionBar from 'app/core/components/OrgActionBar/OrgActionBar';
+import PageHeader from '../../core/components/PageHeader/PageHeader';
+import PluginActionBar from './PluginActionBar';
 import PluginList from './PluginList';
 import PluginList from './PluginList';
-import { NavModel, Plugin } from 'app/types';
-import { loadPlugins, setPluginsLayoutMode, setPluginsSearchQuery } from './state/actions';
+import { NavModel, Plugin } from '../../types';
+import { loadPlugins } from './state/actions';
 import { getNavModel } from '../../core/selectors/navModel';
 import { getNavModel } from '../../core/selectors/navModel';
-import { getLayoutMode, getPlugins, getPluginsSearchQuery } from './state/selectors';
+import { getLayoutMode, getPlugins } from './state/selectors';
 import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
 import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
 
 
 export interface Props {
 export interface Props {
   navModel: NavModel;
   navModel: NavModel;
   plugins: Plugin[];
   plugins: Plugin[];
   layoutMode: LayoutMode;
   layoutMode: LayoutMode;
-  searchQuery: string;
   loadPlugins: typeof loadPlugins;
   loadPlugins: typeof loadPlugins;
-  setPluginsLayoutMode: typeof setPluginsLayoutMode;
-  setPluginsSearchQuery: typeof setPluginsSearchQuery;
 }
 }
 
 
 export class PluginListPage extends PureComponent<Props> {
 export class PluginListPage extends PureComponent<Props> {
@@ -30,23 +27,13 @@ export class PluginListPage extends PureComponent<Props> {
   }
   }
 
 
   render() {
   render() {
-    const { navModel, plugins, layoutMode, setPluginsLayoutMode, setPluginsSearchQuery, searchQuery } = this.props;
+    const { navModel, plugins, layoutMode } = this.props;
 
 
-    const linkButton = {
-      href: 'https://grafana.com/plugins?utm_source=grafana_plugin_list',
-      title: 'Find more plugins on Grafana.com',
-    };
     return (
     return (
       <div>
       <div>
         <PageHeader model={navModel} />
         <PageHeader model={navModel} />
         <div className="page-container page-body">
         <div className="page-container page-body">
-          <OrgActionBar
-            searchQuery={searchQuery}
-            layoutMode={layoutMode}
-            setLayoutMode={mode => setPluginsLayoutMode(mode)}
-            setSearchQuery={query => setPluginsSearchQuery(query)}
-            linkButton={linkButton}
-          />
+          <PluginActionBar />
           {plugins && <PluginList plugins={plugins} layoutMode={layoutMode} />}
           {plugins && <PluginList plugins={plugins} layoutMode={layoutMode} />}
         </div>
         </div>
       </div>
       </div>
@@ -59,14 +46,11 @@ function mapStateToProps(state) {
     navModel: getNavModel(state.navIndex, 'plugins'),
     navModel: getNavModel(state.navIndex, 'plugins'),
     plugins: getPlugins(state.plugins),
     plugins: getPlugins(state.plugins),
     layoutMode: getLayoutMode(state.plugins),
     layoutMode: getLayoutMode(state.plugins),
-    searchQuery: getPluginsSearchQuery(state.plugins),
   };
   };
 }
 }
 
 
 const mapDispatchToProps = {
 const mapDispatchToProps = {
   loadPlugins,
   loadPlugins,
-  setPluginsLayoutMode,
-  setPluginsSearchQuery,
 };
 };
 
 
 export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(PluginListPage));
 export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(PluginListPage));

+ 3 - 2
public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap → public/app/features/plugins/__snapshots__/PluginActionBar.test.tsx.snap

@@ -22,6 +22,7 @@ exports[`Render should render component 1`] = `
       />
       />
     </label>
     </label>
     <LayoutSelector
     <LayoutSelector
+      mode="grid"
       onLayoutModeChanged={[Function]}
       onLayoutModeChanged={[Function]}
     />
     />
   </div>
   </div>
@@ -30,10 +31,10 @@ exports[`Render should render component 1`] = `
   />
   />
   <a
   <a
     className="btn btn-success"
     className="btn btn-success"
-    href="some/url"
+    href="https://grafana.com/plugins?utm_source=grafana_plugin_list"
     target="_blank"
     target="_blank"
   >
   >
-    test
+    Find more plugins on Grafana.com
   </a>
   </a>
 </div>
 </div>
 `;
 `;

+ 1 - 12
public/app/features/plugins/__snapshots__/PluginListPage.test.tsx.snap

@@ -8,18 +8,7 @@ exports[`Render should render component 1`] = `
   <div
   <div
     className="page-container page-body"
     className="page-container page-body"
   >
   >
-    <OrgActionBar
-      layoutMode="grid"
-      linkButton={
-        Object {
-          "href": "https://grafana.com/plugins?utm_source=grafana_plugin_list",
-          "title": "Find more plugins on Grafana.com",
-        }
-      }
-      searchQuery=""
-      setLayoutMode={[Function]}
-      setSearchQuery={[Function]}
-    />
+    <Connect(PluginActionBar) />
     <PluginList
     <PluginList
       layoutMode="grid"
       layoutMode="grid"
       plugins={Array []}
       plugins={Array []}

+ 1 - 1
public/app/features/plugins/state/actions.ts

@@ -24,7 +24,7 @@ export interface SetLayoutModeAction {
   payload: LayoutMode;
   payload: LayoutMode;
 }
 }
 
 
-export const setPluginsLayoutMode = (mode: LayoutMode): SetLayoutModeAction => ({
+export const setLayoutMode = (mode: LayoutMode): SetLayoutModeAction => ({
   type: ActionTypes.SetLayoutMode,
   type: ActionTypes.SetLayoutMode,
   payload: mode,
   payload: mode,
 });
 });

+ 0 - 32
public/app/features/users/InviteesTable.test.tsx

@@ -1,32 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import InviteesTable, { Props } from './InviteesTable';
-import { Invitee } from 'app/types';
-import { getMockInvitees } from './__mocks__/userMocks';
-
-const setup = (propOverrides?: object) => {
-  const props: Props = {
-    invitees: [] as Invitee[],
-    revokeInvite: jest.fn(),
-  };
-
-  Object.assign(props, propOverrides);
-
-  return shallow(<InviteesTable {...props} />);
-};
-
-describe('Render', () => {
-  it('should render component', () => {
-    const wrapper = setup();
-
-    expect(wrapper).toMatchSnapshot();
-  });
-
-  it('should render invitees', () => {
-    const wrapper = setup({
-      invitees: getMockInvitees(5),
-    });
-
-    expect(wrapper).toMatchSnapshot();
-  });
-});

+ 0 - 64
public/app/features/users/InviteesTable.tsx

@@ -1,64 +0,0 @@
-import React, { createRef, PureComponent } from 'react';
-import { Invitee } from 'app/types';
-
-export interface Props {
-  invitees: Invitee[];
-  revokeInvite: (code: string) => void;
-}
-
-export default class InviteesTable extends PureComponent<Props> {
-  private copyUrlRef = createRef<HTMLTextAreaElement>();
-
-  copyToClipboard = () => {
-    const node = this.copyUrlRef.current;
-
-    if (node) {
-      node.select();
-      document.execCommand('copy');
-    }
-  };
-
-  render() {
-    const { invitees, revokeInvite } = this.props;
-
-    return (
-      <table className="filter-table form-inline">
-        <thead>
-          <tr>
-            <th>Email</th>
-            <th>Name</th>
-            <th />
-            <th style={{ width: '34px' }} />
-          </tr>
-        </thead>
-        <tbody>
-          {invitees.map((invitee, index) => {
-            return (
-              <tr key={`${invitee.id}-${index}`}>
-                <td>{invitee.email}</td>
-                <td>{invitee.name}</td>
-                <td className="text-right">
-                  <button className="btn btn-inverse btn-mini" onClick={this.copyToClipboard}>
-                    <textarea
-                      readOnly={true}
-                      value={invitee.url}
-                      style={{ position: 'absolute', right: -1000 }}
-                      ref={this.copyUrlRef}
-                    />
-                    <i className="fa fa-clipboard" /> Copy Invite
-                  </button>
-                  &nbsp;
-                </td>
-                <td>
-                  <button className="btn btn-danger btn-mini" onClick={() => revokeInvite(invitee.code)}>
-                    <i className="fa fa-remove" />
-                  </button>
-                </td>
-              </tr>
-            );
-          })}
-        </tbody>
-      </table>
-    );
-  }
-}

+ 0 - 51
public/app/features/users/UsersActionBar.test.tsx

@@ -1,51 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import { UsersActionBar, Props } from './UsersActionBar';
-
-const setup = (propOverrides?: object) => {
-  const props: Props = {
-    searchQuery: '',
-    setUsersSearchQuery: jest.fn(),
-    showInvites: jest.fn(),
-    pendingInvitesCount: 0,
-    canInvite: false,
-    externalUserMngLinkUrl: '',
-    externalUserMngLinkName: '',
-  };
-
-  Object.assign(props, propOverrides);
-
-  return shallow(<UsersActionBar {...props} />);
-};
-
-describe('Render', () => {
-  it('should render component', () => {
-    const wrapper = setup();
-
-    expect(wrapper).toMatchSnapshot();
-  });
-
-  it('should render pending invites button', () => {
-    const wrapper = setup({
-      pendingInvitesCount: 5,
-    });
-
-    expect(wrapper).toMatchSnapshot();
-  });
-
-  it('should show invite button', () => {
-    const wrapper = setup({
-      canInvite: true,
-    });
-
-    expect(wrapper).toMatchSnapshot();
-  });
-
-  it('should show external user management button', () => {
-    const wrapper = setup({
-      externalUserMngLinkUrl: 'some/url',
-    });
-
-    expect(wrapper).toMatchSnapshot();
-  });
-});

+ 0 - 80
public/app/features/users/UsersActionBar.tsx

@@ -1,80 +0,0 @@
-import React, { PureComponent } from 'react';
-import { connect } from 'react-redux';
-import { setUsersSearchQuery } from './state/actions';
-import { getInviteesCount, getUsersSearchQuery } from './state/selectors';
-
-export interface Props {
-  searchQuery: string;
-  setUsersSearchQuery: typeof setUsersSearchQuery;
-  showInvites: () => void;
-  pendingInvitesCount: number;
-  canInvite: boolean;
-  externalUserMngLinkUrl: string;
-  externalUserMngLinkName: string;
-}
-
-export class UsersActionBar extends PureComponent<Props> {
-  render() {
-    const {
-      canInvite,
-      externalUserMngLinkName,
-      externalUserMngLinkUrl,
-      searchQuery,
-      pendingInvitesCount,
-      setUsersSearchQuery,
-      showInvites,
-    } = this.props;
-
-    return (
-      <div className="page-action-bar">
-        <div className="gf-form gf-form--grow">
-          <label className="gf-form--has-input-icon">
-            <input
-              type="text"
-              className="gf-form-input width-20"
-              value={searchQuery}
-              onChange={event => setUsersSearchQuery(event.target.value)}
-              placeholder="Filter by name or type"
-            />
-            <i className="gf-form-input-icon fa fa-search" />
-          </label>
-
-          <div className="page-action-bar__spacer" />
-          {pendingInvitesCount > 0 && (
-            <button className="btn btn-inverse" onClick={showInvites}>
-              Pending Invites ({pendingInvitesCount})
-            </button>
-          )}
-          {canInvite && (
-            <a className="btn btn-success" href="org/users/invite">
-              <i className="fa fa-plus" />
-              <span>Invite</span>
-            </a>
-          )}
-          {externalUserMngLinkUrl && (
-            <a className="btn btn-success" href={externalUserMngLinkUrl} target="_blank">
-              <i className="fa fa-external-link-square" />
-              {externalUserMngLinkName}
-            </a>
-          )}
-        </div>
-      </div>
-    );
-  }
-}
-
-function mapStateToProps(state) {
-  return {
-    searchQuery: getUsersSearchQuery(state.users),
-    pendingInvitesCount: getInviteesCount(state.users),
-    externalUserMngLinkName: state.users.externalUserMngLinkName,
-    externalUserMngLinkUrl: state.users.externalUserMngLinkUrl,
-    canInvite: state.users.canInvite,
-  };
-}
-
-const mapDispatchToProps = {
-  setUsersSearchQuery,
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(UsersActionBar);

+ 0 - 55
public/app/features/users/UsersListPage.test.tsx

@@ -1,55 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import { UsersListPage, Props } from './UsersListPage';
-import { Invitee, NavModel, OrgUser } from 'app/types';
-import { getMockUser } from './__mocks__/userMocks';
-import appEvents from '../../core/app_events';
-
-jest.mock('../../core/app_events', () => ({
-  emit: jest.fn(),
-}));
-
-const setup = (propOverrides?: object) => {
-  const props: Props = {
-    navModel: {} as NavModel,
-    users: [] as OrgUser[],
-    invitees: [] as Invitee[],
-    searchQuery: '',
-    externalUserMngInfo: '',
-    revokeInvite: jest.fn(),
-    loadInvitees: jest.fn(),
-    loadUsers: jest.fn(),
-    updateUser: jest.fn(),
-    removeUser: jest.fn(),
-    setUsersSearchQuery: jest.fn(),
-  };
-
-  Object.assign(props, propOverrides);
-
-  const wrapper = shallow(<UsersListPage {...props} />);
-  const instance = wrapper.instance() as UsersListPage;
-
-  return {
-    wrapper,
-    instance,
-  };
-};
-
-describe('Render', () => {
-  it('should render component', () => {
-    const { wrapper } = setup();
-
-    expect(wrapper).toMatchSnapshot();
-  });
-});
-
-describe('Functions', () => {
-  it('should emit show remove user modal', () => {
-    const { instance } = setup();
-    const mockUser = getMockUser();
-
-    instance.onRemoveUser(mockUser);
-
-    expect(appEvents.emit).toHaveBeenCalled();
-  });
-});

+ 0 - 125
public/app/features/users/UsersListPage.tsx

@@ -1,125 +0,0 @@
-import React, { PureComponent } from 'react';
-import { hot } from 'react-hot-loader';
-import { connect } from 'react-redux';
-import PageHeader from 'app/core/components/PageHeader/PageHeader';
-import UsersActionBar from './UsersActionBar';
-import UsersTable from 'app/features/users/UsersTable';
-import InviteesTable from './InviteesTable';
-import { Invitee, NavModel, OrgUser } from 'app/types';
-import appEvents from 'app/core/app_events';
-import { loadUsers, loadInvitees, revokeInvite, setUsersSearchQuery, updateUser, removeUser } from './state/actions';
-import { getNavModel } from '../../core/selectors/navModel';
-import { getInvitees, getUsers, getUsersSearchQuery } from './state/selectors';
-
-export interface Props {
-  navModel: NavModel;
-  invitees: Invitee[];
-  users: OrgUser[];
-  searchQuery: string;
-  externalUserMngInfo: string;
-  loadUsers: typeof loadUsers;
-  loadInvitees: typeof loadInvitees;
-  setUsersSearchQuery: typeof setUsersSearchQuery;
-  updateUser: typeof updateUser;
-  removeUser: typeof removeUser;
-  revokeInvite: typeof revokeInvite;
-}
-
-export interface State {
-  showInvites: boolean;
-}
-
-export class UsersListPage extends PureComponent<Props, State> {
-  state = {
-    showInvites: false,
-  };
-
-  componentDidMount() {
-    this.fetchUsers();
-    this.fetchInvitees();
-  }
-
-  async fetchUsers() {
-    return await this.props.loadUsers();
-  }
-
-  async fetchInvitees() {
-    return await this.props.loadInvitees();
-  }
-
-  onRoleChange = (role, user) => {
-    const updatedUser = { ...user, role: role };
-
-    this.props.updateUser(updatedUser);
-  };
-
-  onRemoveUser = user => {
-    appEvents.emit('confirm-modal', {
-      title: 'Delete',
-      text: 'Are you sure you want to delete user ' + user.login + '?',
-      yesText: 'Delete',
-      icon: 'fa-warning',
-      onConfirm: () => {
-        this.props.removeUser(user.userId);
-      },
-    });
-  };
-
-  onRevokeInvite = code => {
-    this.props.revokeInvite(code);
-  };
-
-  showInvites = () => {
-    this.setState(prevState => ({
-      showInvites: !prevState.showInvites,
-    }));
-  };
-
-  render() {
-    const { externalUserMngInfo, invitees, navModel, users } = this.props;
-
-    return (
-      <div>
-        <PageHeader model={navModel} />
-        <div className="page-container page-body">
-          <UsersActionBar showInvites={this.showInvites} />
-          {externalUserMngInfo && (
-            <div className="grafana-info-box">
-              <span>{externalUserMngInfo}</span>
-            </div>
-          )}
-          {this.state.showInvites ? (
-            <InviteesTable invitees={invitees} revokeInvite={code => this.onRevokeInvite(code)} />
-          ) : (
-            <UsersTable
-              users={users}
-              onRoleChange={(role, user) => this.onRoleChange(role, user)}
-              onRemoveUser={user => this.onRemoveUser(user)}
-            />
-          )}
-        </div>
-      </div>
-    );
-  }
-}
-
-function mapStateToProps(state) {
-  return {
-    navModel: getNavModel(state.navIndex, 'users'),
-    users: getUsers(state.users),
-    searchQuery: getUsersSearchQuery(state.users),
-    invitees: getInvitees(state.users),
-    externalUserMngInfo: state.users.externalUserMngInfo,
-  };
-}
-
-const mapDispatchToProps = {
-  loadUsers,
-  loadInvitees,
-  setUsersSearchQuery,
-  updateUser,
-  removeUser,
-  revokeInvite,
-};
-
-export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(UsersListPage));

+ 0 - 33
public/app/features/users/UsersTable.test.tsx

@@ -1,33 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import UsersTable, { Props } from './UsersTable';
-import { OrgUser } from 'app/types';
-import { getMockUsers } from './__mocks__/userMocks';
-
-const setup = (propOverrides?: object) => {
-  const props: Props = {
-    users: [] as OrgUser[],
-    onRoleChange: jest.fn(),
-    onRemoveUser: jest.fn(),
-  };
-
-  Object.assign(props, propOverrides);
-
-  return shallow(<UsersTable {...props} />);
-};
-
-describe('Render', () => {
-  it('should render component', () => {
-    const wrapper = setup();
-
-    expect(wrapper).toMatchSnapshot();
-  });
-
-  it('should render users table', () => {
-    const wrapper = setup({
-      users: getMockUsers(5),
-    });
-
-    expect(wrapper).toMatchSnapshot();
-  });
-});

+ 0 - 67
public/app/features/users/UsersTable.tsx

@@ -1,67 +0,0 @@
-import React, { SFC } from 'react';
-import { OrgUser } from 'app/types';
-
-export interface Props {
-  users: OrgUser[];
-  onRoleChange: (role: string, user: OrgUser) => void;
-  onRemoveUser: (user: OrgUser) => void;
-}
-
-const UsersTable: SFC<Props> = props => {
-  const { users, onRoleChange, onRemoveUser } = props;
-
-  return (
-    <table className="filter-table form-inline">
-      <thead>
-        <tr>
-          <th />
-          <th>Login</th>
-          <th>Email</th>
-          <th>Seen</th>
-          <th>Role</th>
-          <th style={{ width: '34px' }} />
-        </tr>
-      </thead>
-      <tbody>
-        {users.map((user, index) => {
-          return (
-            <tr key={`${user.userId}-${index}`}>
-              <td className="width-4 text-center">
-                <img className="filter-table__avatar" src={user.avatarUrl} />
-              </td>
-              <td>{user.login}</td>
-              <td>
-                <span className="ellipsis">{user.email}</span>
-              </td>
-              <td>{user.lastSeenAtAge}</td>
-              <td>
-                <div className="gf-form-select-wrapper width-12">
-                  <select
-                    value={user.role}
-                    className="gf-form-input"
-                    onChange={event => onRoleChange(event.target.value, user)}
-                  >
-                    {['Viewer', 'Editor', 'Admin'].map((option, index) => {
-                      return (
-                        <option value={option} key={`${option}-${index}`}>
-                          {option}
-                        </option>
-                      );
-                    })}
-                  </select>
-                </div>
-              </td>
-              <td>
-                <div onClick={() => onRemoveUser(user)} className="btn btn-danger btn-mini">
-                  <i className="fa fa-remove" />
-                </div>
-              </td>
-            </tr>
-          );
-        })}
-      </tbody>
-    </table>
-  );
-};
-
-export default UsersTable;

+ 0 - 56
public/app/features/users/__mocks__/userMocks.ts

@@ -1,56 +0,0 @@
-export const getMockUsers = (amount: number) => {
-  const users = [];
-
-  for (let i = 0; i <= amount; i++) {
-    users.push({
-      avatarUrl: 'url/to/avatar',
-      email: `user-${i}@test.com`,
-      lastSeenAt: '2018-10-01',
-      lastSeenAtAge: '',
-      login: `user-${i}`,
-      orgId: 1,
-      role: 'Admin',
-      userId: i,
-    });
-  }
-
-  return users;
-};
-
-export const getMockUser = () => {
-  return {
-    avatarUrl: 'url/to/avatar',
-    email: `user@test.com`,
-    lastSeenAt: '2018-10-01',
-    lastSeenAtAge: '',
-    login: `user`,
-    orgId: 1,
-    role: 'Admin',
-    userId: 2,
-  };
-};
-
-export const getMockInvitees = (amount: number) => {
-  const invitees = [];
-
-  for (let i = 0; i <= amount; i++) {
-    invitees.push({
-      code: `asdfasdfsadf-${i}`,
-      createdOn: '2018-10-02',
-      email: `invitee-${i}@test.com`,
-      emailSent: true,
-      emailSentOn: '2018-10-02',
-      id: i,
-      invitedByEmail: 'admin@grafana.com',
-      invitedByLogin: 'admin',
-      invitedByName: 'admin',
-      name: `invitee-${i}`,
-      orgId: 1,
-      role: 'viewer',
-      status: 'not accepted',
-      url: `localhost/invite/$${i}`,
-    });
-  }
-
-  return invitees;
-};

+ 0 - 318
public/app/features/users/__snapshots__/InviteesTable.test.tsx.snap

@@ -1,318 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Render should render component 1`] = `
-<table
-  className="filter-table form-inline"
->
-  <thead>
-    <tr>
-      <th>
-        Email
-      </th>
-      <th>
-        Name
-      </th>
-      <th />
-      <th
-        style={
-          Object {
-            "width": "34px",
-          }
-        }
-      />
-    </tr>
-  </thead>
-  <tbody />
-</table>
-`;
-
-exports[`Render should render invitees 1`] = `
-<table
-  className="filter-table form-inline"
->
-  <thead>
-    <tr>
-      <th>
-        Email
-      </th>
-      <th>
-        Name
-      </th>
-      <th />
-      <th
-        style={
-          Object {
-            "width": "34px",
-          }
-        }
-      />
-    </tr>
-  </thead>
-  <tbody>
-    <tr
-      key="0-0"
-    >
-      <td>
-        invitee-0@test.com
-      </td>
-      <td>
-        invitee-0
-      </td>
-      <td
-        className="text-right"
-      >
-        <button
-          className="btn btn-inverse btn-mini"
-          onClick={[Function]}
-        >
-          <textarea
-            readOnly={true}
-            style={
-              Object {
-                "position": "absolute",
-                "right": -1000,
-              }
-            }
-            value="localhost/invite/$0"
-          />
-          <i
-            className="fa fa-clipboard"
-          />
-           Copy Invite
-        </button>
-         
-      </td>
-      <td>
-        <button
-          className="btn btn-danger btn-mini"
-          onClick={[Function]}
-        >
-          <i
-            className="fa fa-remove"
-          />
-        </button>
-      </td>
-    </tr>
-    <tr
-      key="1-1"
-    >
-      <td>
-        invitee-1@test.com
-      </td>
-      <td>
-        invitee-1
-      </td>
-      <td
-        className="text-right"
-      >
-        <button
-          className="btn btn-inverse btn-mini"
-          onClick={[Function]}
-        >
-          <textarea
-            readOnly={true}
-            style={
-              Object {
-                "position": "absolute",
-                "right": -1000,
-              }
-            }
-            value="localhost/invite/$1"
-          />
-          <i
-            className="fa fa-clipboard"
-          />
-           Copy Invite
-        </button>
-         
-      </td>
-      <td>
-        <button
-          className="btn btn-danger btn-mini"
-          onClick={[Function]}
-        >
-          <i
-            className="fa fa-remove"
-          />
-        </button>
-      </td>
-    </tr>
-    <tr
-      key="2-2"
-    >
-      <td>
-        invitee-2@test.com
-      </td>
-      <td>
-        invitee-2
-      </td>
-      <td
-        className="text-right"
-      >
-        <button
-          className="btn btn-inverse btn-mini"
-          onClick={[Function]}
-        >
-          <textarea
-            readOnly={true}
-            style={
-              Object {
-                "position": "absolute",
-                "right": -1000,
-              }
-            }
-            value="localhost/invite/$2"
-          />
-          <i
-            className="fa fa-clipboard"
-          />
-           Copy Invite
-        </button>
-         
-      </td>
-      <td>
-        <button
-          className="btn btn-danger btn-mini"
-          onClick={[Function]}
-        >
-          <i
-            className="fa fa-remove"
-          />
-        </button>
-      </td>
-    </tr>
-    <tr
-      key="3-3"
-    >
-      <td>
-        invitee-3@test.com
-      </td>
-      <td>
-        invitee-3
-      </td>
-      <td
-        className="text-right"
-      >
-        <button
-          className="btn btn-inverse btn-mini"
-          onClick={[Function]}
-        >
-          <textarea
-            readOnly={true}
-            style={
-              Object {
-                "position": "absolute",
-                "right": -1000,
-              }
-            }
-            value="localhost/invite/$3"
-          />
-          <i
-            className="fa fa-clipboard"
-          />
-           Copy Invite
-        </button>
-         
-      </td>
-      <td>
-        <button
-          className="btn btn-danger btn-mini"
-          onClick={[Function]}
-        >
-          <i
-            className="fa fa-remove"
-          />
-        </button>
-      </td>
-    </tr>
-    <tr
-      key="4-4"
-    >
-      <td>
-        invitee-4@test.com
-      </td>
-      <td>
-        invitee-4
-      </td>
-      <td
-        className="text-right"
-      >
-        <button
-          className="btn btn-inverse btn-mini"
-          onClick={[Function]}
-        >
-          <textarea
-            readOnly={true}
-            style={
-              Object {
-                "position": "absolute",
-                "right": -1000,
-              }
-            }
-            value="localhost/invite/$4"
-          />
-          <i
-            className="fa fa-clipboard"
-          />
-           Copy Invite
-        </button>
-         
-      </td>
-      <td>
-        <button
-          className="btn btn-danger btn-mini"
-          onClick={[Function]}
-        >
-          <i
-            className="fa fa-remove"
-          />
-        </button>
-      </td>
-    </tr>
-    <tr
-      key="5-5"
-    >
-      <td>
-        invitee-5@test.com
-      </td>
-      <td>
-        invitee-5
-      </td>
-      <td
-        className="text-right"
-      >
-        <button
-          className="btn btn-inverse btn-mini"
-          onClick={[Function]}
-        >
-          <textarea
-            readOnly={true}
-            style={
-              Object {
-                "position": "absolute",
-                "right": -1000,
-              }
-            }
-            value="localhost/invite/$5"
-          />
-          <i
-            className="fa fa-clipboard"
-          />
-           Copy Invite
-        </button>
-         
-      </td>
-      <td>
-        <button
-          className="btn btn-danger btn-mini"
-          onClick={[Function]}
-        >
-          <i
-            className="fa fa-remove"
-          />
-        </button>
-      </td>
-    </tr>
-  </tbody>
-</table>
-`;

+ 0 - 141
public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap

@@ -1,141 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Render should render component 1`] = `
-<div
-  className="page-action-bar"
->
-  <div
-    className="gf-form gf-form--grow"
-  >
-    <label
-      className="gf-form--has-input-icon"
-    >
-      <input
-        className="gf-form-input width-20"
-        onChange={[Function]}
-        placeholder="Filter by name or type"
-        type="text"
-        value=""
-      />
-      <i
-        className="gf-form-input-icon fa fa-search"
-      />
-    </label>
-    <div
-      className="page-action-bar__spacer"
-    />
-  </div>
-</div>
-`;
-
-exports[`Render should render pending invites button 1`] = `
-<div
-  className="page-action-bar"
->
-  <div
-    className="gf-form gf-form--grow"
-  >
-    <label
-      className="gf-form--has-input-icon"
-    >
-      <input
-        className="gf-form-input width-20"
-        onChange={[Function]}
-        placeholder="Filter by name or type"
-        type="text"
-        value=""
-      />
-      <i
-        className="gf-form-input-icon fa fa-search"
-      />
-    </label>
-    <div
-      className="page-action-bar__spacer"
-    />
-    <button
-      className="btn btn-inverse"
-      onClick={[MockFunction]}
-    >
-      Pending Invites (
-      5
-      )
-    </button>
-  </div>
-</div>
-`;
-
-exports[`Render should show external user management button 1`] = `
-<div
-  className="page-action-bar"
->
-  <div
-    className="gf-form gf-form--grow"
-  >
-    <label
-      className="gf-form--has-input-icon"
-    >
-      <input
-        className="gf-form-input width-20"
-        onChange={[Function]}
-        placeholder="Filter by name or type"
-        type="text"
-        value=""
-      />
-      <i
-        className="gf-form-input-icon fa fa-search"
-      />
-    </label>
-    <div
-      className="page-action-bar__spacer"
-    />
-    <a
-      className="btn btn-success"
-      href="some/url"
-      target="_blank"
-    >
-      <i
-        className="fa fa-external-link-square"
-      />
-    </a>
-  </div>
-</div>
-`;
-
-exports[`Render should show invite button 1`] = `
-<div
-  className="page-action-bar"
->
-  <div
-    className="gf-form gf-form--grow"
-  >
-    <label
-      className="gf-form--has-input-icon"
-    >
-      <input
-        className="gf-form-input width-20"
-        onChange={[Function]}
-        placeholder="Filter by name or type"
-        type="text"
-        value=""
-      />
-      <i
-        className="gf-form-input-icon fa fa-search"
-      />
-    </label>
-    <div
-      className="page-action-bar__spacer"
-    />
-    <a
-      className="btn btn-success"
-      href="org/users/invite"
-    >
-      <i
-        className="fa fa-plus"
-      />
-      <span>
-        Invite
-      </span>
-    </a>
-  </div>
-</div>
-`;

+ 0 - 21
public/app/features/users/__snapshots__/UsersListPage.test.tsx.snap

@@ -1,21 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Render should render component 1`] = `
-<div>
-  <PageHeader
-    model={Object {}}
-  />
-  <div
-    className="page-container page-body"
-  >
-    <Connect(UsersActionBar)
-      showInvites={[Function]}
-    />
-    <UsersTable
-      onRemoveUser={[Function]}
-      onRoleChange={[Function]}
-      users={Array []}
-    />
-  </div>
-</div>
-`;

+ 0 - 444
public/app/features/users/__snapshots__/UsersTable.test.tsx.snap

@@ -1,444 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Render should render component 1`] = `
-<table
-  className="filter-table form-inline"
->
-  <thead>
-    <tr>
-      <th />
-      <th>
-        Login
-      </th>
-      <th>
-        Email
-      </th>
-      <th>
-        Seen
-      </th>
-      <th>
-        Role
-      </th>
-      <th
-        style={
-          Object {
-            "width": "34px",
-          }
-        }
-      />
-    </tr>
-  </thead>
-  <tbody />
-</table>
-`;
-
-exports[`Render should render users table 1`] = `
-<table
-  className="filter-table form-inline"
->
-  <thead>
-    <tr>
-      <th />
-      <th>
-        Login
-      </th>
-      <th>
-        Email
-      </th>
-      <th>
-        Seen
-      </th>
-      <th>
-        Role
-      </th>
-      <th
-        style={
-          Object {
-            "width": "34px",
-          }
-        }
-      />
-    </tr>
-  </thead>
-  <tbody>
-    <tr
-      key="0-0"
-    >
-      <td
-        className="width-4 text-center"
-      >
-        <img
-          className="filter-table__avatar"
-          src="url/to/avatar"
-        />
-      </td>
-      <td>
-        user-0
-      </td>
-      <td>
-        <span
-          className="ellipsis"
-        >
-          user-0@test.com
-        </span>
-      </td>
-      <td />
-      <td>
-        <div
-          className="gf-form-select-wrapper width-12"
-        >
-          <select
-            className="gf-form-input"
-            onChange={[Function]}
-            value="Admin"
-          >
-            <option
-              key="Viewer-0"
-              value="Viewer"
-            >
-              Viewer
-            </option>
-            <option
-              key="Editor-1"
-              value="Editor"
-            >
-              Editor
-            </option>
-            <option
-              key="Admin-2"
-              value="Admin"
-            >
-              Admin
-            </option>
-          </select>
-        </div>
-      </td>
-      <td>
-        <div
-          className="btn btn-danger btn-mini"
-          onClick={[Function]}
-        >
-          <i
-            className="fa fa-remove"
-          />
-        </div>
-      </td>
-    </tr>
-    <tr
-      key="1-1"
-    >
-      <td
-        className="width-4 text-center"
-      >
-        <img
-          className="filter-table__avatar"
-          src="url/to/avatar"
-        />
-      </td>
-      <td>
-        user-1
-      </td>
-      <td>
-        <span
-          className="ellipsis"
-        >
-          user-1@test.com
-        </span>
-      </td>
-      <td />
-      <td>
-        <div
-          className="gf-form-select-wrapper width-12"
-        >
-          <select
-            className="gf-form-input"
-            onChange={[Function]}
-            value="Admin"
-          >
-            <option
-              key="Viewer-0"
-              value="Viewer"
-            >
-              Viewer
-            </option>
-            <option
-              key="Editor-1"
-              value="Editor"
-            >
-              Editor
-            </option>
-            <option
-              key="Admin-2"
-              value="Admin"
-            >
-              Admin
-            </option>
-          </select>
-        </div>
-      </td>
-      <td>
-        <div
-          className="btn btn-danger btn-mini"
-          onClick={[Function]}
-        >
-          <i
-            className="fa fa-remove"
-          />
-        </div>
-      </td>
-    </tr>
-    <tr
-      key="2-2"
-    >
-      <td
-        className="width-4 text-center"
-      >
-        <img
-          className="filter-table__avatar"
-          src="url/to/avatar"
-        />
-      </td>
-      <td>
-        user-2
-      </td>
-      <td>
-        <span
-          className="ellipsis"
-        >
-          user-2@test.com
-        </span>
-      </td>
-      <td />
-      <td>
-        <div
-          className="gf-form-select-wrapper width-12"
-        >
-          <select
-            className="gf-form-input"
-            onChange={[Function]}
-            value="Admin"
-          >
-            <option
-              key="Viewer-0"
-              value="Viewer"
-            >
-              Viewer
-            </option>
-            <option
-              key="Editor-1"
-              value="Editor"
-            >
-              Editor
-            </option>
-            <option
-              key="Admin-2"
-              value="Admin"
-            >
-              Admin
-            </option>
-          </select>
-        </div>
-      </td>
-      <td>
-        <div
-          className="btn btn-danger btn-mini"
-          onClick={[Function]}
-        >
-          <i
-            className="fa fa-remove"
-          />
-        </div>
-      </td>
-    </tr>
-    <tr
-      key="3-3"
-    >
-      <td
-        className="width-4 text-center"
-      >
-        <img
-          className="filter-table__avatar"
-          src="url/to/avatar"
-        />
-      </td>
-      <td>
-        user-3
-      </td>
-      <td>
-        <span
-          className="ellipsis"
-        >
-          user-3@test.com
-        </span>
-      </td>
-      <td />
-      <td>
-        <div
-          className="gf-form-select-wrapper width-12"
-        >
-          <select
-            className="gf-form-input"
-            onChange={[Function]}
-            value="Admin"
-          >
-            <option
-              key="Viewer-0"
-              value="Viewer"
-            >
-              Viewer
-            </option>
-            <option
-              key="Editor-1"
-              value="Editor"
-            >
-              Editor
-            </option>
-            <option
-              key="Admin-2"
-              value="Admin"
-            >
-              Admin
-            </option>
-          </select>
-        </div>
-      </td>
-      <td>
-        <div
-          className="btn btn-danger btn-mini"
-          onClick={[Function]}
-        >
-          <i
-            className="fa fa-remove"
-          />
-        </div>
-      </td>
-    </tr>
-    <tr
-      key="4-4"
-    >
-      <td
-        className="width-4 text-center"
-      >
-        <img
-          className="filter-table__avatar"
-          src="url/to/avatar"
-        />
-      </td>
-      <td>
-        user-4
-      </td>
-      <td>
-        <span
-          className="ellipsis"
-        >
-          user-4@test.com
-        </span>
-      </td>
-      <td />
-      <td>
-        <div
-          className="gf-form-select-wrapper width-12"
-        >
-          <select
-            className="gf-form-input"
-            onChange={[Function]}
-            value="Admin"
-          >
-            <option
-              key="Viewer-0"
-              value="Viewer"
-            >
-              Viewer
-            </option>
-            <option
-              key="Editor-1"
-              value="Editor"
-            >
-              Editor
-            </option>
-            <option
-              key="Admin-2"
-              value="Admin"
-            >
-              Admin
-            </option>
-          </select>
-        </div>
-      </td>
-      <td>
-        <div
-          className="btn btn-danger btn-mini"
-          onClick={[Function]}
-        >
-          <i
-            className="fa fa-remove"
-          />
-        </div>
-      </td>
-    </tr>
-    <tr
-      key="5-5"
-    >
-      <td
-        className="width-4 text-center"
-      >
-        <img
-          className="filter-table__avatar"
-          src="url/to/avatar"
-        />
-      </td>
-      <td>
-        user-5
-      </td>
-      <td>
-        <span
-          className="ellipsis"
-        >
-          user-5@test.com
-        </span>
-      </td>
-      <td />
-      <td>
-        <div
-          className="gf-form-select-wrapper width-12"
-        >
-          <select
-            className="gf-form-input"
-            onChange={[Function]}
-            value="Admin"
-          >
-            <option
-              key="Viewer-0"
-              value="Viewer"
-            >
-              Viewer
-            </option>
-            <option
-              key="Editor-1"
-              value="Editor"
-            >
-              Editor
-            </option>
-            <option
-              key="Admin-2"
-              value="Admin"
-            >
-              Admin
-            </option>
-          </select>
-        </div>
-      </td>
-      <td>
-        <div
-          className="btn btn-danger btn-mini"
-          onClick={[Function]}
-        >
-          <i
-            className="fa fa-remove"
-          />
-        </div>
-      </td>
-    </tr>
-  </tbody>
-</table>
-`;

+ 0 - 79
public/app/features/users/state/actions.ts

@@ -1,79 +0,0 @@
-import { ThunkAction } from 'redux-thunk';
-import { StoreState } from '../../../types';
-import { getBackendSrv } from '../../../core/services/backend_srv';
-import { Invitee, OrgUser } from 'app/types';
-
-export enum ActionTypes {
-  LoadUsers = 'LOAD_USERS',
-  LoadInvitees = 'LOAD_INVITEES',
-  SetUsersSearchQuery = 'SET_USERS_SEARCH_QUERY',
-}
-
-export interface LoadUsersAction {
-  type: ActionTypes.LoadUsers;
-  payload: OrgUser[];
-}
-
-export interface LoadInviteesAction {
-  type: ActionTypes.LoadInvitees;
-  payload: Invitee[];
-}
-
-export interface SetUsersSearchQueryAction {
-  type: ActionTypes.SetUsersSearchQuery;
-  payload: string;
-}
-
-const usersLoaded = (users: OrgUser[]): LoadUsersAction => ({
-  type: ActionTypes.LoadUsers,
-  payload: users,
-});
-
-const inviteesLoaded = (invitees: Invitee[]): LoadInviteesAction => ({
-  type: ActionTypes.LoadInvitees,
-  payload: invitees,
-});
-
-export const setUsersSearchQuery = (query: string): SetUsersSearchQueryAction => ({
-  type: ActionTypes.SetUsersSearchQuery,
-  payload: query,
-});
-
-export type Action = LoadUsersAction | SetUsersSearchQueryAction | LoadInviteesAction;
-
-type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
-
-export function loadUsers(): ThunkResult<void> {
-  return async dispatch => {
-    const users = await getBackendSrv().get('/api/org/users');
-    dispatch(usersLoaded(users));
-  };
-}
-
-export function loadInvitees(): ThunkResult<void> {
-  return async dispatch => {
-    const invitees = await getBackendSrv().get('/api/org/invites');
-    dispatch(inviteesLoaded(invitees));
-  };
-}
-
-export function updateUser(user: OrgUser): ThunkResult<void> {
-  return async dispatch => {
-    await getBackendSrv().patch(`/api/org/users/${user.userId}`, user);
-    dispatch(loadUsers());
-  };
-}
-
-export function removeUser(userId: number): ThunkResult<void> {
-  return async dispatch => {
-    await getBackendSrv().delete(`/api/org/users/${userId}`);
-    dispatch(loadUsers());
-  };
-}
-
-export function revokeInvite(code: string): ThunkResult<void> {
-  return async dispatch => {
-    await getBackendSrv().patch(`/api/org/invites/${code}/revoke`, {});
-    dispatch(loadInvitees());
-  };
-}

+ 0 - 32
public/app/features/users/state/reducers.ts

@@ -1,32 +0,0 @@
-import { Invitee, OrgUser, UsersState } from 'app/types';
-import { Action, ActionTypes } from './actions';
-import config from '../../../core/config';
-
-export const initialState: UsersState = {
-  invitees: [] as Invitee[],
-  users: [] as OrgUser[],
-  searchQuery: '',
-  canInvite: !config.disableLoginForm && !config.externalUserMngLinkName,
-  externalUserMngInfo: config.externalUserMngInfo,
-  externalUserMngLinkName: config.externalUserMngLinkName,
-  externalUserMngLinkUrl: config.externalUserMngLinkUrl,
-};
-
-export const usersReducer = (state = initialState, action: Action): UsersState => {
-  switch (action.type) {
-    case ActionTypes.LoadUsers:
-      return { ...state, users: action.payload };
-
-    case ActionTypes.LoadInvitees:
-      return { ...state, invitees: action.payload };
-
-    case ActionTypes.SetUsersSearchQuery:
-      return { ...state, searchQuery: action.payload };
-  }
-
-  return state;
-};
-
-export default {
-  users: usersReducer,
-};

+ 0 - 18
public/app/features/users/state/selectors.ts

@@ -1,18 +0,0 @@
-export const getUsers = state => {
-  const regex = new RegExp(state.searchQuery, 'i');
-
-  return state.users.filter(user => {
-    return regex.test(user.login) || regex.test(user.email);
-  });
-};
-
-export const getInvitees = state => {
-  const regex = new RegExp(state.searchQuery, 'i');
-
-  return state.invitees.filter(invitee => {
-    return regex.test(invitee.name) || regex.test(invitee.email);
-  });
-};
-
-export const getInviteesCount = state => state.invitees.length;
-export const getUsersSearchQuery = state => state.searchQuery;

+ 3 - 5
public/app/routes/routes.ts

@@ -10,7 +10,6 @@ import PluginListPage from 'app/features/plugins/PluginListPage';
 import FolderSettingsPage from 'app/features/folders/FolderSettingsPage';
 import FolderSettingsPage from 'app/features/folders/FolderSettingsPage';
 import FolderPermissions from 'app/features/folders/FolderPermissions';
 import FolderPermissions from 'app/features/folders/FolderPermissions';
 import DataSourcesListPage from 'app/features/datasources/DataSourcesListPage';
 import DataSourcesListPage from 'app/features/datasources/DataSourcesListPage';
-import UsersListPage from 'app/features/users/UsersListPage';
 
 
 /** @ngInject */
 /** @ngInject */
 export function setupAngularRoutes($routeProvider, $locationProvider) {
 export function setupAngularRoutes($routeProvider, $locationProvider) {
@@ -134,10 +133,9 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
       controller: 'NewOrgCtrl',
       controller: 'NewOrgCtrl',
     })
     })
     .when('/org/users', {
     .when('/org/users', {
-      template: '<react-container />',
-      resolve: {
-        component: () => UsersListPage,
-      },
+      templateUrl: 'public/app/features/org/partials/orgUsers.html',
+      controller: 'OrgUsersCtrl',
+      controllerAs: 'ctrl',
     })
     })
     .when('/org/users/invite', {
     .when('/org/users/invite', {
       templateUrl: 'public/app/features/org/partials/invite.html',
       templateUrl: 'public/app/features/org/partials/invite.html',

+ 0 - 2
public/app/store/configureStore.ts

@@ -9,7 +9,6 @@ import foldersReducers from 'app/features/folders/state/reducers';
 import dashboardReducers from 'app/features/dashboard/state/reducers';
 import dashboardReducers from 'app/features/dashboard/state/reducers';
 import pluginReducers from 'app/features/plugins/state/reducers';
 import pluginReducers from 'app/features/plugins/state/reducers';
 import dataSourcesReducers from 'app/features/datasources/state/reducers';
 import dataSourcesReducers from 'app/features/datasources/state/reducers';
-import usersReducers from 'app/features/users/state/reducers';
 
 
 const rootReducer = combineReducers({
 const rootReducer = combineReducers({
   ...sharedReducers,
   ...sharedReducers,
@@ -20,7 +19,6 @@ const rootReducer = combineReducers({
   ...dashboardReducers,
   ...dashboardReducers,
   ...pluginReducers,
   ...pluginReducers,
   ...dataSourcesReducers,
   ...dataSourcesReducers,
-  ...usersReducers,
 });
 });
 
 
 export let store;
 export let store;

+ 2 - 7
public/app/types/index.ts

@@ -6,7 +6,7 @@ import { FolderDTO, FolderState, FolderInfo } from './folders';
 import { DashboardState } from './dashboard';
 import { DashboardState } from './dashboard';
 import { DashboardAcl, OrgRole, PermissionLevel } from './acl';
 import { DashboardAcl, OrgRole, PermissionLevel } from './acl';
 import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys';
 import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys';
-import { Invitee, OrgUser, User, UsersState } from './user';
+import { User } from './user';
 import { DataSource, DataSourcesState } from './datasources';
 import { DataSource, DataSourcesState } from './datasources';
 import { PluginMeta, Plugin, PluginsState } from './plugins';
 import { PluginMeta, Plugin, PluginsState } from './plugins';
 
 
@@ -38,13 +38,10 @@ export {
   ApiKey,
   ApiKey,
   ApiKeysState,
   ApiKeysState,
   NewApiKey,
   NewApiKey,
+  User,
   Plugin,
   Plugin,
   PluginsState,
   PluginsState,
   DataSourcesState,
   DataSourcesState,
-  Invitee,
-  OrgUser,
-  User,
-  UsersState,
 };
 };
 
 
 export interface StoreState {
 export interface StoreState {
@@ -55,6 +52,4 @@ export interface StoreState {
   team: TeamState;
   team: TeamState;
   folder: FolderState;
   folder: FolderState;
   dashboard: DashboardState;
   dashboard: DashboardState;
-  dataSources: DataSourcesState;
-  users: UsersState;
 }
 }

+ 1 - 39
public/app/types/user.ts

@@ -1,44 +1,6 @@
-export interface OrgUser {
-  avatarUrl: string;
-  email: string;
-  lastSeenAt: string;
-  lastSeenAtAge: string;
-  login: string;
-  orgId: number;
-  role: string;
-  userId: number;
-}
-
-export interface User {
+export interface User {
   id: number;
   id: number;
   label: string;
   label: string;
   avatarUrl: string;
   avatarUrl: string;
   login: string;
   login: string;
 }
 }
-
-export interface Invitee {
-  code: string;
-  createdOn: string;
-  email: string;
-  emailSent: boolean;
-  emailSentOn: string;
-  id: number;
-  invitedByEmail: string;
-  invitedByLogin: string;
-  invitedByName: string;
-  name: string;
-  orgId: number;
-  role: string;
-  status: string;
-  url: string;
-}
-
-export interface UsersState {
-  users: OrgUser[];
-  invitees: Invitee[];
-  searchQuery: string;
-  canInvite: boolean;
-  externalUserMngLinkUrl: string;
-  externalUserMngLinkName: string;
-  externalUserMngInfo: string;
-}

+ 0 - 37
public/app/types/users.ts

@@ -1,37 +0,0 @@
-export interface Invitee {
-  code: string;
-  createdOn: string;
-  email: string;
-  emailSent: boolean;
-  emailSentOn: string;
-  id: number;
-  invitedByEmail: string;
-  invitedByLogin: string;
-  invitedByName: string;
-  name: string;
-  orgId: number;
-  role: string;
-  status: string;
-  url: string;
-}
-
-export interface User {
-  avatarUrl: string;
-  email: string;
-  lastSeenAt: string;
-  lastSeenAtAge: string;
-  login: string;
-  orgId: number;
-  role: string;
-  userId: number;
-}
-
-export interface UsersState {
-  users: User[];
-  invitees: Invitee[];
-  searchQuery: string;
-  canInvite: boolean;
-  externalUserMngLinkUrl: string;
-  externalUserMngLinkName: string;
-  externalUserMngInfo: string;
-}