@codecovevienna/gittt-cli
Version:
Tracking time with CLI into a git repository
1,001 lines • 64.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.App = void 0;
const axios_1 = __importDefault(require("axios"));
const chalk_1 = __importDefault(require("chalk"));
const commander_1 = require("commander");
const lodash_1 = __importStar(require("lodash"));
const moment_1 = __importDefault(require("moment"));
const path_1 = __importDefault(require("path"));
const helper_1 = require("./helper");
const types_1 = require("./types");
// eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-explicit-any
const packageJson = require("./package.json");
const APP_NAME = packageJson.name;
const APP_VERSION = packageJson.version;
const APP_CONFIG_DIR = ".gittt-cli";
const JIRA_ENDPOINT_VERSION = "v2";
class App {
start() {
if (process.argv.length === 2) {
this.commander.help();
}
else {
this.commander.parse(process.argv);
}
}
exit(msg, code) {
if (code === 0) {
helper_1.LogHelper.warn(msg);
}
else {
helper_1.LogHelper.error(msg);
}
process.exit(code);
}
setup() {
return __awaiter(this, void 0, void 0, function* () {
this.configDir = path_1.default.join(helper_1.FileHelper.getHomeDir(), `${APP_CONFIG_DIR}`);
this.fileHelper = new helper_1.FileHelper(this.configDir, "config.json", "timer.json", "projects");
this.configHelper = helper_1.ConfigHelper.getInstance(this.fileHelper);
if (!(yield this.configHelper.isInitialized())) {
if (yield helper_1.QuestionHelper.confirmSetup()) {
yield this.initConfigDir();
helper_1.LogHelper.info("Initialized git-time-tracker (GITTT) you are good to go now ;)\n\n");
}
else {
this.exit(`${APP_NAME} does not work without setup, bye!`, 0);
}
}
this.gitHelper = new helper_1.GitHelper(this.configDir, this.fileHelper);
this.projectHelper = new helper_1.ProjectHelper(this.gitHelper, this.fileHelper);
this.timerHelper = new helper_1.TimerHelper(this.fileHelper, this.projectHelper);
this.importHelper = new helper_1.ImportHelper();
this.authHelper = new helper_1.AuthHelper();
this.initCommander();
});
}
// TODO should be moved to config helper, but gitHelper needs a valid config dir
initConfigDir() {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield this.fileHelper.configDirExists())) {
yield this.fileHelper.createConfigDir();
this.gitHelper = new helper_1.GitHelper(this.configDir, this.fileHelper);
if (!(yield this.fileHelper.isConfigFileValid())) {
const gitUrl = yield helper_1.QuestionHelper.askGitUrl();
helper_1.LogHelper.info("Initializing local repo");
yield this.gitHelper.initRepo(gitUrl);
// TODO remove reset=true?
helper_1.LogHelper.info("Pulling repo...");
yield this.gitHelper.pullRepo();
// Check if a valid config file is already in the repo
if (!(yield this.fileHelper.isConfigFileValid())) {
helper_1.LogHelper.info("Initializing gittt config file");
yield this.fileHelper.initConfigFile(gitUrl);
helper_1.LogHelper.info("Committing created config file");
yield this.gitHelper.commitChanges("Initialized config file");
helper_1.LogHelper.info("Pushing changes to remote repo");
yield this.gitHelper.pushChanges();
}
}
else {
yield this.gitHelper.pullRepo();
}
}
else {
if (yield this.fileHelper.isConfigFileValid()) {
this.gitHelper = new helper_1.GitHelper(this.configDir, this.fileHelper);
yield this.gitHelper.pullRepo();
helper_1.LogHelper.info(`Config directory ${this.configDir} already initialized`);
}
else {
helper_1.LogHelper.warn(`Config file exists, but is invalid`);
this.exit("Invalid config file", 1);
// TODO reinitialize?
}
}
});
}
handleRole(cmd, interactive, project, record, inputRole) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const multipieHelper = new helper_1.MultipieHelper();
const updatedRecord = record;
// Project does not require a role, just return the original record
if (!project.requiresRoles) {
return updatedRecord;
}
if (!interactive) {
if (!inputRole) {
helper_1.LogHelper.error("No role option found");
return cmd.help();
}
// get roles
if (project.requiresRoles) {
const availableRoles = yield multipieHelper.getValidRoles(project, record);
const role = (_a = availableRoles.find((role_) => role_.name == inputRole)) === null || _a === void 0 ? void 0 : _a.value;
if (role === undefined) {
helper_1.LogHelper.info(`Available roles for ${project.name} are: \n${availableRoles
.map(availableRole => ` - ${availableRole.name}`)
.sort()
.join(",\n")}`);
this.exit(`Role "${inputRole}" is not available in this project`, 1);
}
updatedRecord.role = role;
}
}
else {
updatedRecord.role = yield helper_1.QuestionHelper.chooseRole(project, record);
}
return updatedRecord;
});
}
exportAction(options) {
return __awaiter(this, void 0, void 0, function* () {
helper_1.LogHelper.print(`Gathering projects...`);
let projectsToExport = [];
const { project, directory, filename, type } = options;
if (project) {
const projectToExport = yield this.fileHelper.findProjectByName(project);
if (!projectToExport) {
this.exit(`✗ Project "${project}" not found`, 1);
}
else {
projectsToExport.push(projectToExport);
}
}
else {
projectsToExport = yield this.fileHelper.findAllProjects();
}
helper_1.LogHelper.info(`✓ Got all ${projectsToExport.length} projects`);
helper_1.ExportHelper.export(directory, filename, type, projectsToExport);
helper_1.LogHelper.info(`✓ Export done`);
});
}
linkAction(options) {
return __awaiter(this, void 0, void 0, function* () {
const interactiveMode = process.argv.length === 3;
let project;
try {
if (!interactiveMode) {
project = yield this.projectHelper.getProjectByName(options.project);
}
else {
project = yield this.projectHelper.getOrAskForProjectFromGit();
}
}
catch (err) {
return this.exit(err.message, 1);
}
if (!project) {
return this.exit("No valid git project", 1);
}
const integration = yield helper_1.QuestionHelper.chooseIntegration();
helper_1.LogHelper.debug(`Trying to find links for "${project.name}"`);
// Check for previous data
const prevIntegrationLink = (yield this.configHelper
.findLinksByProject(project, integration))[0];
switch (integration) {
case "Jira":
let prevJiraLink;
if (prevIntegrationLink) {
helper_1.LogHelper.info(`Found link for "${project.name}", enriching dialog with previous data`);
prevJiraLink = prevIntegrationLink;
}
const jiraLink = yield helper_1.QuestionHelper.askJiraLink(project, prevJiraLink, JIRA_ENDPOINT_VERSION);
try {
yield this.configHelper.addOrUpdateLink(jiraLink);
}
catch (err) {
helper_1.LogHelper.debug(`Unable to add link to config file`, err);
return this.exit(`Unable to add link to config file`, 1);
}
break;
case "Multipie":
let prevMultipieLink;
if (prevIntegrationLink) {
helper_1.LogHelper.info(`Found link for "${project.name}", enriching dialog with previous data`);
prevMultipieLink = prevIntegrationLink;
}
const multiPieInputLink = yield helper_1.QuestionHelper.askMultipieLink(project, prevMultipieLink);
const multipieAuth = this.authHelper.getAuthClient(multiPieInputLink);
try {
const authResponse = yield multipieAuth.owner.getToken(multiPieInputLink.username, multiPieInputLink.password);
helper_1.LogHelper.debug(`Got offline access refresh token`);
const offlineToken = {
projectName: multiPieInputLink.projectName,
linkType: multiPieInputLink.linkType,
endpoint: multiPieInputLink.endpoint,
clientSecret: multiPieInputLink.clientSecret,
refreshToken: authResponse.refreshToken,
};
try {
yield this.configHelper.addOrUpdateLink(offlineToken);
}
catch (err) {
helper_1.LogHelper.debug(`Unable to add link to config file`, err);
return this.exit(`Unable to add link to config file`, 1);
}
}
catch (err) {
helper_1.LogHelper.debug(`Unable to authenticate user`, err);
return this.exit(`Unable to authenticate user`, 1);
}
break;
default:
this.exit(`Integration "${integration}" not implemented`, 1);
break;
}
});
}
publishAction(options) {
return __awaiter(this, void 0, void 0, function* () {
const interactiveMode = process.argv.length === 3;
let projects = [];
try {
if (!interactiveMode) {
if (options.all) {
projects = yield this.projectHelper.getAllProjects();
}
else {
const project = yield this.projectHelper.getProjectByName(options.project);
if (project) {
projects = [project];
}
}
}
else {
const project = yield this.projectHelper.getOrAskForProjectFromGit();
if (project) {
projects = [project];
}
}
}
catch (err) {
return this.exit(err.message, 1);
}
if (projects.length < 1 || !projects) {
return this.exit("No valid git project", 1);
}
const projectIntegrationLinks = yield Promise.all(projects.map((project) => __awaiter(this, void 0, void 0, function* () {
const links = yield this.configHelper.findLinksByProject(project);
return {
name: project.name,
meta: project.meta,
records: project.records,
integrationLinks: links
};
})));
if (projectIntegrationLinks.length === 1 && projectIntegrationLinks[0].integrationLinks.length === 0) {
helper_1.LogHelper.warn(`Unable to find a link for "${projectIntegrationLinks[0].name}"`);
if (yield helper_1.QuestionHelper.confirmLinkCreation()) {
yield this.linkAction(options);
return yield this.publishAction(options);
}
else {
return this.exit(`Unable to publish without link`, 1);
}
}
else {
for (const project of projectIntegrationLinks) {
if (project.integrationLinks.length === 0) {
helper_1.LogHelper.warn(`Unable to find a link for "${project.name}"`);
}
}
}
const logs = yield this.gitHelper.logChanges();
if (logs.length > 0) {
if (yield helper_1.QuestionHelper.confirmPushLocalChanges()) {
yield this.gitHelper.pushChanges();
}
else {
return this.exit("Unable to publish with local changes", 1);
}
}
const publishSummary = [];
for (const project of projectIntegrationLinks) {
for (const link of project.integrationLinks) {
switch (link.linkType) {
case "Jira":
const jiraLink = link;
// Map local project to jira key
if (jiraLink.issue) {
helper_1.LogHelper.info(`Mapping "${project.name}" to Jira issue "${jiraLink.issue}" within project "${jiraLink.key}"`);
}
else {
helper_1.LogHelper.info(`Mapping "${project.name}" to Jira project "${jiraLink.key}"`);
}
if (!jiraLink.host) {
// Handle deprecated config
return this.exit('The configuration of this jira link is deprecated, please consider updating the link with "gittt link"', 1);
}
const jiraUrl = `${jiraLink.host}${jiraLink.endpoint}`;
helper_1.LogHelper.debug(`Publishing to ${jiraUrl}`);
try {
const publishResult = yield axios_1.default
.post(jiraUrl, {
projectKey: jiraLink.key,
issueKey: jiraLink.issue,
project,
}, {
headers: {
"Authorization": `Basic ${jiraLink.hash}`,
"Cache-Control": "no-cache",
"Content-Type": "application/json",
},
});
const data = publishResult.data;
if (data.success) {
publishSummary.push({
success: true,
type: link.linkType,
});
}
else {
publishSummary.push({
success: false,
type: link.linkType,
reason: `Publishing failed [${publishResult.status}]`
});
}
}
catch (err) {
delete err.config;
delete err.request;
delete err.response;
helper_1.LogHelper.debug("Publish request failed", err);
publishSummary.push({
success: false,
type: link.linkType,
reason: `Publish request failed, please consider updating the link`
});
}
break;
case "Multipie":
const multipieLink = link;
try {
let authorizationHeader = "";
if (multipieLink.username) {
// Legacy flow
helper_1.LogHelper.debug("Found username parameter in link configuration, using legacy auth method");
authorizationHeader = this.authHelper.getLegacyAuth(multipieLink);
}
else {
const multipieAuth = this.authHelper.getAuthClient(multipieLink);
const { refreshToken } = multipieLink;
if (!refreshToken) {
this.exit(`Unable to find refresh token for this project, please login via 'gittt link'`, 1);
return;
}
const offlineToken = yield multipieAuth.createToken("", refreshToken, {});
helper_1.LogHelper.debug(`Refreshing token to get access token`);
const refreshedToken = yield offlineToken.refresh();
helper_1.LogHelper.debug(`Got access token`);
authorizationHeader = `Bearer ${refreshedToken.accessToken}`;
}
const multipieUrl = `${multipieLink.endpoint}`;
helper_1.LogHelper.debug(`Publishing to ${multipieUrl}`);
const publishResult = yield axios_1.default
.post(multipieUrl, project, {
headers: {
"Authorization": authorizationHeader,
"Cache-Control": "no-cache",
"Content-Type": "application/json",
},
});
const data = publishResult.data;
if (data && (publishResult.status === 200 || publishResult.status === 201)) {
publishSummary.push({
success: true,
type: link.linkType,
});
}
else {
publishSummary.push({
success: false,
type: link.linkType,
reason: `Publishing failed [${publishResult.status}]`
});
}
}
catch (err) {
delete err.config;
delete err.request;
delete err.response;
helper_1.LogHelper.debug("Publish request failed", err);
publishSummary.push({
success: false,
type: link.linkType,
reason: `Publish request failed, please consider updating the link`
});
}
break;
default:
publishSummary.push({
success: false,
type: "unknown",
reason: `Link type "${link.linkType}" not implemented`
});
break;
}
}
}
for (const item of publishSummary) {
if (item.success) {
helper_1.LogHelper.info(`✓ Successfully published to ${item.type}`);
}
else {
helper_1.LogHelper.warn(`✗ Unable to publish to ${item.type}: ${item.reason}`);
}
}
if (publishSummary.filter(item => item.success === false).length > 0) {
this.exit(`One or more errors occurred while publishing data`, 1);
}
});
}
editAction(options, cmd) {
return __awaiter(this, void 0, void 0, function* () {
const interactiveMode = process.argv.length === 3;
let project;
// TODO move to own function, is used multiple times
try {
if (!interactiveMode) {
project = yield this.projectHelper.getProjectByName(options.project);
}
else {
project = yield this.projectHelper.getOrAskForProjectFromGit();
}
}
catch (err) {
return this.exit(err.message, 1);
}
if (!project) {
return this.exit("No valid git project", 1);
}
const projectWithRecords = yield this.fileHelper.findProjectByName(project.name);
if (!projectWithRecords) {
return this.exit(`Unable to find project "${project.name}"`, 1);
}
if (projectWithRecords.records.length === 0) {
return this.exit(`No records found for "${project.name}"`, 1);
}
const { records } = projectWithRecords;
let recordsToEdit;
let chosenRecord;
if (!interactiveMode) {
const recordGuid = options.guid;
const chosenRecords = records.filter((rc) => {
return rc.guid === recordGuid;
});
chosenRecord = chosenRecords[0];
if (!chosenRecord) {
return this.exit(`No records found for guid "${recordGuid}"`, 1);
}
}
else {
recordsToEdit = yield helper_1.RecordHelper.filterRecordsByYear(records);
recordsToEdit = yield helper_1.RecordHelper.filterRecordsByMonth(recordsToEdit);
recordsToEdit = yield helper_1.RecordHelper.filterRecordsByDay(recordsToEdit);
chosenRecord = yield helper_1.QuestionHelper.chooseRecord(recordsToEdit);
}
let updatedRecord = Object.assign({}, chosenRecord);
let year;
let month;
let day;
let hour;
let minute;
let amount;
let message;
if (!interactiveMode) {
if (options.type) {
updatedRecord.type = options.type;
}
else {
helper_1.LogHelper.error("No type option found");
return cmd.help();
}
if (!helper_1.ValidationHelper.validateNumber(options.amount)) {
helper_1.LogHelper.error("No amount option found");
return cmd.help();
}
if (!options.role && project.requiresRoles) {
helper_1.LogHelper.error("No role option found");
return cmd.help();
}
amount = parseFloat(options.amount);
year = helper_1.ValidationHelper.validateNumber(options.year)
? parseInt(options.year, 10) : (0, moment_1.default)().year();
month = helper_1.ValidationHelper.validateNumber(options.month, 1, 12)
? parseInt(options.month, 10) : (0, moment_1.default)().month() + 1;
day = helper_1.ValidationHelper.validateNumber(options.day, 1, 31)
? parseInt(options.day, 10) : (0, moment_1.default)().date();
hour = helper_1.ValidationHelper.validateNumber(options.hour, 0, 23)
? parseInt(options.hour, 10) : (0, moment_1.default)().hour();
minute = helper_1.ValidationHelper.validateNumber(options.minute, 0, 59)
? parseInt(options.minute, 10) : (0, moment_1.default)().minute();
message = (options.message && options.message.length > 0) ? options.message : undefined;
}
else {
updatedRecord.type = yield helper_1.QuestionHelper.chooseType(chosenRecord.type);
year = yield helper_1.QuestionHelper.askYear((0, moment_1.default)(chosenRecord.end).year());
month = yield helper_1.QuestionHelper.askMonth((0, moment_1.default)(chosenRecord.end).month() + 1);
day = yield helper_1.QuestionHelper.askDay((0, moment_1.default)(chosenRecord.end).date());
hour = yield helper_1.QuestionHelper.askHour((0, moment_1.default)(chosenRecord.end).hour());
minute = yield helper_1.QuestionHelper.askMinute((0, moment_1.default)(chosenRecord.end).minute());
amount = yield helper_1.QuestionHelper.askAmount(chosenRecord.amount);
message = yield helper_1.QuestionHelper.askMessage(chosenRecord.message);
}
updatedRecord.updated = Date.now();
updatedRecord.message = message;
updatedRecord.amount = amount;
updatedRecord = yield this.handleRole(cmd, interactiveMode, project, updatedRecord, options.role);
const modifiedMoment = (0, moment_1.default)().set({
date: day,
hour,
millisecond: 0,
minute,
month: month - 1,
second: 0,
year,
});
updatedRecord.end = modifiedMoment.unix() * 1000;
const updatedRecords = records.map((rc) => {
return rc.guid === updatedRecord.guid ? updatedRecord : rc;
});
const updatedProject = projectWithRecords;
updatedProject.records = updatedRecords;
yield this.fileHelper.saveProjectObject(updatedProject);
let changes = "";
if (updatedRecord.amount !== chosenRecord.amount) {
changes += `amount: ${updatedRecord.amount}, `;
}
if (updatedRecord.end !== chosenRecord.end) {
changes += `end: ${updatedRecord.end}, `;
}
if (updatedRecord.message !== chosenRecord.message) {
changes += `message: ${updatedRecord.message}, `;
}
if (updatedRecord.type !== chosenRecord.type) {
changes += `type: ${updatedRecord.type}, `;
}
if (updatedRecord.role !== chosenRecord.role) {
changes += `role: ${updatedRecord.role}, `;
}
if (changes.length > 0) {
changes = changes.slice(0, -2);
}
const commitMessage = changes.length > 0 ? `Updated record(${changes}) at ${updatedProject.name} ` : `Updated record at ${updatedProject.name} `;
yield this.gitHelper.commitChanges(commitMessage);
helper_1.LogHelper.info(commitMessage);
});
}
// TODO pretty much the same as editAction, refactor?
removeAction(options, cmd) {
return __awaiter(this, void 0, void 0, function* () {
const interactiveMode = process.argv.length === 3;
let project;
try {
try {
if (!interactiveMode) {
project = yield this.projectHelper.getProjectByName(options.project);
}
else {
project = yield this.projectHelper.getOrAskForProjectFromGit();
}
}
catch (err) {
return this.exit(err.message, 1);
}
}
catch (err) {
helper_1.LogHelper.debug("Unable to get project name from git folder", err);
return this.exit("Unable to get project name from git folder", 1);
}
if (!project) {
return this.exit("No valid git project", 1);
}
const projectWithRecords = yield this.fileHelper.findProjectByName(project.name);
if (!projectWithRecords) {
return this.exit(`Unable to find project "${project.name}"`, 1);
}
if (projectWithRecords.records.length === 0) {
return this.exit(`No records found for "${project.name}"`, 1);
}
const { records } = projectWithRecords;
let recordsToDelete;
let chosenRecord;
if (!interactiveMode) {
if (!options.guid) {
helper_1.LogHelper.error("No guid option found");
return cmd.help();
}
const recordGuid = options.guid;
const chosenRecords = records.filter((rc) => {
return rc.guid === recordGuid;
});
chosenRecord = chosenRecords[0];
if (!chosenRecord) {
return this.exit(`No records found for guid "${recordGuid}"`, 1);
}
}
else {
recordsToDelete = yield helper_1.RecordHelper.filterRecordsByYear(records);
recordsToDelete = yield helper_1.RecordHelper.filterRecordsByMonth(recordsToDelete);
recordsToDelete = yield helper_1.RecordHelper.filterRecordsByDay(recordsToDelete);
chosenRecord = yield helper_1.QuestionHelper.chooseRecord(recordsToDelete);
}
// TODO confirm deletion?
const updatedRecords = records.filter((rc) => {
return rc.guid !== chosenRecord.guid;
});
const updatedProject = projectWithRecords;
updatedProject.records = updatedRecords;
yield this.fileHelper.saveProjectObject(updatedProject);
const commitMessage = `Removed record ${chosenRecord.guid} from project ${updatedProject.name}`;
yield this.gitHelper.commitChanges(commitMessage);
helper_1.LogHelper.info(`Removed record (${(0, moment_1.default)(chosenRecord.end).format("DD.MM.YYYY, HH:mm:ss")}: ${chosenRecord.amount} ${chosenRecord.type} - "${lodash_1.default.truncate(chosenRecord.message)}") from project ${updatedProject.name}`);
});
}
commitAction(options, cmd) {
return __awaiter(this, void 0, void 0, function* () {
const interactiveMode = process.argv.length === 3;
let amount;
let message;
let commitMessage;
let project;
try {
if (!interactiveMode) {
amount = parseFloat(options.amount);
message = options.message;
project = yield this.projectHelper.getProjectByName(options.project);
}
else {
amount = yield helper_1.QuestionHelper.askAmount(1);
project = yield this.projectHelper.getOrAskForProjectFromGit();
message = yield helper_1.QuestionHelper.askMessage();
}
}
catch (err) {
return this.exit(err.message, 1);
}
if (isNaN(amount)) {
return this.exit("No valid amount", 1);
}
if (!project) {
return this.exit("No valid git project", 1);
}
if (message && message.length > 0) {
commitMessage = message;
}
else {
commitMessage = `Committed ${amount} hour${amount > 1 ? "s" : ""} to ${project.name}`;
}
commitMessage = yield (0, helper_1.appendTicketNumber)(commitMessage, yield this.gitHelper.getCurrentBranch());
try {
const data = {
amount,
end: Date.now(),
message: commitMessage,
type: types_1.RECORD_TYPES.Time,
};
const updatedData = yield this.handleRole(cmd, interactiveMode, project, data, options.role);
yield this.projectHelper.addRecordToProject(updatedData, project);
}
catch (err) {
helper_1.LogHelper.debug("Unable to add record to project", err);
this.exit("Unable to add record to project", 1);
}
});
}
addAction(options, cmd) {
return __awaiter(this, void 0, void 0, function* () {
const interactiveMode = process.argv.length === 3;
let year;
let month;
let day;
let hour;
let minute;
let amount;
let message;
let type;
let project;
try {
if (!interactiveMode) {
// TODO move to option of commander
if (!helper_1.ValidationHelper.validateNumber(options.amount)) {
helper_1.LogHelper.error("No amount option found");
return cmd.help();
}
if (!options.type) {
helper_1.LogHelper.error("No type option found");
return cmd.help();
}
amount = parseFloat(options.amount);
type = options.type;
year = helper_1.ValidationHelper.validateNumber(options.year)
? parseInt(options.year, 10) : (0, moment_1.default)().year();
month = helper_1.ValidationHelper.validateNumber(options.month, 1, 12)
? parseInt(options.month, 10) : (0, moment_1.default)().month() + 1;
day = helper_1.ValidationHelper.validateNumber(options.day, 1, 31)
? parseInt(options.day, 10) : (0, moment_1.default)().date();
hour = helper_1.ValidationHelper.validateNumber(options.hour, 0, 23)
? parseInt(options.hour, 10) : (0, moment_1.default)().hour();
minute = helper_1.ValidationHelper.validateNumber(options.minute, 0, 59)
? parseInt(options.minute, 10) : (0, moment_1.default)().minute();
message = (options.message && options.message.length > 0) ? options.message : undefined;
project = yield this.projectHelper.getProjectByName(options.project);
}
else {
project = yield this.projectHelper.getOrAskForProjectFromGit();
year = yield helper_1.QuestionHelper.askYear();
month = yield helper_1.QuestionHelper.askMonth();
day = yield helper_1.QuestionHelper.askDay();
hour = yield helper_1.QuestionHelper.askHour();
minute = yield helper_1.QuestionHelper.askMinute();
amount = yield helper_1.QuestionHelper.askAmount(1);
message = yield helper_1.QuestionHelper.askMessage();
type = yield helper_1.QuestionHelper.chooseType();
}
}
catch (err) {
return this.exit(err.message, 1);
}
const modifiedMoment = (0, moment_1.default)().set({
date: day,
hour,
millisecond: 0,
minute,
month: month - 1,
second: 0,
year,
});
const end = modifiedMoment.unix() * 1000;
let newRecord = {
amount,
end,
message: message ? message : undefined,
type,
};
newRecord = yield this.handleRole(cmd, interactiveMode, project, newRecord, options.role);
if (newRecord.message) {
newRecord.message = yield (0, helper_1.appendTicketNumber)(newRecord.message, yield this.gitHelper.getCurrentBranch());
}
try {
yield this.projectHelper.addRecordToProject(newRecord, project);
}
catch (err) {
helper_1.LogHelper.debug("Unable to add record to project", err);
this.exit("Unable to add record to project", 1);
}
});
}
importCsv(cmd, options) {
return __awaiter(this, void 0, void 0, function* () {
const interactiveMode = process.argv.length === 4;
const filePath = cmd;
if (!(0, lodash_1.isString)(filePath) || !helper_1.ValidationHelper.validateFile(filePath)) {
return this.exit("Unable to get csv file path", 1);
}
let project;
try {
if (!interactiveMode) {
project = yield this.projectHelper.getProjectByName(options.project);
}
else {
project = yield this.projectHelper.getOrAskForProjectFromGit();
}
}
catch (err) {
return this.exit(err.message, 1);
}
if (!project) {
return this.exit("No valid git project", 1);
}
try {
const records = yield this.importHelper.importCsv(filePath);
helper_1.LogHelper.debug(`Parsed ${records.length} records from ${filePath}`);
const uniqueRecords = lodash_1.default.uniqWith(records, lodash_1.default.isEqual);
helper_1.LogHelper.debug(`Filtered out ${records.length - uniqueRecords.length} duplicates`);
yield this.projectHelper.addRecordsToProject(uniqueRecords, project, true, false);
}
catch (err) {
helper_1.LogHelper.debug("Error importing records from csv", err);
this.exit(err.message, 1);
}
});
}
infoAction(options) {
return __awaiter(this, void 0, void 0, function* () {
const interactiveMode = process.argv.length === 3;
const order = types_1.ORDER_TYPE.indexOf(options.order) === -1 ? types_1.ORDER_TYPE[0] : options.order;
const direction = types_1.ORDER_DIRECTION.indexOf(options.direction) === -1 ? types_1.ORDER_DIRECTION[0] : options.direction;
let project;
try {
if (!interactiveMode) {
project = yield this.projectHelper.getProjectByName(options.project);
}
else {
project = yield this.projectHelper.getOrAskForProjectFromGit();
}
}
catch (err) {
return this.exit(err.message, 1);
}
const projects = yield this.fileHelper.findAllProjects();
// get current Gittt project
if (!project) {
return this.exit("No valid git project", 1);
}
else {
// check if the project is a gittt project
const foundProject = projects.filter((p) => project && p.name === project.name)[0];
if (foundProject) {
helper_1.LogHelper.info("");
helper_1.LogHelper.info(`Current project:`);
const hours = yield this.projectHelper.getTotalHours(foundProject.name);
helper_1.LogHelper.log(`Name:\t${foundProject.name}`);
helper_1.LogHelper.log(`Hours:\t${hours}h`);
const links = yield this.configHelper.findLinksByProject(project);
for (const link of links) {
switch (link.linkType) {
case "Jira":
const jiraLink = link;
helper_1.LogHelper.log("");
helper_1.LogHelper.log("Jira link:");
helper_1.LogHelper.log(`> Host:\t\t${jiraLink.host}`);
helper_1.LogHelper.log(`> Project:\t${jiraLink.key}`);
if (jiraLink.issue) {
helper_1.LogHelper.log(`> Issue:\t${jiraLink.issue}`);
}
break;
case "Multipie":
const multipieLink = link;
helper_1.LogHelper.log("");
helper_1.LogHelper.log("Multipie link:");
helper_1.LogHelper.log(`> Host:\t\t${multipieLink.endpoint}`);
helper_1.LogHelper.log(`> Project:\t${multipieLink.projectName}`);
break;
}
}
}
else {
helper_1.LogHelper.error("No gittt project in current git project.");
}
}
helper_1.LogHelper.info("");
helper_1.LogHelper.info(`Projects:`);
// add hours to projects
const projectsWithHours = yield Promise.all(projects.map((prj) => __awaiter(this, void 0, void 0, function* () {
return {
hours: yield this.projectHelper.getTotalHours(prj.name),
project: prj,
};
})));
// order projects
// sort mutates the array so we cannot save it to orderedProjects directly
projectsWithHours.sort((a, b) => {
if (order === "hours") {
if (direction === "desc") {
return (a.hours - b.hours) * -1;
}
return (a.hours - b.hours);
}
if (a.project.name < b.project.name) {
return (direction === "desc") ? 1 : -1;
}
if (a.project.name > b.project.name) {
return (direction === "desc") ? -1 : 1;
}
return 0;
});
const orderedProjects = projectsWithHours;
// print projects
for (const prj of orderedProjects) {
helper_1.LogHelper.log(`- ${prj.project.name}: ${prj.hours}h`);
}
});
}
listAction(options) {
return __awaiter(this, void 0, void 0, function* () {
const interactiveMode = process.argv.length === 3;
let project;
try {
if (!interactiveMode) {
project = yield this.projectHelper.getProjectByName(options.project);
}
else {
project = yield this.projectHelper.getOrAskForProjectFromGit();
}
}
catch (err) {
return this.exit(err.message, 1);
}
if (!project) {
return this.exit("No valid git project", 1);
}
if (project.records.length === 0) {
return this.exit(`No records found for "${project.name}"`, 1);
}
// sorting newest to latest
// because sort mutates the array we cannot assign it to records directly
project.records.sort((a, b) => {
const aStartTime = (0, moment_1.default)(a.end).subtract(a.amount, "hours");
const bStartTime = (0, moment_1.default)(b.end).subtract(b.amount, "hours");
return aStartTime.diff(bStartTime);
});
const records = project.records;
helper_1.LogHelper.info(`${project.name}`);
helper_1.LogHelper.print(`--------------------------------------------------------------------------------`);
helper_1.LogHelper.info(`TYPE\tAMOUNT\tTIME\t\t\tCOMMENT\t\t\t\tROLE`);
helper_1.LogHelper.print(`--------------------------------------------------------------------------------`);
let sumOfTime = 0;
for (const record of records) {
let line = "";
line += `${record.type}\t`;
line += chalk_1.default.yellow.bold(`${record.amount.toFixed(2)}h\t`);
line += `${(0, moment_1.default)(record.end).format("DD.MM.YYYY HH:mm:ss")}\t`;
line += chalk_1.default.yellow.bold(`${(0, helper_1.toFixedLength)(record.message, 24)}\t`);
line += chalk_1.default.yellow.bold(`${record.role}`);
sumOfTime += record.amount;
helper_1.LogHelper.print(line);
}
helper_1.LogHelper.print(`--------------------------------------------------------------------------------`);
helper_1.LogHelper.info(`SUM:\t${sumOfTime}h`);
});
}
todayAction() {
return __awaiter(this, void 0, void 0, function* () {
const projects = yield this.fileHelper.findAllProjects();
const todaysRecords = projects.flatMap(project => {
return project.records.filter(record => {
const momentEnd = (0, moment_1.default)(record.end);
const momentDayStart = (0, moment_1.default)().startOf('day');
const momentDayEnd = (0, moment_1.default)().endOf('day');
return momentEnd.isBetween(momentDayStart, momentDayEnd);
}).map(record => {
return {
record,
project
};
});
});
// because sort mutates the array we cannot assign it to sortedTodaysRecords directly
todaysRecords.sort((a, b) => {
return (0, moment_1.default)(a.record.end).diff((0, moment_1.default)(b.record.end));
});
const sortedTodaysRecords = todaysRecords;
helper_1.LogHelper.info(`${(0, moment_1.default)().format("dddd, MMMM D, YYYY")}`);
helper_1.LogHelper.print(`-------------------------------------------------------------------------------------------------------`);
helper_1.LogHelper.info(`TYPE\tAMOUNT\tTIME\t\tPROJECT\t\t\tCOMMENT\t\t\t\tROLE`);
helper_1.LogHelper.print(`------------------