Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/.project
.idea/
/**/node_modules
.clasp.json
1 change: 1 addition & 0 deletions projects/gapps-jira-backlog-import/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# gapps-jira-backlog-import
17 changes: 17 additions & 0 deletions projects/gapps-jira-backlog-import/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "gapps-jira-backlog-import",
"version": "0.1.0",
"scripts": {
"build": "echo \"Not yet implemented\"",
"test": "echo \"Not yet implemented\"",
"test:e2e": "echo \"Not yet implemented\"",
"deploy:pr": "echo \"Not yet implemented\"",
"deploy:develop": "echo \"Not yet implemented\"",
"deploy:staging": "echo \"Not yet implemented\"",
"deploy:production": "echo \"Not yet implemented\""
},
"devDependencies": {
},
"dependencies": {
}
}
123 changes: 123 additions & 0 deletions projects/gapps-jira-backlog-import/src/Code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('Sprint Tracking')
.addItem('Import Jira Tickets', 'importJiraTickets')
.addToUi();
}

// Function to create Jira data
function createJiraData() {
// Access script properties to get Jira credentials and base URL
const scriptProperties = PropertiesService.getScriptProperties();
const baseUrl = scriptProperties.getProperty('JIRA_BASE_URL');
const username = scriptProperties.getProperty('JIRA_USERNAME');
const apiToken = scriptProperties.getProperty('JIRA_API_TOKEN');
const boardId = scriptProperties.getProperty('BOARD_ID');

// Ensure all required properties are available
if (!baseUrl || !username || !apiToken) {
throw new Error("Missing one or more Jira API credentials in script properties.");
}

// Create an instance of the JiraAPI class
const jira = new JiraAPI(baseUrl, username, apiToken);

// Define the JQL query to get the tickets for the current sprint
let sprintId;

try {
const currentSprint = jira.fetchCurrentSprint(boardId);
sprintId = currentSprint.id;
Logger.log(`Current Sprint: ${currentSprint.name} (ID: ${currentSprint.id})`);
} catch (error) {
throw new Error(`Error fetching the current sprint: ${error.message}`);
}

// Fetch issues using a JQL query
const jqlQuery = `sprint = ${sprintId} ORDER BY rank ASC`; // Replace 'rank' with your field ID if needed
let issues;

try {
issues = jira.fetchIssuesByJQL(jqlQuery);
} catch (error) {
throw new Error(`Error fetching issues by JQL: ${error.message}`);
}

// Create an array to hold the Jira data
const jiraData = [
["Ticket ID", "Summary", "Status", "Assignee", "Due Date", "Story Points", "Balance"] // Header row
];

// Populate the array with issue data
let runningTotal = 0;
issues.forEach(issue => {
const storyPoints = issue.fields.customfield_10004 || 0; // Replace 'customfield_10002' with your Story Points field ID
runningTotal += storyPoints;

jiraData.push([
issue.key,
issue.fields.summary,
issue.fields.status.name,
issue.fields.assignee ? issue.fields.assignee.displayName : "",
issue.fields.duedate || "",
storyPoints,
runningTotal
]);
});

return jiraData;
}

function formatSprintSheet(sprintSheet, headerLength) {
// Apply styles to the column headers
const headerRange = sprintSheet.getRange(1, 1, 1, headerLength);
headerRange.setFontWeight("bold");
headerRange.setBackground("#4CAF50"); // Green background
headerRange.setFontColor("white"); // White font for better contrast

// Adjust the width of the "Summary" column
const summaryColumnIndex = 2; // "Summary" is in the second column
sprintSheet.setColumnWidth(summaryColumnIndex, 300); // Set width as needed

// Apply subdued styling for rows with "Status" = "Done"
const lastRow = sprintSheet.getLastRow();
const statusColumnIndex = 3; // "Status" is in the third column

if (lastRow > 1) { // Check if there are any rows beyond the header
const statusRange = sprintSheet.getRange(2, statusColumnIndex, lastRow - 1, 1); // Status column, excluding the header
const statuses = statusRange.getValues();

for (let i = 0; i < statuses.length; i++) {
if (statuses[i][0] === "Done") {
const row = i + 2; // Row index starts from 2 (accounting for header row)
const rowRange = sprintSheet.getRange(row, 1, 1, headerLength);
rowRange.setFontColor("#808080"); // Gray font for subdued effect
rowRange.setBackground("#F0F0F0"); // Light gray background
}
}
}
}


function importJiraTickets() {
// Get the active spreadsheet
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();

// Find the sheet named "Sprint" or create it if it doesn't exist
let sprintSheet = spreadsheet.getSheetByName("Sprint");
if (!sprintSheet) {
sprintSheet = spreadsheet.insertSheet("Sprint");
}

// Clear the contents of the "Sprint" sheet
sprintSheet.clear();

// Add new content (placeholder for Jira ticket data)
const jiraData = createJiraData();

sprintSheet.getRange(1, 1, jiraData.length, jiraData[0].length).setValues(jiraData);

// Format the Sprint sheet
formatSprintSheet(sprintSheet, jiraData[0].length);
}

117 changes: 117 additions & 0 deletions projects/gapps-jira-backlog-import/src/JiraAPI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
class JiraAPI {
constructor(baseUrl, username, apiToken) {
this.baseUrl = baseUrl;
this.auth = Utilities.base64Encode(`${username}:${apiToken}`);
}

// Method to fetch the current sprint for a given project
fetchCurrentSprint(boardId) {
const endpoint = `${this.baseUrl}/rest/agile/1.0/board/${boardId}/sprint?state=active`;
const options = {
method: 'GET',
headers: {
'Authorization': `Basic ${this.auth}`,
'Content-Type': 'application/json'
},
muteHttpExceptions: true
};

const response = UrlFetchApp.fetch(endpoint, options);

if (response.getResponseCode() !== 200) {
throw new Error(`Failed to fetch current sprint. HTTP Status Code: ${response.getResponseCode()}`);
}

const data = JSON.parse(response.getContentText());
const sprints = data.values || [];

// Return the first active sprint if available
if (sprints.length === 0) {
throw new Error("No active sprint found for the specified board.");
}

return sprints[0]; // Assuming there's only one active sprint
}

// Method to fetch sprint tickets
fetchSprintTickets(sprintId) {
const endpoint = `${this.baseUrl}/rest/agile/1.0/sprint/${sprintId}/issue`;
const options = {
method: 'GET',
headers: {
'Authorization': `Basic ${this.auth}`,
'Content-Type': 'application/json'
},
muteHttpExceptions: true
};

const response = UrlFetchApp.fetch(endpoint, options);

if (response.getResponseCode() !== 200) {
throw new Error(`Failed to fetch sprint tickets. HTTP Status Code: ${response.getResponseCode()}`);
}

const data = JSON.parse(response.getContentText());
const issues = data.issues || [];
return issues;
}

// New method: Fetch issues by JQL query
fetchIssuesByJQL(jqlQuery) {
const endpoint = `${this.baseUrl}/rest/api/2/search?jql=${encodeURIComponent(jqlQuery)}`;
const options = {
method: 'GET',
headers: {
'Authorization': `Basic ${this.auth}`,
'Content-Type': 'application/json'
},
muteHttpExceptions: true
};

const response = UrlFetchApp.fetch(endpoint, options);

if (response.getResponseCode() !== 200) {
throw new Error(`Failed to fetch issues by JQL. HTTP Status Code: ${response.getResponseCode()}`);
}

const data = JSON.parse(response.getContentText());
return data.issues || []; // Return the list of issues
}
}

function jira_api_test() {
// Access script properties
const scriptProperties = PropertiesService.getScriptProperties();

// Retrieve the necessary values
const baseUrl = scriptProperties.getProperty('JIRA_BASE_URL');
const username = scriptProperties.getProperty('JIRA_USERNAME');
const apiToken = scriptProperties.getProperty('JIRA_API_TOKEN');

// Ensure all properties are set
if (!baseUrl || !username || !apiToken) {
Logger.log("Missing one or more Jira API credentials in script properties.");
return;
}

// Create an instance of the JiraAPI class
const jira = new JiraAPI(baseUrl, username, apiToken);

// Test the instance (e.g., by fetching the current sprint for a test board ID)
try {
const boardId = 317; // Replace with an appropriate test board ID
const currentSprint = jira.fetchCurrentSprint(boardId);

Logger.log(`Current Sprint: ${currentSprint.name} (ID: ${currentSprint.id})`);

const sprintBacklog = jira.fetchSprintTickets(currentSprint.id);
Logger.log(`Tickets in current sprint: ${sprintBacklog.length}`);

const issuesByRankQuery = `sprint = ${currentSprint.id} ORDER BY Rank ASC`;
const issuesByRank = jira.fetchIssuesByJQL(issuesByRankQuery);
Logger.log(`Tickets in backlog: ${issuesByRank.map(issue => issue.key)}`);
} catch (error) {
Logger.log(`Error during Jira API test: ${error.message}`);
}
}

7 changes: 7 additions & 0 deletions projects/gapps-jira-backlog-import/src/appsscript.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"timeZone": "America/Chicago",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8"
}