Skip to content
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
Binary file added assets/icon16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icon48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion css/options.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
.form-textarea {
font-size: 1rem;
width: 100%;
height: 10rem;
height: 7rem;
}

.btn {
Expand Down
59 changes: 59 additions & 0 deletions css/popup.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.popup-wrapper {
margin: 0;
overflow: hidden;
min-width: 150px;
}

.popup {
margin: 0;
overflow: hidden;
min-width: 100px;
}

.btn {
font-family: Helvetica, Arial, sans-serif;
font-size: 0.85rem;
padding: 0.5rem 1rem;
width: 100%;
height: 50%;
border: 0.07rem solid black;
}

.btn:hover {
background-color: lightskyblue;
}

.hide {
display: none;
}

.show {
display: block;
}

.work-log-modal {
width: 400px;
}
.input-title {
font-size: 1rem;
}

.input-text {
font-size: 1rem;
margin-top: 0.5rem;
height: 2rem;
width: 100%;
}

.track-button {
font-family: Helvetica, Arial, sans-serif;
font-size: 0.85rem;
padding: 0.5rem 1rem;
width: 100%;
border: 0.07rem solid black;
width: 100%;
}

.track-button:hover {
background-color: lightskyblue;
}
196 changes: 122 additions & 74 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,90 +1,138 @@
import {MetadataReader} from './lib/MetadataReader';
import {PageController} from './lib/PageController';
import {SimpleTemplateDriver} from './lib/SimpleTemplateDriver';
import {JiraApiClient} from './lib/JiraApiClient';
import {ChromePluginConfig} from './lib/ChromePluginConfig';
import { MetadataReader } from "./lib/MetadataReader";
import { PageController } from "./lib/PageController";
import { SimpleTemplateDriver } from "./lib/SimpleTemplateDriver";
import { JiraApiClient } from "./lib/JiraApiClient";
import { ChromePluginConfig } from "./lib/ChromePluginConfig";
import updateJiraBoard from "./updateJiraBoard";
import workLog from "./workLog";
import "./css/normalize.css";
import "./css/popup.css";

chrome.browserAction.onClicked.addListener(() => {
function generateTemplate() {
chrome.tabs.getSelected(null, function (tab) {
const controller = new PageController(chrome.tabs, tab.id);
const reader = new MetadataReader(controller);
const templateDriver = new SimpleTemplateDriver();
const pluginConfig = new ChromePluginConfig(chrome.storage);

pluginConfig.load().then(() => reader.collect({
reviewers: {
strategy: 'dom-query',
selector: '.js-issue-sidebar-form .css-truncate',
mapper: function (element) {
return Array.from(element.children)
.filter(reviewerParagraph => Boolean(reviewerParagraph.querySelector('.octicon-check')))
.map(reviewerParagraph => reviewerParagraph.innerText.trim());
}
},
jiraTicket: {
strategy: 'dom-query',
selector: 'a[href*="atlassian.net/"]',
mapper: e => e.innerHTML.trim(),
},
prNumber: {
strategy: 'js-eval',
code: document => document.location.pathname.split("/").pop(),
},
mergeTitle: {
strategy: 'dom-query',
selector: '#merge_title_field',
mapper: e => e.value,
},
hasToUpdateJiraTicket: {
strategy: 'js-eval',
code: document =>
document.querySelector('a[href*="atlassian.net/"]') && confirm('Do you want to update jira ticket ?'),
}
})).then(data => {
const userAliases = pluginConfig.get('userAliases');
pluginConfig
.load()
.then(() =>
reader.collect({
reviewers: {
strategy: "dom-query",
selector: ".js-issue-sidebar-form .css-truncate",
mapper: function (element) {
return Array.from(element.children)
.filter((reviewerParagraph) =>
Boolean(reviewerParagraph.querySelector(".octicon-check"))
)
.map(
(reviewerParagraph) =>
console.log({ reviewerParagraph }) ||
reviewerParagraph.innerText.trim().split("\n")[0]
);
},
},
jiraTicket: {
strategy: "dom-query",
selector: `a[href*="${pluginConfig.get("jiraBase")}/"]`,
mapper: (e) => e.innerHTML.trim(),
},
prNumber: {
strategy: "js-eval",
code: (document) => document.location.pathname.split("/").pop(),
},
mergeTitle: {
strategy: "dom-query",
selector: "#merge_title_field",
mapper: (e) => e.value,
},
hasToUpdateJiraTicket: {
strategy: "js-eval",
code: (document) =>
document.querySelector(
`a[href*=${new JiraApiClient(pluginConfig.get("jiraBase"))}]`
) && confirm("Do you want to update jira ticket ?"),
},
})
)
.then((data) => {
const userAliases = pluginConfig.get("userAliases");

data.reviewers = data.reviewers.map(reviewer => {
return userAliases[reviewer] || reviewer.toLowerCase();
});
data.reviewers = data.reviewers.map((reviewer) => {
console.log("---- reviewer", reviewer);

const {jiraTicket, hasToUpdateJiraTicket} = data;
const jiraApi = new JiraApiClient(pluginConfig.get('jiraBase'));
const boardColumnName = pluginConfig.get('boardColumn');
const commitMessage = templateDriver.renderToString(pluginConfig.get('template'), data);
const mergeTitle = templateDriver.renderToString('{{ title | clear }}', {
title: data.mergeTitle,
});
return userAliases[reviewer] || reviewer.toLowerCase();
});

const { jiraTicket, hasToUpdateJiraTicket } = data;
const jiraApi = new JiraApiClient(pluginConfig.get("jiraBase"));
const boardColumnName = pluginConfig.get("boardColumn");
const commitMessage = templateDriver.renderToString(
pluginConfig.get("template"),
data
);
const mergeTitle = templateDriver.renderToString(
"{{ title | clear }}",
{
title: data.mergeTitle,
}
);

const updateJiraTicket = jiraTicket && hasToUpdateJiraTicket
? jiraApi
.getTransitions(jiraTicket)
.catch(e => {
throw new Error(`${e.message}. Please ensure "${pluginConfig.get('jiraBase')}" is accessible.`);
})
.then(response => {
const transitions = response.data.transitions;
const toDevCompleteTransition = transitions.find(t => new RegExp(boardColumnName, 'i').test(t.name));
const updateJiraTicket =
jiraTicket && hasToUpdateJiraTicket
? jiraApi
.getTransitions(jiraTicket)
.catch((e) => {
throw new Error(
`${e.message}. Please ensure "${pluginConfig.get(
"jiraBase"
)}" is accessible.`
);
})
.then((response) => {
const transitions = response.data.transitions;
const toDevCompleteTransition = transitions.find((t) =>
new RegExp(boardColumnName, "i").test(t.name)
);

if (!toDevCompleteTransition) {
const tNames = transitions.map(t => t.name).join(', ');
if (!toDevCompleteTransition) {
const tNames = transitions.map((t) => t.name).join(", ");

throw new Error(`Couldn't find column matching "${boardColumnName}". Available columns: ${tNames}.`);
}
throw new Error(
`Couldn't find column matching "${boardColumnName}". Available columns: ${tNames}.`
);
}

const {id, name} = toDevCompleteTransition;
const { id, name } = toDevCompleteTransition;

return jiraApi
.postTransition(jiraTicket, {transition: {id}})
.then(() => controller.alert(`Ticket ${jiraTicket} successfully moved to ${name}`));
})
.catch(e => controller.alert(`Error moving jira ticket: ${e.message}`))
: Promise.resolve();
return jiraApi
.postTransition(jiraTicket, { transition: { id } })
.then(() =>
controller.alert(
`Ticket ${jiraTicket} successfully moved to ${name}`
)
);
})
.catch((e) =>
controller.alert(`Error moving jira ticket: ${e.message}`)
)
: Promise.resolve();

return Promise.all([
controller.updateInputValue('#merge_message_field', commitMessage),
controller.updateInputValue('#merge_title_field', mergeTitle),
updateJiraTicket,
]);
});
return Promise.all([
controller.updateInputValue("#merge_message_field", commitMessage),
controller.updateInputValue("#merge_title_field", mergeTitle),
updateJiraTicket,
]);
});
});
});
}

document
.getElementById("geenerate_template")
.addEventListener("click", generateTemplate);
document
.getElementById("update_jira_board")
.addEventListener("click", updateJiraBoard);
document.getElementById("work_log").addEventListener("click", workLog);
5 changes: 5 additions & 0 deletions lib/ChromePluginConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export const defaultOptions = {
userAliases: {
userName: 'first.lastName',
},
labelTransitions: {
hold: 'Open',
wip: 'In Progress',
default: 'Review',
}
};

export class ChromePluginConfig {
Expand Down
26 changes: 20 additions & 6 deletions lib/JiraApiClient.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import axios from 'axios';
import axios from "axios";

const ENDPOINTS = {
TRANSACTIONS: '/rest/api/2/issue/:ticketId/transitions',
TRANSACTIONS: "/rest/api/2/issue/:ticketId/transitions",
WORKLOG: "/rest/api/2/issue/:ticketId/worklog"
};

export class JiraApiClient {
Expand All @@ -10,7 +11,7 @@ export class JiraApiClient {
*/
constructor(apiBase) {
this.client = axios.create({
baseURL: apiBase,
baseURL: apiBase
});
}

Expand All @@ -19,7 +20,7 @@ export class JiraApiClient {
* @returns {Promise}
*/
getTransitions(ticketId) {
const url = this.buildUrl(ENDPOINTS.TRANSACTIONS, {ticketId});
const url = this.buildUrl(ENDPOINTS.TRANSACTIONS, { ticketId });

return this.client.get(url);
}
Expand All @@ -30,7 +31,18 @@ export class JiraApiClient {
* @returns {Promise}
*/
postTransition(ticketId, params) {
const url = this.buildUrl(ENDPOINTS.TRANSACTIONS, {ticketId});
const url = this.buildUrl(ENDPOINTS.TRANSACTIONS, { ticketId });

return this.client.post(url, params);
}

/**
* @param {String} ticketId
* @param {Object} params
* @returns {Promise}
*/
postAddTime(ticketId, params) {
const url = this.buildUrl(ENDPOINTS.WORKLOG, { ticketId });

return this.client.post(url, params);
}
Expand All @@ -43,7 +55,9 @@ export class JiraApiClient {
buildUrl(resourceUrlTpl, params) {
return resourceUrlTpl.replace(/:([^\/]+)/g, (match, paramName) => {
if (!params.hasOwnProperty(paramName)) {
throw new Error(`Missing "${paramName}" parameter. Cannot build "${resourceUrlTpl}" resource"`);
throw new Error(
`Missing "${paramName}" parameter. Cannot build "${resourceUrlTpl}" resource"`
);
}

const paramValue = params[paramName];
Expand Down
10 changes: 6 additions & 4 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
"name": "Merge assistant",
"description": "This extension creates merge commit for you",
"version": "1.0",
"background": {
"page": "index.html"
"browser_action": {
"default_title": "Git Merger",
"default_icon": "icon128.png",
"default_popup": "popup.html"
},
"browser_action": {},
"options_page": "options.html",
"permissions": [
"tabs",
"activeTab",
"background",
"storage",
"https://*.atlassian.net/*"
"https://*.atlassian.net/*",
"https://jira.tenkasu.net/*"
],
"icons": {
"16": "icon128.png",
Expand Down
Loading