@credenceanalytics/metabasecli
Version:
Credence Metabase CLI
252 lines (217 loc) • 9.8 kB
JavaScript
/* eslint-disable no-useless-catch */
/* eslint-disable array-callback-return */
/* eslint-disable unicorn/consistent-destructuring */
/* eslint-disable one-var */
/* eslint-disable prefer-const */
/* eslint-disable import/no-dynamic-require */
/* eslint-disable unicorn/prefer-module */
const { Command } = require('@oclif/core');
const path = require('path');
const { prompt } = require('enquirer');
const fs = require('fs-extra');
const chalk = require('chalk');
const moment = require('moment');
const util = require(path.join(__dirname, '../util.js'));
const { setConsoleLog, initLogger, CredError } = require(path.join(__dirname, '../helper/logger.js'));
const logger = initLogger();
const { sendRequest, validateCredConfig, getMetabaseSessionID } = require(path.join(__dirname, '../helper/api-utils.js'));
setConsoleLog(Command);
function formatDate(dateObj, dateFormat) {
return moment(dateObj).format(dateFormat);
}
async function storeResponse({ detailedInfo, basicInfo: { name: quesOrDashName }, type, fileName }) {
try {
let dateObj = new Date(),
finalFileName = `${fileName.toLowerCase()}${formatDate(dateObj, 'YYYY-MM-DD HH mm ss')}.json`.split(' ').join('_'),
finalResponse = {
date: formatDate(dateObj, 'YYYY-MM-DD'),
type,
typename: quesOrDashName,
data: detailedInfo
},
responseFilePath;
const directory = await prompt([
{
type: 'input',
name: 'save',
message: 'Enter the export folder path',
initial: path.resolve('./'),
result: input => {
return path.resolve(input);
}
}
]);
if (!fs.existsSync(directory.save) || !fs.statSync(directory.save).isDirectory()) {
logger.info(`Provided directory : ${directory.save} is not valid. Stopping the export...`);
throw new Error(`Provided directory : ${directory.save} is not valid.`);
}
responseFilePath = path.join(directory.save, finalFileName);
logger.info(`Storing ${type === 'D' ? 'dashboard' : 'question'} data ${JSON.stringify(finalResponse)} to ${responseFilePath} file`);
await fs.outputFile(responseFilePath, JSON.stringify(finalResponse, null, 2));
logger.info(`Successfully stored ${type === 'D' ? 'dashboard' : 'question'} in ${responseFilePath} file`);
this.console.log(`Successfully stored ${type === 'D' ? 'dashboard' : 'question'} in ${chalk.greenBright(responseFilePath)} file`);
} catch (error) {
throw error;
}
}
async function getSampleDatabase({ sessionID }) {
try {
let APIURL = `${global.credConfig.metabase.url}/api/database`,
options = {
headers: {
'X-Metabase-Session': sessionID
}
};
jsonResponse = await sendRequest(null, options, 'GET', APIURL, "to get sample database");
return jsonResponse.data?.find((db) => db.is_sample && db)
} catch (error) {
error.name = 'Metabase API call failed';
logger.error(`${error.stack ? error.stack : error}`);
throw new CredError('Failed to get the list of databases');
}
}
async function exportDashboard({ sessionID }) {
try {
let APIURL = `${global.credConfig.metabase.url}/api/dashboard`,
options = {
headers: {
'X-Metabase-Session': sessionID
}
},
response,
fileName,
dashboardList = [];
logger.info(`Fetching all the dashboards`);
response = await sendRequest(null, options, 'GET', APIURL, "to get all the dashboards");
logger.info('Successfully fetched all the dashboards');
dashboardList = response.map(({ name, id }) => {
return { name, id };
});
if (dashboardList.length === 0) {
throw new Error('There is no dashboard available for export');
}
const dashboard = await prompt([
{
type: 'select',
name: 'info',
message: 'Please select the dashboard:',
result: dashboardName => {
let dashboardInfo;
dashboardList.map(({ name, id }) => {
if (name === dashboardName) {
dashboardInfo = { id, name };
}
});
return dashboardInfo;
},
choices: dashboardList.map(({ name }) => name)
}
]);
const {
info: { id, name }
} = dashboard;
logger.info(`User has selected ${name} dashboard`);
APIURL = `${global.credConfig.metabase.url}/api/dashboard/${id}`;
logger.info(`Fetching Info. about dashboard ${name}`);
response = await sendRequest(null, options, 'GET', APIURL, "to get detailed information of selected dashboard");
logger.info(`Successfully fetched dashboard ${name}`);
fileName = `dashboard_${name}_`;
await storeResponse({ detailedInfo: response, basicInfo: dashboard.info, type: 'D', fileName });
} catch (error) {
if (error.name !== 'CredError' && error.message) throw new CredError(`${error.message}`);
error.name = 'Metabase API call failed';
logger.error(`${error.stack ? error.stack : error}`);
throw new CredError('Dashboard export failed');
}
}
async function exportQuestion({ sessionID }) {
try {
let APIURL = `${global.credConfig.metabase.url}/api/card`,
options = {
headers: {
'X-Metabase-Session': sessionID
}
},
response,
fileName,
questionsList = [], sampleDbInfo;
logger.info(`Fetching all the questions`);
response = await sendRequest(null, options, 'GET', APIURL, "to get all the questions");
logger.info('Successfully fetched all the questions');
logger.info('Fetching sample database information');
sampleDbInfo = await getSampleDatabase({ sessionID });
logger.info(`Successfully fetched sample database information`);
if (!!sampleDbInfo) {
questionsList = response.filter(({ name, id, database_id }) => {
if (database_id !== sampleDbInfo.id) return { name, id }
});
}
if (questionsList.length === 0) {
throw new Error('There is no question available for export');
}
const question = await prompt([
{
type: 'select',
name: 'info',
message: 'Please select the question:',
result: questionName => {
let questionInfo;
questionsList.map(({ name, id }) => {
if (name === questionName) {
questionInfo = { id, name };
}
});
return questionInfo;
},
choices: questionsList.map(({ name }) => name)
}
]);
const {
info: { id, name }
} = question;
logger.info(`User has selected ${name} question`);
APIURL = `${global.credConfig.metabase.url}/api/card/${id}`;
logger.info(`Fetching Info. about question ${name}`);
response = await sendRequest(null, options, 'GET', APIURL, "to get detailed information of selected question");
logger.info(`Successfully fetched question ${name}`);
fileName = `question_${name}_`;
await storeResponse({ detailedInfo: response, basicInfo: question.info, type: 'Q', fileName });
} catch (error) {
logger.error(`${error.stack ? error.stack : error}`);
if (error.name !== 'CredError' && error.message) throw new CredError(`${error.message}`);
error.name = 'Metabase API call failed';
throw new CredError('Question export failed');
}
}
class ExportCommand extends Command {
async run() {
try {
await validateCredConfig('metabase', "Metabase not initialised.");
logger.info('Checking user sesssion');
let sessionID = await getMetabaseSessionID();
logger.info(`Session check successful. Retrieved session id is : ${sessionID}`);
const choice = await prompt([
{
type: 'select',
name: 'main',
message: 'Please select one of the following:',
choices: ['Dashboard', 'Question']
}
]);
if (choice.main === 'Dashboard') {
logger.info(`Exporting metabase dashboard`);
await exportDashboard({ sessionID });
} else {
logger.info(`Exporting metabase question`);
await exportQuestion({ sessionID });
}
logger.info(`${choice.main} exported successfully`);
this.console.log(`${chalk.greenBright(`${choice.main} exported successfully`)}`);
} catch (error) {
util.errorHandler(error);
}
}
}
ExportCommand.description = `Export metabase dashboard/question`;
ExportCommand.flags = {};
module.exports = ExportCommand;