[MM-14740] Consolidate configuration to support integration of MSI/GPO (#959)

* config logic consolidation

* filter out duplicate servers

* build default teams and GPO teams are not editable

* tweaks

* tweak config architecture to support tests

- config needs to load in each process (main and renderer) and then synchronize with each other
- finished saving ui functionality

* add esdoc comments to new config module

* remove old config-related files

* revert eslint comment

* don’t filter teams, duplicates are allowed

* some code review tweaks

* Remove unecessary deepCopy

* tweak for tests

* Skip test for now

Can’t seem to get this test to work, even though what is being tested works fine in the actual app.

* fix for failing test

click of ‘light’ option wasn’t triggering an update as it is selected by default, so flipped the order to first select ‘dark’ and then ‘light’
This commit is contained in:
Dean Whillier
2019-04-11 07:58:30 -04:00
committed by William Gathoye
parent b7b88c4fbb
commit 4137d0ea23
15 changed files with 618 additions and 422 deletions

354
src/common/config/index.js Normal file
View File

@@ -0,0 +1,354 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import fs from 'fs';
import path from 'path';
import {EventEmitter} from 'events';
import defaultPreferences from './defaultPreferences';
import upgradeConfigData from './upgradePreferences';
import buildConfig from './buildConfig';
/**
* Handles loading and merging all sources of configuration as well as saving user provided config
*/
export default class Config extends EventEmitter {
constructor(configFilePath) {
super();
this.configFilePath = configFilePath;
this.reload();
}
/**
* Reload all sources of config data
*
* @param {boolean} synchronize determines whether or not to emit a synchronize event once config has been reloaded
* @emits {update} emitted once all data has been loaded and merged
* @emits {synchronize} emitted when requested by a call to method; used to notify other config instances of changes
*/
reload(synchronize = false) {
this.defaultConfigData = this.loadDefaultConfigData();
this.buildConfigData = this.loadBuildConfigData();
this.localConfigData = this.loadLocalConfigFile();
this.localConfigData = this.checkForConfigUpdates(this.localConfigData);
this.GPOConfigData = this.loadGPOConfigData();
this.regenerateCombinedConfigData();
this.emit('update', this.combinedData);
if (synchronize) {
this.emit('synchronize');
}
}
/**
* Used to save a single config property
*
* @param {string} key name of config property to be saved
* @param {*} data value to save for provided key
*/
set(key, data) {
if (key) {
this.localConfigData[key] = data;
this.regenerateCombinedConfigData();
this.saveLocalConfigData();
}
}
/**
* Used to save an array of config properties in one go
*
* @param {array} properties an array of config properties to save
*/
setMultiple(properties = []) {
if (properties.length) {
properties.forEach(({key, data}) => {
if (key) {
this.localConfigData[key] = data;
}
});
this.regenerateCombinedConfigData();
this.saveLocalConfigData();
}
}
/**
* Used to replace the existing config data with new config data
*
* @param {object} configData a new, config data object to completely replace the existing config data
*/
replace(configData) {
const newConfigData = configData;
this.localConfigData = Object.assign({}, this.localConfigData, newConfigData);
this.regenerateCombinedConfigData();
this.saveLocalConfigData();
}
/**
* Used to save the current set of local config data to disk
*
* @emits {update} emitted once all data has been saved
* @emits {synchronize} emitted once all data has been saved; used to notify other config instances of changes
* @emits {error} emitted if saving local config data to file fails
*/
saveLocalConfigData() {
try {
this.writeFile(this.configFilePath, this.localConfigData, (error) => {
if (error) {
throw new Error(error);
}
this.emit('update', this.combinedData);
this.emit('synchronize');
});
} catch (error) {
this.emit('error', error);
}
}
// getters for accessing the various config data inputs
get data() {
return this.combinedData;
}
get localData() {
return this.localConfigData;
}
get defaultData() {
return this.defaultConfigData;
}
get buildData() {
return this.buildConfigData;
}
get GPOData() {
return this.GPOConfigData;
}
// convenience getters
get version() {
return this.combinedData.version;
}
get teams() {
return this.combinedData.teams;
}
get localTeams() {
return this.localConfigData.teams;
}
get predefinedTeams() {
return [...this.buildConfigData.defaultTeams, ...this.GPOConfigData.teams];
}
get enableHardwareAcceleration() {
return this.combinedData.enableHardwareAcceleration;
}
get enableServerManagement() {
return this.combinedData.enableServerManagement;
}
get enableAutoUpdater() {
return this.combinedData.enableAutoUpdater;
}
get autostart() {
return this.combinedData.autostart;
}
get notifications() {
return this.combinedData.notifications;
}
get showUnreadBadge() {
return this.combinedData.showUnreadBadge;
}
get useSpellChecker() {
return this.combinedData.useSpellChecker;
}
get spellCheckerLocale() {
return this.combinedData.spellCheckerLocale;
}
get showTrayIcon() {
return this.combinedData.showTrayIcon;
}
get trayIconTheme() {
return this.combinedData.trayIconTheme;
}
get helpLink() {
return this.combinedData.helpLink;
}
// initialization/processing methods
/**
* Returns a copy of the app's default config data
*/
loadDefaultConfigData() {
return this.copy(defaultPreferences);
}
/**
* Returns a copy of the app's build config data
*/
loadBuildConfigData() {
return this.copy(buildConfig);
}
/**
* Loads and returns locally stored config data from the filesystem or returns app defaults if no file is found
*/
loadLocalConfigFile() {
let configData = {};
try {
configData = this.readFileSync(this.configFilePath);
} catch (e) {
console.log('Failed to load configuration file from the filesystem. Using defaults.');
configData = this.copy(this.defaultConfigData);
// add default team to teams if one exists and there arent currently any teams
if (!configData.teams.length && this.defaultConfigData.defaultTeam) {
configData.teams.push(this.defaultConfigData.defaultTeam);
}
delete configData.defaultTeam;
this.writeFileSync(this.configFilePath, configData);
}
return configData;
}
/**
* Loads and returns config data defined in GPO for Windows
*/
loadGPOConfigData() {
const configData = {
teams: [],
enableServerManagement: true,
enableAutoUpdater: true,
};
if (process.platform === 'win32') {
//
// TODO: GPO data needs to be retrieved here and merged into the local `configData` variable for return
//
}
return configData;
}
/**
* Determines if locally stored data needs to be updated and upgrades as needed
*
* @param {*} data locally stored data
*/
checkForConfigUpdates(data) {
let configData = data;
try {
if (configData.version !== this.defaultConfigData.version) {
configData = upgradeConfigData(configData);
this.writeFileSync(this.configFilePath, configData);
console.log(`Configuration updated to version ${this.defaultConfigData.version} successfully.`);
}
} catch (error) {
console.log(`Failed to update configuration to version ${this.defaultConfigData.version}.`);
}
return configData;
}
/**
* Properly combines all sources of data into a single, manageable set of all config data
*/
regenerateCombinedConfigData() {
// combine all config data in the correct order
this.combinedData = Object.assign({}, this.defaultConfigData, this.localConfigData, this.buildConfigData, this.GPOConfigData);
// remove unecessary data pulled from default and build config
delete this.combinedData.defaultTeam;
delete this.combinedData.defaultTeams;
// IMPORTANT: properly combine teams from all sources
const combinedTeams = [];
// - start by adding default teams from buildConfig, if any
if (this.buildConfigData.defaultTeams && this.buildConfigData.defaultTeams.length) {
combinedTeams.push(...this.buildConfigData.defaultTeams);
}
// - add GPO defined teams, if any
if (this.GPOConfigData.teams && this.GPOConfigData.teams.length) {
combinedTeams.push(...this.GPOConfigData.teams);
}
// - add locally defined teams only if server management is enabled
if (this.enableServerManagement) {
combinedTeams.push(...this.localConfigData.teams);
}
this.combinedData.teams = combinedTeams;
this.combinedData.localTeams = this.localConfigData.teams;
this.combinedData.buildTeams = this.buildConfigData.defaultTeams;
this.combinedData.GPOTeams = this.GPOConfigData.teams;
}
/**
* Returns the provided list of teams with duplicates filtered out
*
* @param {array} teams array of teams to check for duplicates
*/
filterOutDuplicateTeams(teams) {
let newTeams = teams;
const uniqueURLs = new Set();
newTeams = newTeams.filter((team) => {
return uniqueURLs.has(team.url) ? false : uniqueURLs.add(team.url);
});
return newTeams;
}
/**
* Returns the provided array fo teams with existing teams filtered out
* @param {array} teams array of teams to check for already defined teams
*/
filterOutPredefinedTeams(teams) {
let newTeams = teams;
// filter out predefined teams
newTeams = newTeams.filter((newTeam) => {
return this.predefinedTeams.findIndex((existingTeam) => newTeam.url === existingTeam.url) === -1; // eslint-disable-line max-nested-callbacks
});
return newTeams;
}
// helper functions
readFileSync(filePath) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
writeFile(filePath, configData, callback) {
if (configData.version !== this.defaultConfigData.version) {
throw new Error('version ' + configData.version + ' is not equal to ' + this.defaultConfigData.version);
}
const json = JSON.stringify(configData, null, ' ');
fs.writeFile(filePath, json, 'utf8', callback);
}
writeFileSync(filePath, config) {
if (config.version !== this.defaultConfigData.version) {
throw new Error('version ' + config.version + ' is not equal to ' + this.defaultConfigData.version);
}
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
const json = JSON.stringify(config, null, ' ');
fs.writeFileSync(filePath, json, 'utf8');
}
merge(base, target) {
return Object.assign({}, base, target);
}
copy(data) {
return Object.assign({}, data);
}
}

View File

@@ -1,78 +0,0 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
'use strict';
import fs from 'fs';
import path from 'path';
import buildConfig from './config/buildConfig';
function merge(base, target) {
return Object.assign({}, base, target);
}
import defaultPreferences from './config/defaultPreferences';
import upgradePreferences from './config/upgradePreferences';
function loadDefault() {
return JSON.parse(JSON.stringify(defaultPreferences));
}
function hasBuildConfigDefaultTeams(config) {
return config.defaultTeams.length > 0;
}
function upgrade(config) {
return upgradePreferences(config);
}
export default {
version: defaultPreferences.version,
upgrade,
readFileSync(configFile) {
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
if (config.version === defaultPreferences.version) {
const defaultConfig = loadDefault();
return merge(defaultConfig, config);
}
return config;
},
writeFile(configFile, config, callback) {
if (config.version !== defaultPreferences.version) {
throw new Error('version ' + config.version + ' is not equal to ' + defaultPreferences.version);
}
const data = JSON.stringify(config, null, ' ');
fs.writeFile(configFile, data, 'utf8', callback);
},
writeFileSync(configFile, config) {
if (config.version !== defaultPreferences.version) {
throw new Error('version ' + config.version + ' is not equal to ' + defaultPreferences.version);
}
const dir = path.dirname(configFile);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
const data = JSON.stringify(config, null, ' ');
fs.writeFileSync(configFile, data, 'utf8');
},
loadDefault,
mergeDefaultTeams(teams) {
const newTeams = [];
if (hasBuildConfigDefaultTeams(buildConfig)) {
newTeams.push(...JSON.parse(JSON.stringify(buildConfig.defaultTeams)));
}
if (buildConfig.enableServerManagement) {
newTeams.push(...JSON.parse(JSON.stringify(teams)));
}
return newTeams;
},
};