pivotal-flow
Version:
🔀 A command-line tool that helps you manage & automate your workflow to use with PivotalTracker.
168 lines (167 loc) • 7.25 kB
JavaScript
;
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 });
const cli_table_1 = __importDefault(require("cli-table"));
const fuzzy_1 = require("fuzzy");
const git_1 = require("../../utils/git");
const common_1 = require("../../utils/pivotal/common");
const questions_1 = require("./questions");
const inquirer_1 = __importDefault(require("../../utils/inquirer"));
const string_1 = require("../../utils/string");
const utils_1 = require("../init/utils");
/**
* Parse the labels string into a list of labels
* @param {string} labels
* @example formatLabels('dx, 2.0, test, ') => ['dx', '2.0, 'test']
* @example formatLabels('') => []
*/
const parseLabels = (labelNames) => {
if (!labelNames)
return [];
return labelNames
.split(',')
.map(val => val.trim())
.filter(Boolean);
};
/**
* Get display-able labels as a string.
* @example formatLabels([{ name: 'one', ...}, { name: 'two', ... }]) => 'one, two'
*/
exports.formatLabels = (labels) => labels
.map(label => label.name)
.join(', ')
.trim();
exports.getNewStoryPayload = ({ ownerId, answers, }) => {
const { story_type, name, estimate, labelNames, description = '' } = answers;
return {
story_type,
name,
estimate,
labels: parseLabels(labelNames || ''),
description,
owner_ids: [ownerId],
// assuming the story is in the current iteration if you are working on it
current_state: "planned" /* Planned */,
};
};
exports.getStoryDetailsAsTable = (story) => {
const table = new cli_table_1.default({
colWidths: [15, 55],
head: ['', 'Story Details'],
colors: true,
style: {
head: ['green', 'bold'],
},
});
const { name, story_type, labels, estimate, url, description = null } = story;
const rows = [
{ Title: name },
{ Type: common_1.getStoryTypeLabel(story_type) },
{ Labels: exports.formatLabels(labels) },
typeof estimate === 'number' && { Estimate: estimate },
{ URL: url },
description && description.length && { Description: description },
].filter(Boolean);
table.push(...rows);
return table.toString();
};
exports.createNewStory = (client, ownerId) => __awaiter(void 0, void 0, void 0, function* () {
const answers = yield inquirer_1.default.prompt(questions_1.WorkOnNewStoryQuestions);
const storyPayload = exports.getNewStoryPayload({ ownerId, answers });
return client.createStory(storyPayload);
});
/**
* Gets the search query for Pivotal Advanced Search to fetch stories based on the parameters.
* @param assignedSelf Whether to search for stories assigned to the current user
* @param ownerId Current users id
*/
exports.getSearchStoryQuery = (assignedSelf, ownerId) => {
if (assignedSelf) {
return `mywork:"${ownerId}" AND state:unstarted,planned`;
}
// mywork query is much faster than owner:"" or no:owner and other options
// refer https://www.pivotaltracker.com/help/articles/advanced_search/
return `mywork:"" AND -owner:"${ownerId}" AND state:unstarted,planned`;
};
exports.getExistingStories = (client, owned, ownerId) => __awaiter(void 0, void 0, void 0, function* () {
const query = exports.getSearchStoryQuery(owned, ownerId);
const { stories: { stories }, } = yield client.getStories(query);
if (!(stories && stories.length)) {
throw new Error('No stories found. Please ensure that you have stories in your current iteration/backlog and try again.');
}
return stories;
});
exports.getWorkflow = ({ newStory }) => __awaiter(void 0, void 0, void 0, function* () {
if (newStory === true) {
return 0 /* New */;
}
const { selection } = yield inquirer_1.default.prompt(questions_1.PickStoryWorkflowQuestions);
return selection;
});
exports.selectProjectToCreateStory = () => __awaiter(void 0, void 0, void 0, function* () {
const config = yield utils_1.getPivotalFlowConfig();
if (config && config.projects && config.projects.length) {
const { projects, pivotalApiToken } = config;
// if there's a single project, let's just select it
if (projects.length === 1) {
const [project] = projects;
return { projectId: String(project.id), apiToken: pivotalApiToken };
}
const { selectedProject } = yield inquirer_1.default.prompt(questions_1.getPickProjectQuestions(projects));
return { projectId: String(selectedProject), apiToken: pivotalApiToken };
}
return { projectId: '', apiToken: '' };
});
exports.getStoryToWorkOn = (client, owner, workflow) => __awaiter(void 0, void 0, void 0, function* () {
const { id: ownerId } = owner;
if (workflow === 0 /* New */) {
return yield exports.createNewStory(client, ownerId);
}
const owned = workflow === 1 /* Owned */;
const stories = yield exports.getExistingStories(client, owned, ownerId);
const { story } = yield inquirer_1.default.prompt(questions_1.getSelectStoryFromListQuestions(stories));
return story;
});
/**
* Returns an autocomplete-source for searching through stories.
* @see https://github.com/mokkabonna/inquirer-autocomplete-prompt#example
*/
exports.getSearchableStoryListSource = (
/** Current story list to search from */
stories) => {
const choices = stories.map(story => {
const { story_type, name, id } = story;
return {
name: `[${id}] ${common_1.getStoryTypeIcon(story_type)}: ${string_1.truncate(name)}`,
value: story,
};
});
const source = (_, input = '') => __awaiter(void 0, void 0, void 0, function* () {
const results = fuzzy_1.filter(input, choices, {
extract: choice => choice.name,
});
return results.map(result => result.original);
});
return source;
};
exports.startWorkingOnStory = (story) => __awaiter(void 0, void 0, void 0, function* () {
const { checkoutBranch, branchName: branchNameInput } = yield inquirer_1.default.prompt(questions_1.getStartStoryQuestions(story));
const { story_type, id } = story;
if (checkoutBranch) {
const slugifiedBranchName = string_1.slugifyName(branchNameInput);
const branchName = common_1.getStoryBranchName(slugifiedBranchName, story_type, id);
git_1.checkoutNewBranch(branchName);
}
return;
});