Skip to content
Snippets Groups Projects
Commit 1da919d9 authored by Allen Gilliland's avatar Allen Gilliland
Browse files

admin settings page angular -> redux

parent 4e23d2a6
Branches
Tags
No related merge requests found
import React, { Component, PropTypes } from "react";
import { connect } from "react-redux";
import MetabaseAnalytics from "metabase/lib/analytics";
import MetabaseSettings from "metabase/lib/settings";
import SettingsHeader from "./SettingsHeader.jsx";
import SettingsSetting from "./SettingsSetting.jsx";
import SettingsEmailForm from "./SettingsEmailForm.jsx";
import SettingsSlackForm from "./SettingsSlackForm.jsx";
import SettingsSetupList from "./SettingsSetupList.jsx";
import SettingsUpdatesForm from "./SettingsUpdatesForm.jsx";
import SettingsHeader from "../components/SettingsHeader.jsx";
import SettingsSetting from "../components/SettingsSetting.jsx";
import SettingsEmailForm from "../components/SettingsEmailForm.jsx";
import SettingsSlackForm from "../components/SettingsSlackForm.jsx";
import SettingsSetupList from "../components/SettingsSetupList.jsx";
import SettingsUpdatesForm from "../components/SettingsUpdatesForm.jsx";
import _ from "underscore";
import cx from 'classnames';
export default class SettingsEditor extends Component {
import {
getSettings,
getSections,
getActiveSection,
getNewVersionAvailable
} from "../selectors";
import * as settingsActions from "../settings";
const mapStateToProps = (state, props) => {
return {
settings: getSettings(state),
sections: getSections(state),
activeSection: getActiveSection(state),
newVersionAvailable: getNewVersionAvailable(state)
}
}
const mapDispatchToProps = {
...settingsActions
}
@connect(mapStateToProps, mapDispatchToProps)
export default class SettingsEditorApp extends Component {
constructor(props, context) {
super(props, context);
this.handleChangeEvent = this.handleChangeEvent.bind(this);
this.selectSection = this.selectSection.bind(this);
this.updateSetting = this.updateSetting.bind(this);
this.state = {
currentSection: 0
};
}
static propTypes = {
initialSection: PropTypes.number,
sections: PropTypes.object.isRequired,
sections: PropTypes.array.isRequired,
activeSection: PropTypes.object.isRequired,
updateSetting: PropTypes.func.isRequired,
updateEmailSettings: PropTypes.func.isRequired,
updateSlackSettings: PropTypes.func.isRequired,
sendTestEmail: PropTypes.func.isRequired
};
componentWillMount() {
if (this.props.initialSection) {
this.setState({
currentSection: this.props.initialSection
});
}
}
selectSection(section) {
this.setState({ currentSection: section });
this.props.initializeSettings();
}
updateSetting(setting, value) {
......@@ -64,7 +75,9 @@ export default class SettingsEditor extends Component {
}
renderSettingsPane() {
let section = this.props.sections[this.state.currentSection];
if (!this.props.activeSection) return null;
let section = this.props.activeSection; // this.props.sections[this.state.currentSection];
if (section.name === "Email") {
return (
......@@ -120,14 +133,16 @@ export default class SettingsEditor extends Component {
}
renderSettingsSections() {
const sections = _.map(this.props.sections, (section, idx) => {
const { sections, activeSection, selectSection, newVersionAvailable } = this.props;
const renderedSections = _.map(sections, (section, idx) => {
const classes = cx("AdminList-item", "flex", "align-center", "justify-between", "no-decoration", {
"selected": this.state.currentSection === idx
"selected": activeSection && section.name === activeSection.name // this.state.currentSection === idx
});
// if this is the Updates section && there is a new version then lets add a little indicator
let newVersionIndicator;
if (section.name === "Updates" && MetabaseSettings.newVersionAvailable(this.props.settings)) {
if (section.name === "Updates" && newVersionAvailable) {
newVersionIndicator = (
<span style={{padding: "4px 8px 4px 8px"}} className="bg-brand rounded text-white text-bold h6">1</span>
);
......@@ -135,7 +150,7 @@ export default class SettingsEditor extends Component {
return (
<li key={section.name}>
<a href="#" className={classes} onClick={this.selectSection.bind(null, idx)}>
<a href="#" className={classes} onClick={() => selectSection(section.name)}>
<span>{section.name}</span>
{newVersionIndicator}
</a>
......@@ -146,7 +161,7 @@ export default class SettingsEditor extends Component {
return (
<div className="MetadataEditor-table-list AdminList flex-no-shrink">
<ul className="AdminList-items pt1">
{sections}
{renderedSections}
</ul>
</div>
);
......
import "metabase/services";
import _ from "underscore";
import SettingsEditor from './components/SettingsEditor.jsx';
import MetabaseSettings from 'metabase/lib/settings';
import { createSelector } from "reselect";
import MetabaseSettings from "metabase/lib/settings";
var SettingsAdminControllers = angular.module('metabase.admin.settings.controllers', ['metabase.services']);
const SECTIONS = [
{
name: "Setup",
......@@ -134,37 +129,24 @@ const SECTIONS = [
}
];
SettingsAdminControllers.controller('SettingsEditor', ['$scope', '$location', 'Settings', 'Email', 'Slack', 'AppState', 'settings',
function($scope, $location, Settings, Email, Slack, AppState, settings) {
$scope.SettingsEditor = SettingsEditor;
if ('section' in $location.search()) {
$scope.initialSection = _.findIndex(SECTIONS, function(v) {
return v.name === $location.search().section;
});
}
$scope.updateSetting = async function(setting) {
await Settings.put({ key: setting.key }, setting).$promise;
AppState.refreshSiteSettings();
};
export const getSettings = state => state.settings;
$scope.updateEmailSettings = async function(settings) {
await Email.updateSettings(settings).$promise;
AppState.refreshSiteSettings();
}
$scope.updateSlackSettings = async function(settings) {
await Slack.updateSettings(settings).$promise;
AppState.refreshSiteSettings();
}
export const getNewVersionAvailable = createSelector(
getSettings,
(settings) => {
return MetabaseSettings.newVersionAvailable(settings);
}
);
$scope.sendTestEmail = async function(settings) {
await Email.sendTest().$promise;
export const getSections = createSelector(
getSettings,
(settings) => {
if (!settings || _.isEmpty(settings)) {
return [];
}
let settingsByKey = _.groupBy(settings, 'key');
$scope.sections = SECTIONS.map(function(section) {
return SECTIONS.map(function(section) {
let sectionSettings = section.settings.map(function(setting) {
const apiSetting = settingsByKey[setting.key][0];
if (apiSetting) {
......@@ -177,7 +159,17 @@ SettingsAdminControllers.controller('SettingsEditor', ['$scope', '$location', 'S
updatedSection.settings = sectionSettings;
return updatedSection;
});
}
);
$scope.settings = settings;
export const getActiveSection = createSelector(
state => state.activeSection,
getSections,
(section, sections) => {
if (sections) {
return _.findWhere(sections, {name: section});
} else {
return null;
}
}
]);
);
import { createAction } from "redux-actions";
import { handleActions, combineReducers, AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
// resource wrappers
const SettingsApi = new AngularResourceProxy("Settings", ["list", "put"]);
const EmailApi = new AngularResourceProxy("Email", ["updateSettings"]);
const SlackApi = new AngularResourceProxy("Slack", ["updateSettings"]);
async function loadSettings() {
try {
let settings = await SettingsApi.list();
return settings.map(function(setting) {
setting.originalValue = setting.value;
return setting;
});
} catch(error) {
console.log("error fetching settings", error);
}
}
// initializeSettings
export const initializeSettings = createThunkAction("INITIALIZE_SETTINGS", function() {
return async function(dispatch, getState) {
try {
return await loadSettings();
} catch(error) {
console.log("error fetching settings", error);
}
};
});
// selectSection
export const selectSection = createAction("SELECT_SECTION");
// updateSetting
export const updateSetting = createThunkAction("UPDATE_SETTING", function(setting) {
return async function(dispatch, getState) {
const { refreshSiteSettings } = getState();
try {
await SettingsApi.put({ key: setting.key }, setting);
refreshSiteSettings();
return await loadSettings();
} catch(error) {
console.log("error updating setting", setting, error);
}
};
});
// updateEmailSettings
export const updateEmailSettings = createThunkAction("UPDATE_EMAIL_SETTINGS", function(settings) {
return async function(dispatch, getState) {
const { refreshSiteSettings } = getState();
try {
await EmailApi.updateSettings(settings);
refreshSiteSettings();
return await loadSettings();
} catch(error) {
console.log("error updating email settings", settings, error);
}
};
});
// sendTestEmail
export const sendTestEmail = createThunkAction("SEND_TEST_EMAIL", function() {
return async function(dispatch, getState) {
try {
await EmailApi.sendTest();
} catch(error) {
console.log("error sending test email", error);
}
};
});
// updateSlackSettings
export const updateSlackSettings = createThunkAction("UPDATE_SLACK_SETTINGS", function(settings) {
return async function(dispatch, getState) {
const { refreshSiteSettings } = getState();
try {
await SlackApi.updateSettings(settings);
refreshSiteSettings();
return await loadSettings();
} catch(error) {
console.log("error updating slack settings", settings, error);
}
};
});
// reducers
// this is a backwards compatibility thing with angular to allow programmatic route changes. remove/change this when going to ReduxRouter
const refreshSiteSettings = handleActions({}, () => null);
const settings = handleActions({
["INITIALIZE_SETTINGS"]: { next: (state, { payload }) => payload },
["UPDATE_SETTING"]: { next: (state, { payload }) => payload },
["UPDATE_EMAIL_SETTINGS"]: { next: (state, { payload }) => payload },
["UPDATE_SLACK_SETTINGS"]: { next: (state, { payload }) => payload }
}, []);
const activeSection = handleActions({
["SELECT_SECTION"]: { next: (state, { payload }) => payload }
}, "Setup");
export default combineReducers({
settings,
activeSection,
refreshSiteSettings
});
import "metabase/services";
import "./settings.controllers";
import { createStore } from "metabase/lib/redux";
import SetttingsEditorApp from "./containers/SettingsEditorApp.jsx";
import settingsReducers from "./settings";
var SettingsAdmin = angular.module('metabase.admin.settings', [
'metabase.admin.settings.controllers',
'metabase.services'
]);
SettingsAdmin.config(['$routeProvider', function($routeProvider) {
angular
.module('metabase.admin.settings', ['metabase.services'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/admin/settings/', {
template: '<div mb-react-component="SettingsEditor" class="full-height"></div>',
controller: 'SettingsEditor',
resolve: {
settings: ['Settings', async function(Settings) {
var settings = await Settings.list().$promise
return settings.map(function(setting) {
setting.originalValue = setting.value;
return setting;
template: '<div class="full-height" mb-redux-component />',
controller: ['$scope', '$location', 'AppState',
function($scope, $location, AppState) {
$scope.Component = SetttingsEditorApp;
$scope.props = {};
$scope.store = createStore(settingsReducers, {
refreshSiteSettings: () => AppState.refreshSiteSettings(),
activeSection: $location.search().section || "Setup"
});
}
],
resolve: {
appState: ["AppState", function(AppState) {
return AppState.init();
}]
}
});
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment