Ver Fonte

Trying to reduce the amount of duplication with preferences

Torkel Ödegaard há 7 anos atrás
pai
commit
cf0db51659

+ 0 - 28
public/app/core/actions/user.ts

@@ -1,28 +0,0 @@
-import { ThunkAction } from 'redux-thunk';
-import { getBackendSrv } from '../services/backend_srv';
-import { DashboardAcl, DashboardSearchHit, StoreState } from '../../types';
-
-type ThunkResult<R> = ThunkAction<R, StoreState, undefined, any>;
-
-export type Action = LoadStarredDashboardsAction;
-
-export enum ActionTypes {
-  LoadStarredDashboards = 'LOAD_STARRED_DASHBOARDS',
-}
-
-interface LoadStarredDashboardsAction {
-  type: ActionTypes.LoadStarredDashboards;
-  payload: DashboardSearchHit[];
-}
-
-const starredDashboardsLoaded = (dashboards: DashboardAcl[]) => ({
-  type: ActionTypes.LoadStarredDashboards,
-  payload: dashboards,
-});
-
-export function loadStarredDashboards(): ThunkResult<void> {
-  return async dispatch => {
-    const starredDashboards = await getBackendSrv().search({ starred: true });
-    dispatch(starredDashboardsLoaded(starredDashboards));
-  };
-}

+ 4 - 1
public/app/core/components/Picker/SimplePicker.tsx

@@ -5,13 +5,14 @@ import ResetStyles from './ResetStyles';
 
 interface Props {
   className?: string;
-  defaultValue: any;
+  defaultValue?: any;
   getOptionLabel: (item: any) => string;
   getOptionValue: (item: any) => string;
   onSelected: (item: any) => {} | void;
   options: any[];
   placeholder?: string;
   width: number;
+  value: any;
 }
 
 const SimplePicker: SFC<Props> = ({
@@ -23,6 +24,7 @@ const SimplePicker: SFC<Props> = ({
   options,
   placeholder,
   width,
+  value,
 }) => {
   return (
     <Select
@@ -32,6 +34,7 @@ const SimplePicker: SFC<Props> = ({
         Option: DescriptionOption,
       }}
       defaultValue={defaultValue}
+      value={value}
       getOptionLabel={getOptionLabel}
       getOptionValue={getOptionValue}
       isSearchable={false}

+ 134 - 0
public/app/core/components/SharedPreferences/SharedPreferences.tsx

@@ -0,0 +1,134 @@
+import React, { PureComponent } from 'react';
+
+import { Label } from 'app/core/components/Label/Label';
+import SimplePicker from 'app/core/components/Picker/SimplePicker';
+import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
+
+import { DashboardSearchHit } from 'app/types';
+
+export interface Props {
+  resourceUri: string;
+}
+
+export interface State {
+  homeDashboardId: number;
+  theme: string;
+  timezone: string;
+  dashboards: DashboardSearchHit[];
+}
+
+const themes = [{ value: '', text: 'Default' }, { value: 'dark', text: 'Dark' }, { value: 'light', text: 'Light' }];
+
+const timezones = [
+  { value: '', text: 'Default' },
+  { value: 'browser', text: 'Local browser time' },
+  { value: 'utc', text: 'UTC' },
+];
+
+export class SharedPreferences extends PureComponent<Props, State> {
+  backendSrv: BackendSrv = getBackendSrv();
+
+  constructor(props) {
+    super(props);
+    console.log('props', props);
+
+    this.state = {
+      homeDashboardId: 0,
+      theme: '',
+      timezone: '',
+      dashboards: [],
+    };
+  }
+
+  async componentDidMount() {
+    const prefs = await this.backendSrv.get(`/api/${this.props.resourceUri}/preferences`);
+    const dashboards = await this.backendSrv.search({ starred: true });
+
+    this.setState({
+      homeDashboardId: prefs.homeDashboardId,
+      theme: prefs.theme,
+      timezone: prefs.timezone,
+      dashboards: [{ id: 0, title: 'Default', tags: [], type: '', uid: '', uri: '', url: '' }, ...dashboards],
+    });
+  }
+
+  onSubmitForm = async event => {
+    event.preventDefault();
+
+    const { homeDashboardId, theme, timezone } = this.state;
+
+    await this.backendSrv.put(`/api/${this.props.resourceUri}/preferences`, {
+      homeDashboardId,
+      theme,
+      timezone,
+    });
+  };
+
+  onThemeChanged = (theme: string) => {
+    this.setState({ theme });
+  };
+
+  onTimeZoneChanged = (timezone: string) => {
+    this.setState({ timezone });
+  };
+
+  onHomeDashboardChanged = (dashboardId: number) => {
+    this.setState({ homeDashboardId: dashboardId });
+  };
+
+  render() {
+    const { theme, timezone, homeDashboardId, dashboards } = this.state;
+
+    return (
+      <form className="section gf-form-group" onSubmit={this.onSubmitForm}>
+        <h3 className="page-heading">Preferences</h3>
+        <div className="gf-form">
+          <span className="gf-form-label width-11">UI Theme</span>
+          <SimplePicker
+            value={themes.find(item => item.value === theme)}
+            options={themes}
+            getOptionValue={i => i.value}
+            getOptionLabel={i => i.text}
+            onSelected={theme => this.onThemeChanged(theme.value)}
+            width={20}
+          />
+        </div>
+        <div className="gf-form">
+          <Label
+            width={11}
+            tooltip="Not finding dashboard you want? Star it first, then it should appear in this select box."
+          >
+            Home Dashboard
+          </Label>
+          <SimplePicker
+            value={dashboards.find(dashboard => dashboard.id === homeDashboardId)}
+            getOptionValue={i => i.id}
+            getOptionLabel={i => i.title}
+            onSelected={(dashboard: DashboardSearchHit) => this.onHomeDashboardChanged(dashboard.id)}
+            options={dashboards}
+            placeholder="Chose default dashboard"
+            width={20}
+          />
+        </div>
+        <div className="gf-form">
+          <label className="gf-form-label width-11">Timezone</label>
+          <SimplePicker
+            value={timezones.find(item => item.value === timezone)}
+            getOptionValue={i => i.value}
+            getOptionLabel={i => i.text}
+            onSelected={timezone => this.onTimeZoneChanged(timezone.value)}
+            options={timezones}
+            width={20}
+          />
+        </div>
+        <div className="gf-form-button-row">
+          <button type="submit" className="btn btn-success">
+            Save
+          </button>
+        </div>
+      </form>
+    );
+  }
+}
+
+export default SharedPreferences;

+ 0 - 2
public/app/core/reducers/index.ts

@@ -1,11 +1,9 @@
 import { navIndexReducer as navIndex } from './navModel';
 import { locationReducer as location } from './location';
 import { appNotificationsReducer as appNotifications } from './appNotification';
-import { userReducer as user } from './user';
 
 export default {
   navIndex,
   location,
   appNotifications,
-  user,
 };

+ 0 - 15
public/app/core/reducers/user.ts

@@ -1,15 +0,0 @@
-import { DashboardSearchHit, UserState } from '../../types';
-import { Action, ActionTypes } from '../actions/user';
-
-const initialState: UserState = {
-  starredDashboards: [] as DashboardSearchHit[],
-};
-
-export const userReducer = (state: UserState = initialState, action: Action): UserState => {
-  switch (action.type) {
-    case ActionTypes.LoadStarredDashboards:
-      return { ...state, starredDashboards: action.payload };
-  }
-
-  return state;
-};

+ 1 - 4
public/app/features/org/OrgDetailsPage.test.tsx

@@ -1,16 +1,13 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import { OrgDetailsPage, Props } from './OrgDetailsPage';
-import { NavModel, Organization, OrganizationPreferences } from '../../types';
+import { NavModel, Organization } from '../../types';
 
 const setup = (propOverrides?: object) => {
   const props: Props = {
-    preferences: {} as OrganizationPreferences,
     organization: {} as Organization,
     navModel: {} as NavModel,
     loadOrganization: jest.fn(),
-    loadOrganizationPreferences: jest.fn(),
-    loadStarredDashboards: jest.fn(),
     setOrganizationName: jest.fn(),
     updateOrganization: jest.fn(),
   };

+ 8 - 22
public/app/features/org/OrgDetailsPage.tsx

@@ -4,33 +4,22 @@ import { connect } from 'react-redux';
 import PageHeader from '../../core/components/PageHeader/PageHeader';
 import PageLoader from '../../core/components/PageLoader/PageLoader';
 import OrgProfile from './OrgProfile';
-import OrgPreferences from './OrgPreferences';
-import {
-  loadOrganization,
-  loadOrganizationPreferences,
-  setOrganizationName,
-  updateOrganization,
-} from './state/actions';
-import { loadStarredDashboards } from '../../core/actions/user';
-import { NavModel, Organization, OrganizationPreferences, StoreState } from 'app/types';
+import SharedPreferences from 'app/core/components/SharedPreferences/SharedPreferences';
+import { loadOrganization, setOrganizationName, updateOrganization } from './state/actions';
+import { NavModel, Organization, StoreState } from 'app/types';
 import { getNavModel } from '../../core/selectors/navModel';
 
 export interface Props {
   navModel: NavModel;
   organization: Organization;
-  preferences: OrganizationPreferences;
   loadOrganization: typeof loadOrganization;
-  loadOrganizationPreferences: typeof loadOrganizationPreferences;
-  loadStarredDashboards: typeof loadStarredDashboards;
   setOrganizationName: typeof setOrganizationName;
   updateOrganization: typeof updateOrganization;
 }
 
 export class OrgDetailsPage extends PureComponent<Props> {
   async componentDidMount() {
-    await this.props.loadStarredDashboards();
     await this.props.loadOrganization();
-    await this.props.loadOrganizationPreferences();
   }
 
   onOrgNameChange = name => {
@@ -42,22 +31,22 @@ export class OrgDetailsPage extends PureComponent<Props> {
   };
 
   render() {
-    const { navModel, organization, preferences } = this.props;
+    const { navModel, organization } = this.props;
+    const isLoading = Object.keys(organization).length === 0;
 
     return (
       <div>
         <PageHeader model={navModel} />
         <div className="page-container page-body">
-          {Object.keys(organization).length === 0 || Object.keys(preferences).length === 0 ? (
-            <PageLoader pageName="Organization" />
-          ) : (
+          {isLoading && <PageLoader pageName="Organization" />}
+          {!isLoading && (
             <div>
               <OrgProfile
                 onOrgNameChange={name => this.onOrgNameChange(name)}
                 onSubmit={this.onUpdateOrganization}
                 orgName={organization.name}
               />
-              <OrgPreferences />
+              <SharedPreferences resourceUri="org" />
             </div>
           )}
         </div>
@@ -70,14 +59,11 @@ function mapStateToProps(state: StoreState) {
   return {
     navModel: getNavModel(state.navIndex, 'org-settings'),
     organization: state.organization.organization,
-    preferences: state.organization.preferences,
   };
 }
 
 const mapDispatchToProps = {
   loadOrganization,
-  loadOrganizationPreferences,
-  loadStarredDashboards,
   setOrganizationName,
   updateOrganization,
 };

+ 0 - 28
public/app/features/org/OrgPreferences.test.tsx

@@ -1,28 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import { OrgPreferences, Props } from './OrgPreferences';
-
-const setup = () => {
-  const props: Props = {
-    preferences: {
-      homeDashboardId: 1,
-      timezone: 'UTC',
-      theme: 'Default',
-    },
-    starredDashboards: [{ id: 1, title: 'Standard dashboard', url: '', uri: '', uid: '', type: '', tags: [] }],
-    setOrganizationTimezone: jest.fn(),
-    setOrganizationTheme: jest.fn(),
-    setOrganizationHomeDashboard: jest.fn(),
-    updateOrganizationPreferences: jest.fn(),
-  };
-
-  return shallow(<OrgPreferences {...props} />);
-};
-
-describe('Render', () => {
-  it('should render component', () => {
-    const wrapper = setup();
-
-    expect(wrapper).toMatchSnapshot();
-  });
-});

+ 0 - 116
public/app/features/org/OrgPreferences.tsx

@@ -1,116 +0,0 @@
-import React, { PureComponent } from 'react';
-import { connect } from 'react-redux';
-import { Label } from '../../core/components/Label/Label';
-import SimplePicker from '../../core/components/Picker/SimplePicker';
-import { DashboardSearchHit, OrganizationPreferences } from 'app/types';
-import {
-  setOrganizationHomeDashboard,
-  setOrganizationTheme,
-  setOrganizationTimezone,
-  updateOrganizationPreferences,
-} from './state/actions';
-
-export interface Props {
-  preferences: OrganizationPreferences;
-  starredDashboards: DashboardSearchHit[];
-  setOrganizationHomeDashboard: typeof setOrganizationHomeDashboard;
-  setOrganizationTheme: typeof setOrganizationTheme;
-  setOrganizationTimezone: typeof setOrganizationTimezone;
-  updateOrganizationPreferences: typeof updateOrganizationPreferences;
-}
-
-const themes = [{ value: '', text: 'Default' }, { value: 'dark', text: 'Dark' }, { value: 'light', text: 'Light' }];
-
-const timezones = [
-  { value: '', text: 'Default' },
-  { value: 'browser', text: 'Local browser time' },
-  { value: 'utc', text: 'UTC' },
-];
-
-export class OrgPreferences extends PureComponent<Props> {
-  onSubmitForm = event => {
-    event.preventDefault();
-    this.props.updateOrganizationPreferences();
-  };
-
-  render() {
-    const {
-      preferences,
-      starredDashboards,
-      setOrganizationHomeDashboard,
-      setOrganizationTimezone,
-      setOrganizationTheme,
-    } = this.props;
-
-    const dashboards: DashboardSearchHit[] = [
-      { id: 0, title: 'Default', tags: [], type: '', uid: '', uri: '', url: '' },
-      ...starredDashboards,
-    ];
-
-    return (
-      <form className="section gf-form-group" onSubmit={this.onSubmitForm}>
-        <h3 className="page-heading">Preferences</h3>
-        <div className="gf-form">
-          <span className="gf-form-label width-11">UI Theme</span>
-          <SimplePicker
-            defaultValue={themes.find(theme => theme.value === preferences.theme)}
-            options={themes}
-            getOptionValue={i => i.value}
-            getOptionLabel={i => i.text}
-            onSelected={theme => setOrganizationTheme(theme.value)}
-            width={20}
-          />
-        </div>
-        <div className="gf-form">
-          <Label
-            width={11}
-            tooltip="Not finding dashboard you want? Star it first, then it should appear in this select box."
-          >
-            Home Dashboard
-          </Label>
-          <SimplePicker
-            defaultValue={dashboards.find(dashboard => dashboard.id === preferences.homeDashboardId)}
-            getOptionValue={i => i.id}
-            getOptionLabel={i => i.title}
-            onSelected={(dashboard: DashboardSearchHit) => setOrganizationHomeDashboard(dashboard.id)}
-            options={dashboards}
-            placeholder="Chose default dashboard"
-            width={20}
-          />
-        </div>
-        <div className="gf-form">
-          <label className="gf-form-label width-11">Timezone</label>
-          <SimplePicker
-            defaultValue={timezones.find(timezone => timezone.value === preferences.timezone)}
-            getOptionValue={i => i.value}
-            getOptionLabel={i => i.text}
-            onSelected={timezone => setOrganizationTimezone(timezone.value)}
-            options={timezones}
-            width={20}
-          />
-        </div>
-        <div className="gf-form-button-row">
-          <button type="submit" className="btn btn-success">
-            Save
-          </button>
-        </div>
-      </form>
-    );
-  }
-}
-
-function mapStateToProps(state) {
-  return {
-    preferences: state.organization.preferences,
-    starredDashboards: state.user.starredDashboards,
-  };
-}
-
-const mapDispatchToProps = {
-  setOrganizationHomeDashboard,
-  setOrganizationTimezone,
-  setOrganizationTheme,
-  updateOrganizationPreferences,
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(OrgPreferences);

+ 3 - 70
public/app/features/org/state/actions.ts

@@ -1,16 +1,12 @@
 import { ThunkAction } from 'redux-thunk';
-import { Organization, OrganizationPreferences, StoreState } from 'app/types';
-import { getBackendSrv } from '../../../core/services/backend_srv';
+import { Organization, StoreState } from 'app/types';
+import { getBackendSrv } from 'app/core/services/backend_srv';
 
 type ThunkResult<R> = ThunkAction<R, StoreState, undefined, any>;
 
 export enum ActionTypes {
   LoadOrganization = 'LOAD_ORGANISATION',
-  LoadPreferences = 'LOAD_PREFERENCES',
   SetOrganizationName = 'SET_ORGANIZATION_NAME',
-  SetOrganizationTheme = 'SET_ORGANIZATION_THEME',
-  SetOrganizationHomeDashboard = 'SET_ORGANIZATION_HOME_DASHBOARD',
-  SetOrganizationTimezone = 'SET_ORGANIZATION_TIMEZONE',
 }
 
 interface LoadOrganizationAction {
@@ -18,68 +14,22 @@ interface LoadOrganizationAction {
   payload: Organization;
 }
 
-interface LoadPreferencesAction {
-  type: ActionTypes.LoadPreferences;
-  payload: OrganizationPreferences;
-}
-
 interface SetOrganizationNameAction {
   type: ActionTypes.SetOrganizationName;
   payload: string;
 }
 
-interface SetOrganizationThemeAction {
-  type: ActionTypes.SetOrganizationTheme;
-  payload: string;
-}
-
-interface SetOrganizationHomeDashboardAction {
-  type: ActionTypes.SetOrganizationHomeDashboard;
-  payload: number;
-}
-
-interface SetOrganizationTimezoneAction {
-  type: ActionTypes.SetOrganizationTimezone;
-  payload: string;
-}
-
 const organisationLoaded = (organisation: Organization) => ({
   type: ActionTypes.LoadOrganization,
   payload: organisation,
 });
 
-const preferencesLoaded = (preferences: OrganizationPreferences) => ({
-  type: ActionTypes.LoadPreferences,
-  payload: preferences,
-});
-
 export const setOrganizationName = (orgName: string) => ({
   type: ActionTypes.SetOrganizationName,
   payload: orgName,
 });
 
-export const setOrganizationTheme = (theme: string) => ({
-  type: ActionTypes.SetOrganizationTheme,
-  payload: theme,
-});
-
-export const setOrganizationHomeDashboard = (id: number) => ({
-  type: ActionTypes.SetOrganizationHomeDashboard,
-  payload: id,
-});
-
-export const setOrganizationTimezone = (timezone: string) => ({
-  type: ActionTypes.SetOrganizationTimezone,
-  payload: timezone,
-});
-
-export type Action =
-  | LoadOrganizationAction
-  | LoadPreferencesAction
-  | SetOrganizationNameAction
-  | SetOrganizationThemeAction
-  | SetOrganizationHomeDashboardAction
-  | SetOrganizationTimezoneAction;
+export type Action = LoadOrganizationAction | SetOrganizationNameAction;
 
 export function loadOrganization(): ThunkResult<void> {
   return async dispatch => {
@@ -90,13 +40,6 @@ export function loadOrganization(): ThunkResult<void> {
   };
 }
 
-export function loadOrganizationPreferences(): ThunkResult<void> {
-  return async dispatch => {
-    const preferencesResponse = await getBackendSrv().get('/api/org/preferences');
-    dispatch(preferencesLoaded(preferencesResponse));
-  };
-}
-
 export function updateOrganization() {
   return async (dispatch, getStore) => {
     const organization = getStore().organization.organization;
@@ -106,13 +49,3 @@ export function updateOrganization() {
     dispatch(loadOrganization());
   };
 }
-
-export function updateOrganizationPreferences() {
-  return async (dispatch, getStore) => {
-    const preferences = getStore().organization.preferences;
-
-    await getBackendSrv().put('/api/org/preferences', preferences);
-
-    window.location.reload();
-  };
-}

+ 1 - 14
public/app/features/org/state/reducers.ts

@@ -1,9 +1,8 @@
-import { Organization, OrganizationPreferences, OrganizationState } from 'app/types';
+import { Organization, OrganizationState } from 'app/types';
 import { Action, ActionTypes } from './actions';
 
 const initialState: OrganizationState = {
   organization: {} as Organization,
-  preferences: {} as OrganizationPreferences,
 };
 
 const organizationReducer = (state = initialState, action: Action): OrganizationState => {
@@ -11,20 +10,8 @@ const organizationReducer = (state = initialState, action: Action): Organization
     case ActionTypes.LoadOrganization:
       return { ...state, organization: action.payload };
 
-    case ActionTypes.LoadPreferences:
-      return { ...state, preferences: action.payload };
-
     case ActionTypes.SetOrganizationName:
       return { ...state, organization: { ...state.organization, name: action.payload } };
-
-    case ActionTypes.SetOrganizationTheme:
-      return { ...state, preferences: { ...state.preferences, theme: action.payload } };
-
-    case ActionTypes.SetOrganizationHomeDashboard:
-      return { ...state, preferences: { ...state.preferences, homeDashboardId: action.payload } };
-
-    case ActionTypes.SetOrganizationTimezone:
-      return { ...state, preferences: { ...state.preferences, timezone: action.payload } };
   }
 
   return state;

+ 3 - 91
public/app/features/profile/PrefControlCtrl.ts

@@ -1,92 +1,4 @@
-import config from 'app/core/config';
-import coreModule from 'app/core/core_module';
+import { react2AngularDirective } from 'app/core/utils/react2angular';
+import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences';
 
-export class PrefsControlCtrl {
-  prefs: any;
-  oldTheme: any;
-  prefsForm: any;
-  mode: string;
-
-  timezones: any = [
-    { value: '', text: 'Default' },
-    { value: 'browser', text: 'Local browser time' },
-    { value: 'utc', text: 'UTC' },
-  ];
-  themes: any = [{ value: '', text: 'Default' }, { value: 'dark', text: 'Dark' }, { value: 'light', text: 'Light' }];
-
-  /** @ngInject */
-  constructor(private backendSrv, private $location) {}
-
-  $onInit() {
-    return this.backendSrv.get(`/api/${this.mode}/preferences`).then(prefs => {
-      this.prefs = prefs;
-      this.oldTheme = prefs.theme;
-    });
-  }
-
-  updatePrefs() {
-    if (!this.prefsForm.$valid) {
-      return;
-    }
-
-    const cmd = {
-      theme: this.prefs.theme,
-      timezone: this.prefs.timezone,
-      homeDashboardId: this.prefs.homeDashboardId,
-    };
-
-    this.backendSrv.put(`/api/${this.mode}/preferences`, cmd).then(() => {
-      window.location.href = config.appSubUrl + this.$location.path();
-    });
-  }
-}
-
-const template = `
-<form name="ctrl.prefsForm" class="section gf-form-group">
-  <h3 class="page-heading">Preferences</h3>
-
-  <div class="gf-form">
-    <span class="gf-form-label width-11">UI Theme</span>
-    <div class="gf-form-select-wrapper max-width-20">
-      <select class="gf-form-input" ng-model="ctrl.prefs.theme" ng-options="f.value as f.text for f in ctrl.themes"></select>
-    </div>
-  </div>
-
-  <div class="gf-form">
-    <span class="gf-form-label width-11">
-      Home Dashboard
-      <info-popover mode="right-normal">
-        Not finding dashboard you want? Star it first, then it should appear in this select box.
-      </info-popover>
-    </span>
-    <dashboard-selector class="gf-form-select-wrapper max-width-20" model="ctrl.prefs.homeDashboardId">
-    </dashboard-selector>
-  </div>
-
-  <div class="gf-form">
-    <label class="gf-form-label width-11">Timezone</label>
-    <div class="gf-form-select-wrapper max-width-20">
-      <select class="gf-form-input" ng-model="ctrl.prefs.timezone" ng-options="f.value as f.text for f in ctrl.timezones"></select>
-    </div>
-  </div>
-
-  <div class="gf-form-button-row">
-    <button type="submit" class="btn btn-success" ng-click="ctrl.updatePrefs()">Save</button>
-  </div>
-</form>
-`;
-
-export function prefsControlDirective() {
-  return {
-    restrict: 'E',
-    controller: PrefsControlCtrl,
-    bindToController: true,
-    controllerAs: 'ctrl',
-    template: template,
-    scope: {
-      mode: '@',
-    },
-  };
-}
-
-coreModule.directive('prefsControl', prefsControlDirective);
+react2AngularDirective('prefsControl', SharedPreferences, ['resourceUri']);

+ 1 - 1
public/app/features/profile/partials/profile.html

@@ -24,7 +24,7 @@
     </div>
   </form>
 
-  <prefs-control mode="user"></prefs-control>
+  <prefs-control resource-uri="'user'"></prefs-control>
 
   <h3 class="page-heading" ng-show="ctrl.showTeamsList">Teams</h3>
   <div class="gf-form-group" ng-show="ctrl.showTeamsList">

+ 1 - 4
public/app/features/teams/TeamPages.test.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import { TeamPages, Props } from './TeamPages';
-import { NavModel, Team, OrganizationPreferences } from '../../types';
+import { NavModel, Team } from '../../types';
 import { getMockTeam } from './__mocks__/teamMocks';
 
 jest.mock('app/core/config', () => ({
@@ -15,9 +15,6 @@ const setup = (propOverrides?: object) => {
     loadTeam: jest.fn(),
     pageName: 'members',
     team: {} as Team,
-    loadStarredDashboards: jest.fn(),
-    loadTeamPreferences: jest.fn(),
-    preferences: {} as OrganizationPreferences,
   };
 
   Object.assign(props, propOverrides);

+ 3 - 19
public/app/features/teams/TeamPages.tsx

@@ -7,14 +7,12 @@ import PageHeader from 'app/core/components/PageHeader/PageHeader';
 import TeamMembers from './TeamMembers';
 import TeamSettings from './TeamSettings';
 import TeamGroupSync from './TeamGroupSync';
-import TeamPreferences from './TeamPreferences';
-import { NavModel, Team, OrganizationPreferences } from 'app/types';
-import { loadTeam, loadTeamPreferences } from './state/actions';
+import { NavModel, Team } from 'app/types';
+import { loadTeam } from './state/actions';
 import { getTeam } from './state/selectors';
 import { getTeamLoadingNav } from './state/navModel';
 import { getNavModel } from 'app/core/selectors/navModel';
 import { getRouteParamsId, getRouteParamsPage } from '../../core/selectors/location';
-import { loadStarredDashboards } from '../../core/actions/user';
 
 export interface Props {
   team: Team;
@@ -22,9 +20,6 @@ export interface Props {
   teamId: number;
   pageName: string;
   navModel: NavModel;
-  preferences: OrganizationPreferences;
-  loadStarredDashboards: typeof loadStarredDashboards;
-  loadTeamPreferences: typeof loadTeamPreferences;
 }
 
 interface State {
@@ -47,9 +42,7 @@ export class TeamPages extends PureComponent<Props, State> {
   }
 
   async componentDidMount() {
-    await this.props.loadStarredDashboards();
     await this.fetchTeam();
-    await this.props.loadTeamPreferences();
   }
 
   async fetchTeam() {
@@ -73,13 +66,7 @@ export class TeamPages extends PureComponent<Props, State> {
         return <TeamMembers syncEnabled={isSyncEnabled} />;
 
       case PageTypes.Settings:
-        return (
-          <div>
-            <TeamSettings />
-            <TeamPreferences />
-          </div>
-        );
-
+        return <TeamSettings />;
       case PageTypes.GroupSync:
         return isSyncEnabled && <TeamGroupSync />;
     }
@@ -109,14 +96,11 @@ function mapStateToProps(state) {
     teamId: teamId,
     pageName: pageName,
     team: getTeam(state.team, teamId),
-    preferences: state.preferences,
   };
 }
 
 const mapDispatchToProps = {
   loadTeam,
-  loadStarredDashboards,
-  loadTeamPreferences,
 };
 
 export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(TeamPages));

+ 0 - 28
public/app/features/teams/TeamPreferences.test.tsx

@@ -1,28 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import { TeamPreferences, Props } from './TeamPreferences';
-
-const setup = () => {
-  const props: Props = {
-    preferences: {
-      homeDashboardId: 1,
-      timezone: 'UTC',
-      theme: 'Default',
-    },
-    starredDashboards: [{ id: 1, title: 'Standard dashboard', url: '', uri: '', uid: '', type: '', tags: [] }],
-    setTeamTimezone: jest.fn(),
-    setTeamTheme: jest.fn(),
-    setTeamHomeDashboard: jest.fn(),
-    updateTeamPreferences: jest.fn(),
-  };
-
-  return shallow(<TeamPreferences {...props} />);
-};
-
-describe('Render', () => {
-  it('should render component', () => {
-    const wrapper = setup();
-
-    expect(wrapper).toMatchSnapshot();
-  });
-});

+ 0 - 105
public/app/features/teams/TeamPreferences.tsx

@@ -1,105 +0,0 @@
-import React, { PureComponent } from 'react';
-import { connect } from 'react-redux';
-import { Label } from '../../core/components/Label/Label';
-import SimplePicker from '../../core/components/Picker/SimplePicker';
-import { DashboardSearchHit, OrganizationPreferences } from 'app/types';
-import { setTeamHomeDashboard, setTeamTheme, setTeamTimezone, updateTeamPreferences } from './state/actions';
-
-export interface Props {
-  preferences: OrganizationPreferences;
-  starredDashboards: DashboardSearchHit[];
-  setTeamHomeDashboard: typeof setTeamHomeDashboard;
-  setTeamTheme: typeof setTeamTheme;
-  setTeamTimezone: typeof setTeamTimezone;
-  updateTeamPreferences: typeof updateTeamPreferences;
-}
-
-const themes = [{ value: '', text: 'Default' }, { value: 'dark', text: 'Dark' }, { value: 'light', text: 'Light' }];
-
-const timezones = [
-  { value: '', text: 'Default' },
-  { value: 'browser', text: 'Local browser time' },
-  { value: 'utc', text: 'UTC' },
-];
-
-export class TeamPreferences extends PureComponent<Props> {
-  onSubmitForm = event => {
-    event.preventDefault();
-    this.props.updateTeamPreferences();
-  };
-
-  render() {
-    const { preferences, starredDashboards, setTeamHomeDashboard, setTeamTimezone, setTeamTheme } = this.props;
-
-    const dashboards: DashboardSearchHit[] = [
-      { id: 0, title: 'Default', tags: [], type: '', uid: '', uri: '', url: '' },
-      ...starredDashboards,
-    ];
-
-    return (
-      <form className="section gf-form-group" onSubmit={this.onSubmitForm}>
-        <h3 className="page-heading">Preferences</h3>
-        <div className="gf-form">
-          <span className="gf-form-label width-11">UI Theme</span>
-          <SimplePicker
-            defaultValue={themes.find(theme => theme.value === preferences.theme)}
-            options={themes}
-            getOptionValue={i => i.value}
-            getOptionLabel={i => i.text}
-            onSelected={theme => setTeamTheme(theme.value)}
-            width={20}
-          />
-        </div>
-        <div className="gf-form">
-          <Label
-            width={11}
-            tooltip="Not finding dashboard you want? Star it first, then it should appear in this select box."
-          >
-            Home Dashboard
-          </Label>
-          <SimplePicker
-            defaultValue={dashboards.find(dashboard => dashboard.id === preferences.homeDashboardId)}
-            getOptionValue={i => i.id}
-            getOptionLabel={i => i.title}
-            onSelected={(dashboard: DashboardSearchHit) => setTeamHomeDashboard(dashboard.id)}
-            options={dashboards}
-            placeholder="Chose default dashboard"
-            width={20}
-          />
-        </div>
-        <div className="gf-form">
-          <label className="gf-form-label width-11">Timezone</label>
-          <SimplePicker
-            defaultValue={timezones.find(timezone => timezone.value === preferences.timezone)}
-            getOptionValue={i => i.value}
-            getOptionLabel={i => i.text}
-            onSelected={timezone => setTeamTimezone(timezone.value)}
-            options={timezones}
-            width={20}
-          />
-        </div>
-        <div className="gf-form-button-row">
-          <button type="submit" className="btn btn-success">
-            Save
-          </button>
-        </div>
-      </form>
-    );
-  }
-}
-
-function mapStateToProps(state) {
-  return {
-    preferences: state.team.preferences,
-    starredDashboards: state.user.starredDashboards,
-  };
-}
-
-const mapDispatchToProps = {
-  setTeamHomeDashboard,
-  setTeamTimezone,
-  setTeamTheme,
-  updateTeamPreferences,
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(TeamPreferences);

+ 6 - 2
public/app/features/teams/TeamSettings.tsx

@@ -1,10 +1,12 @@
 import React from 'react';
 import { connect } from 'react-redux';
+
 import { Label } from 'app/core/components/Label/Label';
-import { Team } from '../../types';
+import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences';
 import { updateTeam } from './state/actions';
-import { getRouteParamsId } from '../../core/selectors/location';
+import { getRouteParamsId } from 'app/core/selectors/location';
 import { getTeam } from './state/selectors';
+import { Team } from 'app/types';
 
 export interface Props {
   team: Team;
@@ -41,6 +43,7 @@ export class TeamSettings extends React.Component<Props, State> {
   };
 
   render() {
+    const { team } = this.props;
     const { name, email } = this.state;
 
     return (
@@ -76,6 +79,7 @@ export class TeamSettings extends React.Component<Props, State> {
             </button>
           </div>
         </form>
+        <SharedPreferences resourceUri={`teams/${team.id}`} />
       </div>
     );
   }

+ 1 - 9
public/app/features/teams/__mocks__/teamMocks.ts

@@ -1,4 +1,4 @@
-import { Team, TeamGroup, TeamMember, OrganizationPreferences } from 'app/types';
+import { Team, TeamGroup, TeamMember } from 'app/types';
 
 export const getMultipleMockTeams = (numberOfTeams: number): Team[] => {
   const teams: Team[] = [];
@@ -65,11 +65,3 @@ export const getMockTeamGroups = (amount: number): TeamGroup[] => {
 
   return groups;
 };
-
-export const getMockTeamPreferences = (): OrganizationPreferences => {
-  return {
-    theme: 'dark',
-    timezone: 'browser',
-    homeDashboardId: 1,
-  };
-};

+ 2 - 69
public/app/features/teams/state/actions.ts

@@ -1,20 +1,16 @@
 import { ThunkAction } from 'redux-thunk';
 import { getBackendSrv } from 'app/core/services/backend_srv';
-import { StoreState, Team, TeamGroup, TeamMember, OrganizationPreferences } from 'app/types';
+import { StoreState, Team, TeamGroup, TeamMember } from 'app/types';
 import { updateNavIndex, UpdateNavIndexAction } from 'app/core/actions';
 import { buildNavModel } from './navModel';
 
 export enum ActionTypes {
   LoadTeams = 'LOAD_TEAMS',
   LoadTeam = 'LOAD_TEAM',
-  LoadTeamPreferences = 'LOAD_TEAM_PREFERENCES',
   SetSearchQuery = 'SET_TEAM_SEARCH_QUERY',
   SetSearchMemberQuery = 'SET_TEAM_MEMBER_SEARCH_QUERY',
   LoadTeamMembers = 'TEAM_MEMBERS_LOADED',
   LoadTeamGroups = 'TEAM_GROUPS_LOADED',
-  SetTeamTheme = 'SET_TEAM_THEME',
-  SetTeamHomeDashboard = 'SET_TEAM_HOME_DASHBOARD',
-  SetTeamTimezone = 'SET_TEAM_TIMEZONE',
 }
 
 export interface LoadTeamsAction {
@@ -27,11 +23,6 @@ export interface LoadTeamAction {
   payload: Team;
 }
 
-export interface LoadTeamPreferencesAction {
-  type: ActionTypes.LoadTeamPreferences;
-  payload: OrganizationPreferences;
-}
-
 export interface LoadTeamMembersAction {
   type: ActionTypes.LoadTeamMembers;
   payload: TeamMember[];
@@ -52,32 +43,13 @@ export interface SetSearchMemberQueryAction {
   payload: string;
 }
 
-export interface SetTeamThemeAction {
-  type: ActionTypes.SetTeamTheme;
-  payload: string;
-}
-
-export interface SetTeamHomeDashboardAction {
-  type: ActionTypes.SetTeamHomeDashboard;
-  payload: number;
-}
-
-export interface SetTeamTimezoneAction {
-  type: ActionTypes.SetTeamTimezone;
-  payload: string;
-}
-
 export type Action =
   | LoadTeamsAction
   | SetSearchQueryAction
   | LoadTeamAction
-  | LoadTeamPreferencesAction
   | LoadTeamMembersAction
   | SetSearchMemberQueryAction
-  | LoadTeamGroupsAction
-  | SetTeamThemeAction
-  | SetTeamHomeDashboardAction
-  | SetTeamTimezoneAction;
+  | LoadTeamGroupsAction;
 
 type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action | UpdateNavIndexAction>;
 
@@ -101,11 +73,6 @@ const teamGroupsLoaded = (teamGroups: TeamGroup[]): LoadTeamGroupsAction => ({
   payload: teamGroups,
 });
 
-const teamPreferencesLoaded = (preferences: OrganizationPreferences): LoadTeamPreferencesAction => ({
-  type: ActionTypes.LoadTeamPreferences,
-  payload: preferences,
-});
-
 export const setSearchMemberQuery = (searchQuery: string): SetSearchMemberQueryAction => ({
   type: ActionTypes.SetSearchMemberQuery,
   payload: searchQuery,
@@ -116,21 +83,6 @@ export const setSearchQuery = (searchQuery: string): SetSearchQueryAction => ({
   payload: searchQuery,
 });
 
-export const setTeamTheme = (theme: string) => ({
-  type: ActionTypes.SetTeamTheme,
-  payload: theme,
-});
-
-export const setTeamHomeDashboard = (id: number) => ({
-  type: ActionTypes.SetTeamHomeDashboard,
-  payload: id,
-});
-
-export const setTeamTimezone = (timezone: string) => ({
-  type: ActionTypes.SetTeamTimezone,
-  payload: timezone,
-});
-
 export function loadTeams(): ThunkResult<void> {
   return async dispatch => {
     const response = await getBackendSrv().get('/api/teams/search', { perpage: 1000, page: 1 });
@@ -208,22 +160,3 @@ export function deleteTeam(id: number): ThunkResult<void> {
     dispatch(loadTeams());
   };
 }
-
-export function loadTeamPreferences(): ThunkResult<void> {
-  return async (dispatch, getStore) => {
-    const team = getStore().team.team;
-    const response = await getBackendSrv().get(`/api/teams/${team.id}/preferences`);
-    dispatch(teamPreferencesLoaded(response));
-  };
-}
-
-export function updateTeamPreferences() {
-  return async (dispatch, getStore) => {
-    const team = getStore().team.team;
-    const preferences = getStore().team.preferences;
-
-    await getBackendSrv().put(`/api/teams/${team.id}/preferences`, preferences);
-
-    dispatch(loadTeamPreferences());
-  };
-}

+ 1 - 14
public/app/features/teams/state/reducers.test.ts

@@ -1,6 +1,6 @@
 import { Action, ActionTypes } from './actions';
 import { initialTeamsState, initialTeamState, teamReducer, teamsReducer } from './reducers';
-import { getMockTeam, getMockTeamMember, getMockTeamPreferences } from '../__mocks__/teamMocks';
+import { getMockTeam, getMockTeamMember } from '../__mocks__/teamMocks';
 
 describe('teams reducer', () => {
   it('should set teams', () => {
@@ -69,17 +69,4 @@ describe('team reducer', () => {
 
     expect(result.searchMemberQuery).toEqual('member');
   });
-
-  it('should set team preferences', () => {
-    const mockTeamPrefs = getMockTeamPreferences();
-
-    const action: Action = {
-      type: ActionTypes.LoadTeamPreferences,
-      payload: mockTeamPrefs,
-    };
-
-    const result = teamReducer(initialTeamState, action);
-
-    expect(result.preferences).toEqual(mockTeamPrefs);
-  });
 });

+ 1 - 14
public/app/features/teams/state/reducers.ts

@@ -1,4 +1,4 @@
-import { Team, TeamGroup, TeamMember, TeamsState, TeamState, OrganizationPreferences } from 'app/types';
+import { Team, TeamGroup, TeamMember, TeamsState, TeamState } from 'app/types';
 import { Action, ActionTypes } from './actions';
 
 export const initialTeamsState: TeamsState = { teams: [], searchQuery: '', hasFetched: false };
@@ -7,7 +7,6 @@ export const initialTeamState: TeamState = {
   members: [] as TeamMember[],
   groups: [] as TeamGroup[],
   searchMemberQuery: '',
-  preferences: {} as OrganizationPreferences,
 };
 
 export const teamsReducer = (state = initialTeamsState, action: Action): TeamsState => {
@@ -34,18 +33,6 @@ export const teamReducer = (state = initialTeamState, action: Action): TeamState
 
     case ActionTypes.LoadTeamGroups:
       return { ...state, groups: action.payload };
-
-    case ActionTypes.LoadTeamPreferences:
-      return { ...state, preferences: action.payload };
-
-    case ActionTypes.SetTeamTheme:
-      return { ...state, preferences: { ...state.preferences, theme: action.payload } };
-
-    case ActionTypes.SetTeamHomeDashboard:
-      return { ...state, preferences: { ...state.preferences, homeDashboardId: action.payload } };
-
-    case ActionTypes.SetTeamTimezone:
-      return { ...state, preferences: { ...state.preferences, timezone: action.payload } };
   }
 
   return state;

+ 1 - 7
public/app/features/teams/state/selectors.test.ts

@@ -1,6 +1,6 @@
 import { getTeam, getTeamMembers, getTeams } from './selectors';
 import { getMockTeam, getMockTeamMembers, getMultipleMockTeams } from '../__mocks__/teamMocks';
-import { Team, TeamGroup, TeamsState, TeamState, OrganizationPreferences } from '../../../types';
+import { Team, TeamGroup, TeamsState, TeamState } from '../../../types';
 
 describe('Teams selectors', () => {
   describe('Get teams', () => {
@@ -10,7 +10,6 @@ describe('Teams selectors', () => {
       const mockState: TeamsState = { teams: mockTeams, searchQuery: '', hasFetched: false };
 
       const teams = getTeams(mockState);
-
       expect(teams).toEqual(mockTeams);
     });
 
@@ -18,7 +17,6 @@ describe('Teams selectors', () => {
       const mockState: TeamsState = { teams: mockTeams, searchQuery: '5', hasFetched: false };
 
       const teams = getTeams(mockState);
-
       expect(teams.length).toEqual(1);
     });
   });
@@ -34,11 +32,9 @@ describe('Team selectors', () => {
         searchMemberQuery: '',
         members: [],
         groups: [],
-        preferences: {} as OrganizationPreferences,
       };
 
       const team = getTeam(mockState, '1');
-
       expect(team).toEqual(mockTeam);
     });
   });
@@ -52,11 +48,9 @@ describe('Team selectors', () => {
         searchMemberQuery: '',
         members: mockTeamMembers,
         groups: [] as TeamGroup[],
-        preferences: {} as OrganizationPreferences,
       };
 
       const members = getTeamMembers(mockState);
-
       expect(members).toEqual(mockTeamMembers);
     });
   });

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

@@ -22,7 +22,7 @@ import {
 } from './series';
 import { PanelProps, PanelOptionsProps } from './panel';
 import { PluginDashboard, PluginMeta, Plugin, PluginsState } from './plugins';
-import { Organization, OrganizationPreferences, OrganizationState } from './organization';
+import { Organization, OrganizationState } from './organization';
 import {
   AppNotification,
   AppNotificationSeverity,
@@ -81,7 +81,6 @@ export {
   PluginDashboard,
   Organization,
   OrganizationState,
-  OrganizationPreferences,
   AppNotification,
   AppNotificationsState,
   AppNotificationSeverity,

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

@@ -3,13 +3,6 @@ export interface Organization {
   id: number;
 }
 
-export interface OrganizationPreferences {
-  homeDashboardId: number;
-  theme: string;
-  timezone: string;
-}
-
 export interface OrganizationState {
   organization: Organization;
-  preferences: OrganizationPreferences;
 }

+ 0 - 3
public/app/types/teams.ts

@@ -1,5 +1,3 @@
-import { OrganizationPreferences } from './organization';
-
 export interface Team {
   id: number;
   name: string;
@@ -33,5 +31,4 @@ export interface TeamState {
   members: TeamMember[];
   groups: TeamGroup[];
   searchMemberQuery: string;
-  preferences: OrganizationPreferences;
 }