Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The plan #1

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
# Red Labels for Pivotal Tracker
# Simbi Labels for Pivotal Tracker

This Chrome extension for SystemSeed workflow tweaks in Pivotal Tracker.
This Chrome extension for Simbi workflow tweaks in Pivotal Tracker.

## Taxonomy

Any of these labels on stories will turn blue:

- paused
- on hold
- blocked
- to <do-smth>
- planner
- estimate

Any of these labels on stories will turn red:
- urgent priority
- high priority
- blocked
- needs-<word>
- urgent

Any of these labels on stories will turn gray:
- goal-<word>
23 changes: 5 additions & 18 deletions css/content.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,18 @@
white-space: nowrap;
}

.preview a.label.ss.workflow-state {
.preview a.label.ss.blue {
background: #203e64;
}

.preview a.label.ss.workflow-state:hover {
.preview a.label.ss.blue:hover {
background: #11213e;
}

.preview a.label.ss.important {
.preview a.label.ss.red {
background-color: hsl(349, 69%, 46%);
}

.preview a.label.ss.missing-tags {
background-color: #f39300;
}

.preview .time-overrun {
background: #a71f39;
color: white;
font-size: 8px;
padding: 0 5px;
border-radius: 20px;
position: absolute;
top: 25px;
line-height: 11px;
left: 22px;
font-weight: bold;
.preview a.label.ss.gray {
background-color: #b7b7b7;
}
12 changes: 6 additions & 6 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "SystemSeed PT",
"version": "1.7",
"name": "Simbi PT",
"version": "1.0",
"manifest_version": 2,
"author": "Ev Maslovskiy",
"description": "Provides integration between SystemSeed workflow and Pivotal Tracker.",
"homepage_url": "https://github.com/spleshka/SystemSeed-PT",
"author": "Carlos Arguello",
"description": "Provides integration between Simbi's workflow and Pivotal Tracker.",
"homepage_url": "https://github.com/cjarguello/simbi-labels-for-pivotal-tracker",
"icons": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
Expand All @@ -15,7 +15,7 @@
"*://www.pivotaltracker.com/*"
],
"browser_action": {
"default_title": "SystemSeed PT",
"default_title": "Simbi PT",
"default_icon": "icons/icon16.png"
},
"content_scripts": [
Expand Down
144 changes: 22 additions & 122 deletions src/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,28 @@ chrome.extension.sendMessage({}, function(response) {
* Add observer handler.
*/
observer.observe(document, config);

var handleMutationEvents = function handleMutationEvents(mutation) {
Array.prototype.forEach.call(mutation.addedNodes, processStories);
processStories(mutation.target);
};


/**
* Main entry point to loop through all stories and to process them.
*/
var processStories = function processStories(node) {
if (typeof node.querySelectorAll == 'undefined') {
if (typeof node.querySelectorAll == 'undefined')
return;
}

// Loop through every collapsed story.
var collapsedStories = node.querySelectorAll('header.preview');

if (collapsedStories.length) {
Array.prototype.forEach.call(collapsedStories, function (story) {
var storySelector = story.parentNode;
highlightWorkflowStates(storySelector);
highlightPlannerTags(storySelector);
highlightImportantTags(storySelector);
detectMergeDeployTags(storySelector);
detectTimeIssues(storySelector);
highlightGoalTags(storySelector);
});
}

Expand All @@ -51,14 +50,13 @@ chrome.extension.sendMessage({}, function(response) {


/**
* Highlights tags which contain workflow states.
* Highlights planner stories that need estimate.
*/
function highlightWorkflowStates(story) {
function highlightPlannerTags(story) {
var labels = story.querySelectorAll('a.label');
Array.prototype.forEach.call(labels, function(label) {
if (label.textContent.match(/\b(?:on hold|questions|awaiting|^to)\b/)) {
label.classList.add('ss', 'workflow-state');
}
if (label.textContent.match(/\b(?:planner|estimate)\b/))
label.classList.add('ss', 'blue');
});
}

Expand All @@ -74,126 +72,28 @@ chrome.extension.sendMessage({}, function(response) {
if (!storyAccepted) {
var labels = story.querySelectorAll('a.label');
Array.prototype.forEach.call(labels, function(label) {
if (label.textContent.match(/\b(?:blocked|hot lead|blocker|high priority|urgent priority)\b/)) {
label.classList.add('ss', 'important');
}
if (label.textContent.match(/\b(?:blocked|needs|needs-|urgent)\b/))
label.classList.add('ss', 'red');
});
}
}


/**
* Finds stories where logged time higher than estimate.
* Highlights tags which contain goal-based labels.
* productivity, analytics, etc.
*/
function detectTimeIssues(story) {
var estimation = story.querySelector('.meta');
var spentTime = story.querySelector('.everhour-stat');
if (estimation !== null && spentTime !== null) {
var pts = estimation.textContent;
if (pts >= 0) {
var time = spentTime.textContent.split(' ');
var minutes = 0;
var hours = 0;

// If time has only 1 param, means that it has only minutes.
// Otherwise - hours and minutes.
if (time.length == 1) {
minutes = parseInt(time[0]);
}
else {
hours = parseInt(time[0]);
minutes = parseInt(time[1]);
}

// Care only about huge overruns.
var estimated = pts * 6 * 60 + 60;
var current = hours * 60 + minutes;
if (estimated < current) {
if (story.querySelector('.time-overrun') === null) {
story.insertAdjacentHTML('beforeend', '<span class="time-overrun">overrun</span>');
}
}
}
}
}
function highlightGoalTags(story) {


/**
* Checks for merging / deployment workflow.
*/
function detectMergeDeployTags(story) {
// We care about merging only when story is accepted.
// We care about important tags as long as it is not accepted.
var storyAccepted = story.classList.contains('accepted');
var storyHasBody = story.querySelector('.name') !== null;
if (!storyAccepted || !storyHasBody) {
return;
}

// Make sure that the story has the right section and we haven't
// already processed it.
var bodySection = story.querySelector('.name');
var hasMissingTags = bodySection.querySelector('.missing-tags') !== null;
if (hasMissingTags) {
return;
}

var hasBranch = false;
var hasToMergeTag = false;
var hasMergedTag = false;
var hasToDeployTag = false;
var hasDeployedTag = false;

// Loop through all labels of this story.
var labels = story.querySelectorAll('a.label');
Array.prototype.forEach.call(labels, function (label) {

// Check if the label is in format "b:branchname".
var regexp = new RegExp('^b:', 'i');
if (label.textContent.match(regexp)) {
hasBranch = true;
}

// Check if the label is "to merge".
regexp = new RegExp('^(to merge)', 'i');
if (label.textContent.match(regexp)) {
hasToMergeTag = true;
}

// Check if the label is "merged".
regexp = new RegExp('^(merged)', 'i');
if (label.textContent.match(regexp)) {
hasMergedTag = true;
}

// Check if the label is "to deploy".
regexp = new RegExp('^(to deploy)', 'i');
if (label.textContent.match(regexp)) {
hasToDeployTag = true;
}

// Check if the label is "deployed".
regexp = new RegExp('^(deployed)', 'i');
if (label.textContent.match(regexp)) {
hasDeployedTag = true;
}
});

var warnings = [];

if (hasBranch) {
if (!hasToMergeTag && !hasMergedTag) {
warnings.push('merge');
}

if (!hasToDeployTag && !hasDeployedTag) {
warnings.push('deploy');
}
}

if (warnings.length) {
bodySection.insertAdjacentHTML('beforeend', '<a class="std label ss missing-tags">notice: missing ' + warnings.join(', ') + ' tags</a>');
if (!storyAccepted) {
var labels = story.querySelectorAll('a.label');
Array.prototype.forEach.call(labels, function(label) {
if (label.textContent.match(/goal-b/))
label.classList.add('ss', 'gray');
})
}
}
}
}, 10);
}, 100);
});
12 changes: 1 addition & 11 deletions src/inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,8 @@ function injectTemplateButton(node) {
function generateTasks(e) {

var tasks = [
'**===== TECHNICAL CONCEPT =====**',
'**===== CHECKLIST FOR ACCEPTANCE =====**',
'- TDB',
'**===== DEFINITION OF DONE =====**',
'- Acceptance criteria is confirmed by the client',
'- Story meets acceptance criteria',
'- Story includes post deploy steps (where applicable)',
'- Story includes testing steps',
'- Code passes automated tests / code checks (where applicable)',
'- Story is tested and peer reviewed (where applicable)',
'- Story has test coverage (where applicable)',
'- Story is documented',
'- Story is accepted by the client',
'**===== TESTING STEPS =====**',
'- **Test 1:** TBD'
];
Expand Down