Update after review comments

From review (https://github.com/elastic/kibana/pull/7545#issuecomment-231884490):
- i18n module API should return promises for async operations instead of using
callbacks
- All filesystem access should be async
- Unit tests need to be updated based on new proposed plugin structure
(single language file, not split by view)

From design (https://github.com/elastic/kibana/issues/6515#issuecomment-231400097):
- Removed API as will consider in later phase

TODO:
- Make write function async
This commit is contained in:
Martin Hickey 2016-07-15 19:24:21 +01:00
parent c8b2197ad5
commit e17653dd04
14 changed files with 157 additions and 476 deletions

View file

@ -1,10 +1,7 @@
import route from './server/route';
export default function (kibana) {
return new kibana.Plugin({
init(server, options) {
// Add server routes and initalize the plugin here
route(server);
}
});
};

View file

@ -1,4 +0,0 @@
{
"test_plugin_1-NO_SSL": "Dont run the DE dev server using HTTPS",
"test_plugin_1-DEV": "Run the DE server with development mode defaults"
}

View file

@ -1,4 +0,0 @@
{
"test_plugin_1-NO_SSL": "Dont run the dev server using HTTPS",
"test_plugin_1-DEV": "Run the server with development mode defaults"
}

View file

@ -1,4 +0,0 @@
{
"test_plugin_1-NO_RUN_SERVER": "Dont run the dev server",
"test_plugin_1-HOME": "Run along home now!"
}

View file

@ -1,4 +0,0 @@
{
"test_plugin_2-XXXXXX": "This is XXXXXX string",
"test_plugin_2-YYYY_PPPP": "This is YYYY_PPPP string"
}

View file

@ -1,4 +0,0 @@
{
"test_plugin_2-FFFFFFFFFFFF": "This is FFFFFFFFFFFF string",
"test_plugin_2-ZZZ": "This is ZZZ string"
}

View file

@ -11,58 +11,58 @@ describe('Test plugin translations details for test_plugin_1', function () {
it('Translation languages exist', function (done) {
let result = true;
const expectedLanguages = ['en', 'de'];
getPluginTranslationLanguages(pluginName, pluginTranslationPath, function (err, actualLanguages) {
if (err) {
console.log(err);
let actualLanguages = [];
getPluginTranslationLanguages(pluginTranslationPath, actualLanguages).then(function () {
if (actualLanguages.length !== expectedLanguages.length) {
result = false;
} else {
if (actualLanguages.length !== expectedLanguages.length) {
result = false;
} else {
let index = actualLanguages.length;
actualLanguages.sort();
expectedLanguages.sort();
while (index--) {
if (actualLanguages[index] !== expectedLanguages[index]) {
result = false;
break;
}
let index = actualLanguages.length;
actualLanguages.sort();
expectedLanguages.sort();
while (index--) {
if (actualLanguages[index] !== expectedLanguages[index]) {
result = false;
break;
}
}
}
expect(result).to.be(true);
done();
}).catch(function (e) {
console.log(e);
result = false;
expect(result).to.be(true);
done();
});
});
it('Translation files exist', function (done) {
let result = true;
let actualFiles = [];
const expectedFiles = [
pluginTranslationPath + '/view1/de.json',
pluginTranslationPath + '/view1/en.json',
pluginTranslationPath + '/view2/en.json'
pluginTranslationPath + '/de.json',
pluginTranslationPath + '/en.json'
];
getPluginTranslationFiles(pluginName, pluginTranslationPath, function (err, actualFiles) {
if (err) {
console.log(err);
getPluginTranslationFiles(pluginTranslationPath, actualFiles).then(function () {
if (actualFiles.length !== expectedFiles.length) {
result = false;
} else {
if (actualFiles.length !== expectedFiles.length) {
result = false;
} else {
let index = actualFiles.length;
actualFiles.sort();
expectedFiles.sort();
while (index--) {
if (actualFiles[index] !== expectedFiles[index]) {
result = false;
break;
}
let index = actualFiles.length;
actualFiles.sort();
expectedFiles.sort();
while (index--) {
if (actualFiles[index] !== expectedFiles[index]) {
result = false;
break;
}
}
}
expect(result).to.be(true);
done();
}).catch(function (e) {
console.log(e);
expect(result).to.be(true);
done();
});
});
});
@ -72,83 +72,26 @@ describe('Test registering translations for test_plugin_1', function () {
const pluginTranslationPath = DATA_PATH + '/translations/' + pluginName;
it('Register translations' , function (done) {
let result = true;
i18n.registerTranslations(pluginTranslationPath, function (err) {
if (err) {
console.log(err);
result = false;
}
expect(result).to.be(true);
done();
});
registerTranslations(pluginTranslationPath, done);
});
it('EN translations are registered' , function (done) {
let result = true;
const language = 'en';
const expectedTranslationJsonFile = DATA_PATH + '/reference/' + pluginName + '/' + language + '.json';
const expectedTranslationJson = require(expectedTranslationJsonFile);
i18n.getRegisteredLanguageTranslations(language, function (err, actualTranslationJson) {
if (err) {
console.log(err);
result = false;
} else {
if (!compareTranslations(actualTranslationJson, expectedTranslationJson)) {
result = false;
}
}
expect(result).to.be(true);
done();
});
checkTranslations(language, expectedTranslationJson, done);
});
it('DE translations are registered' , function (done) {
let result = true;
const language = 'de';
const expectedTranslationJsonFile = DATA_PATH + '/reference/' + pluginName + '/' + language + '.json';
const expectedTranslationJson = require(expectedTranslationJsonFile);
i18n.getRegisteredLanguageTranslations(language, function (err, actualTranslationJson) {
if (err) {
console.log(err);
result = false;
} else {
if (!compareTranslations(actualTranslationJson, expectedTranslationJson)) {
result = false;
}
}
expect(result).to.be(true);
done();
});
checkTranslations(language, expectedTranslationJson, done);
});
it('Translation languages are registered', function (done) {
const expectedLanguages = ['en', 'de'];
let result = true;
i18n.getRegisteredTranslationLanguages(function (err, actualLanguages) {
if (err) {
console.log(err);
result = false;
}
if (actualLanguages.length !== expectedLanguages.length) {
result = false;
} else {
let index = actualLanguages.length;
actualLanguages.sort();
expectedLanguages.sort();
while (index--) {
if (actualLanguages[index] !== expectedLanguages[index]) {
result = false;
break;
}
}
}
expect(result).to.be(true);
done();
});
checkRegisteredLanguages(expectedLanguages, done);
});
after(function (done) {
@ -160,99 +103,34 @@ describe('Test registering translations for test_plugin_1', function () {
describe('Test registering translations for test_plugin_1 and test_plugin_2', function () {
it('Register translations for test_plugin_1' , function (done) {
let result = true;
const pluginName = 'test_plugin_1';
const pluginTranslationPath = DATA_PATH + '/translations/' + pluginName;
i18n.registerTranslations(pluginTranslationPath, function (err) {
if (err) {
console.log(err);
result = false;
}
expect(result).to.be(true);
done();
});
registerTranslations(pluginTranslationPath, done);
});
it('Register translations for test_plugin_2' , function (done) {
let result = true;
const pluginName = 'test_plugin_2';
const pluginTranslationPath = DATA_PATH + '/translations/' + pluginName;
i18n.registerTranslations(pluginTranslationPath, function (err) {
if (err) {
console.log(err);
result = false;
}
expect(result).to.be(true);
done();
});
registerTranslations(pluginTranslationPath, done);
});
it('EN translations are registered' , function (done) {
let result = true;
const language = 'en';
const expectedTranslationJsonFile = DATA_PATH + '/reference/' + language + '.json';
const expectedTranslationJson = require(expectedTranslationJsonFile);
i18n.getRegisteredLanguageTranslations(language, function (err, actualTranslationJson) {
if (err) {
console.log(err);
result = false;
} else {
if (!compareTranslations(actualTranslationJson, expectedTranslationJson)) {
result = false;
}
}
expect(result).to.be(true);
done();
});
checkTranslations(language, expectedTranslationJson, done);
});
it('DE translations are registered' , function (done) {
let result = true;
const language = 'de';
const expectedTranslationJsonFile = DATA_PATH + '/reference/' + language + '.json';
const expectedTranslationJson = require(expectedTranslationJsonFile);
i18n.getRegisteredLanguageTranslations(language, function (err, actualTranslationJson) {
if (err) {
console.log(err);
result = false;
} else {
if (!compareTranslations(actualTranslationJson, expectedTranslationJson)) {
result = false;
}
}
expect(result).to.be(true);
done();
});
checkTranslations(language, expectedTranslationJson, done);
});
it('Translation languages are registered', function (done) {
const expectedLanguages = ['en', 'de'];
let result = true;
i18n.getRegisteredTranslationLanguages(function (err, actualLanguages) {
if (err) {
console.log(err);
result = false;
}
if (actualLanguages.length !== expectedLanguages.length) {
result = false;
} else {
let index = actualLanguages.length;
actualLanguages.sort();
expectedLanguages.sort();
while (index--) {
if (actualLanguages[index] !== expectedLanguages[index]) {
result = false;
break;
}
}
}
expect(result).to.be(true);
done();
});
checkRegisteredLanguages(expectedLanguages, done);
});
after(function (done) {
@ -262,22 +140,14 @@ describe('Test registering translations for test_plugin_1 and test_plugin_2', fu
});
});
function getPluginTranslationLanguages(pluginName, pluginTranslationPath, cb) {
function getPluginTranslationLanguages(pluginTranslationPath, languageList) {
let translationFiles = [];
let languageList = [];
i18n.getPluginTranslationDetails(pluginTranslationPath, translationFiles, languageList, function (err) {
if (err) return cb(err);
return cb(null, languageList);
});
return i18n.getPluginTranslationDetails(pluginTranslationPath, translationFiles, languageList);
}
function getPluginTranslationFiles(pluginName, pluginTranslationPath, cb) {
let translationFiles = [];
function getPluginTranslationFiles(pluginTranslationPath, translationFiles) {
let languageList = [];
i18n.getPluginTranslationDetails(pluginTranslationPath, translationFiles, languageList, function (err) {
if (err) return cb(err);
return cb(null, translationFiles);
});
return i18n.getPluginTranslationDetails(pluginTranslationPath, translationFiles, languageList);
}
function compareTranslations(actual, expected) {
@ -296,3 +166,61 @@ function compareTranslations(actual, expected) {
return equal;
}
function checkTranslations(language, expectedTranslations, done) {
let result = true;
i18n.getRegisteredLanguageTranslations(language).then(function (actualTranslationJson) {
if (!compareTranslations(actualTranslationJson, expectedTranslations)) {
result = false;
}
expect(result).to.be(true);
done();
}).catch(function (err) {
console.log(err);
result = false;
expect(result).to.be(true);
done();
});
}
function registerTranslations(pluginTranslationPath, done) {
let result = true;
i18n.registerTranslations(pluginTranslationPath).then(function () {
expect(result).to.be(true);
done();
}).catch(function (err) {
console.log(err);
result = false;
expect(result).to.be(true);
done();
});
}
function checkRegisteredLanguages(expectedLanguages, done) {
let result = true;
i18n.getRegisteredTranslationLanguages().then(function (actualLanguages) {
if (actualLanguages.length !== expectedLanguages.length) {
result = false;
} else {
let index = actualLanguages.length;
actualLanguages.sort();
expectedLanguages.sort();
while (index--) {
if (actualLanguages[index] !== expectedLanguages[index]) {
result = false;
break;
}
}
}
expect(result).to.be(true);
done();
}).catch(function (err) {
console.log(err);
result = false;
expect(result).to.be(true);
done();
});
}

View file

@ -4,44 +4,40 @@ import mkdirp from 'mkdirp';
import os from 'os';
import path from 'path';
import process from 'child_process';
import Promise from 'bluebird';
const readdir = Promise.promisify(fs.readdir);
const readFile = Promise.promisify(fs.readFile);
const stat = Promise.promisify(fs.stat);
const TRANSLATION_FILE_EXTENSION = 'json';
const TRANSLATION_STORE_PATH = kibanaPackage.__dirname + '/fixtures/translations';
let getPluginTranslationDetails = function (pluginTranslationPath, translationFiles, languageList, callback) {
try {
getFilesRecursivelyFromTopDir(pluginTranslationPath, translationFiles, languageList);
} catch (err) {
return callback(err);
}
return callback(null);
const getPluginTranslationDetails = function (pluginTranslationPath, translationFiles, languageList) {
return getFilesRecursivelyFromTopDir(pluginTranslationPath, translationFiles, languageList);
};
//TODO(hickeyma): Update to use https://github.com/elastic/kibana/pull/7562
let getTranslationStoragePath = function () {
const getTranslationStoragePath = function () {
return TRANSLATION_STORE_PATH;
};
let getRegisteredTranslationLanguages = function (cb) {
const getRegisteredTranslationLanguages = function () {
let translationFiles = [];
let languageList = [];
const translationStorePath = getTranslationStoragePath();
try {
getTranslationDetailsFromDirectory(translationStorePath, translationFiles, languageList);
} catch (err) {
return cb(err);
}
return cb(null, languageList);
return getTranslationDetailsFromDirectory(translationStorePath, translationFiles, languageList).then(function () {
return languageList;
});
};
let registerTranslations = function (pluginTranslationPath, cb) {
const registerTranslations = function (pluginTranslationPath) {
let translationFiles = [];
let languageList = [];
const translationStorePath = getTranslationStoragePath();
getPluginTranslationDetails(pluginTranslationPath, translationFiles, languageList, function (err) {
if (err) return cb(err);
return getPluginTranslationDetails(pluginTranslationPath, translationFiles, languageList).then(function () {
try {
if (!fs.existsSync(translationStorePath)) {
mkdirp.sync(translationStorePath);
@ -55,26 +51,27 @@ let registerTranslations = function (pluginTranslationPath, cb) {
saveTranslationToFile(fileToWrite, translationJson);
}
} catch (err) {
return cb(err);
throw err;
}
});
return cb(null);
};
let getRegisteredLanguageTranslations = function (language, callback) {
const getRegisteredLanguageTranslations = function (language) {
const translationStorePath = getTranslationStoragePath();
const translationFileName = language + '.' + TRANSLATION_FILE_EXTENSION;
const translationFile = translationStorePath + '/' + translationFileName;
fs.readFile(translationFile, function (err, translationStr) {
if (err) return callback(err);
return readFile(translationFile, 'utf8').then(function (translationStr) {
let translationJson = [];
try {
translationJson = JSON.parse(translationStr);
} catch (e) {
return callback('Bad ' + language + ' translation strings. Error: ' + err);
} catch (err) {
throw new Error('Bad ' + language + ' translation strings. Reason: ' + err);
}
return callback(null, translationJson);
return translationJson;
}).catch(function (e) {
throw new Error('Failed to read translation file. Reason: ' + e);
});
};
@ -97,45 +94,43 @@ function saveTranslationToFile(translationFullFileName, translationToAddJson) {
}
function getFilesRecursivelyFromTopDir(topDir, translationFiles, languageList) {
fs.readdirSync(topDir).forEach(function (name) {
const fullPath = path.join(topDir, name);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
getTranslationDetailsFromDirectory(fullPath, translationFiles, languageList);
}
return readdir(topDir).then((topDirectoryListing) => {
return Promise.map(topDirectoryListing, (listing) => {
const fullPath = path.join(topDir, listing);
return stat(fullPath).then((stats) => {
if (stats.isDirectory()) {
getTranslationDetailsFromDirectory(fullPath, translationFiles, languageList);
} else {
getTranslationDetailsFromFile(fullPath, translationFiles, languageList);
}
});
});
});
}
function getTranslationDetailsFromDirectory(fullPath, translationFiles, languageList) {
const files = getFilesFromDir(fullPath);
const fileLength = files.length;
for (let i = 0; i < fileLength; i++) {
const fullFilePath = files[i];
const fileName = getFileName(fullFilePath);
const fileExt = fileName.split('.').pop();
if (fileName === fileExt) continue;
if (fileExt !== TRANSLATION_FILE_EXTENSION) continue;
translationFiles.push(fullFilePath);
const lang = fileName.substr(0, fileName.lastIndexOf('.'));
if (languageList.indexOf(lang) !== -1) {
continue;
}
languageList.push(lang);
}
function getTranslationDetailsFromDirectory(dir, translationFiles, languageList) {
return readdir(dir).then((dirListing) => {
return Promise.map(dirListing, (listing) => {
const fullPath = path.join(dir, listing);
return stat(fullPath).then((stats) => {
if (!stats.isDirectory()) {
getTranslationDetailsFromFile(fullPath, translationFiles, languageList);
}
});
});
});
}
function getFilesFromDir(dir) {
let fileList = [];
const files = fs.readdirSync(dir);
for (let i in files) {
if (!files.hasOwnProperty(i)) continue;
const name = dir + '/' + files[i];
if (!fs.statSync(name).isDirectory()) {
fileList.push(name);
}
function getTranslationDetailsFromFile(fullFileName, translationFiles, languageList) {
const fileName = getFileName(fullFileName);
const fileExt = fileName.split('.').pop();
if (fileName === fileExt) return;
if (fileExt !== TRANSLATION_FILE_EXTENSION) return;
translationFiles.push(fullFileName);
const lang = fileName.substr(0, fileName.lastIndexOf('.'));
if (languageList.indexOf(lang) === -1) {
languageList.push(lang);
}
return fileList;
}
function getFileName(fullPath) {

View file

@ -1,97 +0,0 @@
import Boom from 'boom';
var i18n = require('./i18n/index');
var langParser = require('accept-language-parser');
const DEFAULT_LANGUAGE = 'en';
export default function (server) {
server.route({
path: '/api/i18n/translations',
method: 'GET',
handler(req, reply) {
var acceptLanguage = req.headers['accept-language'];
var languages = langParser.parse(acceptLanguage);
getRegisteredLanguageTranslations(languages, function (err, translations) {
if (err) {
reply(Boom.internal(err));
return;
}
reply(translations);
});
}
});
};
function getRegisteredLanguageTranslations(acceptLanguages, cb) {
getRegisteredTranslationLanguages(acceptLanguages, function (err, language) {
if (err) {
return cb(err);
}
i18n.getRegisteredLanguageTranslations(language, function (err, translationsJson) {
if (err) {
return cb(err);
} else {
return cb(null, translationsJson);
}
});
});
}
function getRegisteredTranslationLanguages(acceptLanguages, cb) {
var langStr = DEFAULT_LANGUAGE;
i18n.getRegisteredTranslationLanguages(function (err, registeredLanguages) {
if (err) {
return cb(err);
}
var foundLang = false;
var langStr = '';
acceptLanguages.some(function exactMatch(language) {
if (language.region) {
langStr = language.code + '-' + language.region;
} else {
langStr = language.code;
}
if (registeredLanguages.indexOf(langStr) > -1) {
foundLang = true;
return true;
} else {
return false;
}
});
if (foundLang) {
return cb (null, langStr);
}
acceptLanguages.some(function partialMatch(language) {
langStr = language.code;
registeredLanguages.some(function (lang) {
if (lang.match('^' + langStr)) {
langStr = lang;
foundLang = true;
return true;
} else {
return false;
}
});
if (foundLang) {
return true;
} else {
return false;
}
});
if (foundLang) {
return cb (null, langStr);
}
return cb (null, DEFAULT_LANGUAGE);
});
}

View file

@ -1,30 +0,0 @@
define(function (require) {
var expect = require('intern/dojo/node!expect.js');
var testData = require('intern/dojo/node!../../../unit/api/i18n/data');
return function (bdd, scenarioManager, request) {
bdd.describe('GET translations', function postIngest() {
bdd.beforeEach(function () {
return scenarioManager.reload('emptyKibana');
});
bdd.before(function () {
return testData.createTestTranslations();
});
bdd.it('should return 200 and a valid response payload', function validPayload() {
return request.get('/i18n/translations')
.expect(200)
.then(function (res) {
var translationsReturned = JSON.parse(res.text);
expect(testData.checkReturnedTranslations(translationsReturned)).to.be(true);
});
});
bdd.after(function () {
return testData.deleteTestTranslations();
});
});
};
});

View file

@ -1,52 +0,0 @@
var fs = require('fs');
var mkdirp = require('mkdirp');
var process = require('child_process');
var TRANSLATION_STORE_PATH = __dirname + '/../../../../data/translations';
var checkReturnedTranslations = function (actualTranslationJson) {
var language = 'en';
var expectedTranslationJsonFile = __dirname + '/data/reference/' + language + '.json';
var expectedTranslationJson = require(expectedTranslationJsonFile);
return compareTranslations(actualTranslationJson, expectedTranslationJson);
};
var createTestTranslations = function () {
var translationStorePath = TRANSLATION_STORE_PATH;
var language = 'en';
var srcFile = __dirname + '/data/reference/' + language + '.json';
var destFile = translationStorePath + '/' + language + '.json';
if (!fs.existsSync(translationStorePath)) {
mkdirp.sync(translationStorePath);
}
console.log('Write file: ' + srcFile + ' to file: ' + destFile);
fs.writeFileSync(destFile, fs.readFileSync(srcFile));
};
var deleteTestTranslations = function () {
var translationStorePath = TRANSLATION_STORE_PATH;
process.execSync('rm -rf ' + translationStorePath);
};
function compareTranslations(actual, expected) {
var equal = true;
for (var key in expected) {
if (!actual.hasOwnProperty(key)) {
equal = false;
break;
}
if (actual[key] !== expected[key]) {
equal = false;
break;
}
}
return equal;
}
module.exports.checkReturnedTranslations = checkReturnedTranslations;
module.exports.createTestTranslations = createTestTranslations;
module.exports.deleteTestTranslations = deleteTestTranslations;

View file

@ -1,4 +0,0 @@
{
"test_plugin_1-NO_SSL": "Dont run the DE dev server using HTTPS",
"test_plugin_1-DEV": "Run the DE server with development mode defaults"
}

View file

@ -1,10 +0,0 @@
{
"test_plugin_1-NO_SSL": "Dont run the dev server using HTTPS",
"test_plugin_1-DEV": "Run the server with development mode defaults",
"test_plugin_1-NO_RUN_SERVER": "Dont run the dev server",
"test_plugin_1-HOME": "Run along home now!",
"test_plugin_2-XXXXXX": "This is XXXXXX string",
"test_plugin_2-YYYY_PPPP": "This is YYYY_PPPP string",
"test_plugin_2-FFFFFFFFFFFF": "This is FFFFFFFFFFFF string",
"test_plugin_2-ZZZ": "This is ZZZ string"
}

View file

@ -1,26 +0,0 @@
define(function (require) {
var bdd = require('intern!bdd');
var serverConfig = require('intern/dojo/node!../../../server_config');
var ScenarioManager = require('intern/dojo/node!../../../fixtures/scenario_manager');
var request = require('intern/dojo/node!supertest-as-promised');
var url = require('intern/dojo/node!url');
var _ = require('intern/dojo/node!lodash');
var expect = require('intern/dojo/node!expect.js');
var get = require('./_get');
bdd.describe('i18n API', function () {
var scenarioManager = new ScenarioManager(url.format(serverConfig.servers.elasticsearch));
request = request(url.format(serverConfig.servers.kibana) + '/api');
console.log('i18n bdd request: ', request);
bdd.before(function () {
return scenarioManager.load('emptyKibana');
});
bdd.after(function () {
return scenarioManager.unload('emptyKibana');
});
get(bdd, scenarioManager, request);
});
});