@bs-plugins/jira
Version:
Bamboo Shell plugin for JIRA
527 lines (524 loc) • 19.1 kB
JavaScript
import { BSPlugin, bs } from '@bs-core/shell';
// imports here
// Config consts here
// Misc constants here
const JiraResources = {
session: "/rest/auth/1/session",
field: "/rest/api/2/field",
project: "/rest/api/2/project",
issue: "/rest/api/2/issue",
createmeta: "/rest/api/2/issue/createmeta",
components: "/rest/api/2/project",
search: "/rest/api/2/search",
user: "/rest/api/2/user",
group: "/rest/api/2/group",
};
const SCRIPTRUNNER_DASHBOARDS_N_FILTERS_URL = "rest/scriptrunner/latest/canned/com.onresolve.scriptrunner.canned.jira.admin.ChangeSharedEntityOwnership";
// Jira class here
class Jira extends BSPlugin {
// Properties here
_server;
_user;
_password;
_sessionId;
_sessionRefreshPeriod;
_timeout;
_fieldDict;
_sessionHeader; // Used if logged in
_basicAuthHeader; // Used if not logged in
constructor(name, jiraConfig) {
super(name,
// NOTE: 1.2.32 is replaced with package.json#version by a
// rollup plugin at build time
"1.2.32");
let config = {
sessionRefreshPeriod: 60,
...jiraConfig,
};
this._server = config.server;
this._user = config.user;
this._password = config.password;
this._sessionId = null;
this._fieldDict = null;
this._sessionRefreshPeriod = config.sessionRefreshPeriod * 60 * 1000; // Convert to ms
this._sessionHeader = {};
let token = Buffer.from(`${this._user}:${this._password}`).toString("base64");
this._basicAuthHeader = { Authorization: `Basic ${token}` };
}
// Private methods here
// Public methods here
async login(auth) {
let res = await bs.request(this._server, JiraResources.session, {
method: "POST",
body: {
username: auth !== undefined ? auth.username : this._user,
password: auth !== undefined ? auth.password : this._password,
},
});
let session = res.body;
this._sessionId = session.session.value;
this._sessionHeader = { cookie: `JSESSIONID=${this._sessionId}` };
// Start a timer to automatically renew the session ID
this._timeout = setTimeout(() => {
this.info("Refreshing session ID!");
this.login();
}, this._sessionRefreshPeriod);
}
async logout() {
if (this._sessionId === null) {
return;
}
// Stop the timer first!
clearInterval(this._timeout);
await bs.request(this._server, JiraResources.session, {
method: "DELETE",
headers: {
cookie: `JSESSIONID=${this._sessionId}`,
},
});
// Reset the session ID so we know we are not logged in
this._sessionId = null;
this._sessionHeader = {};
}
async getFieldDict(useCurrent = true) {
// Check to see if the field dict is populated AND the user
// wants to use the current field dict
if (this._fieldDict !== null && useCurrent) {
return this._fieldDict;
}
let res = await bs.request(this._server, JiraResources.field, {
method: "GET",
headers: this._sessionId === null ? this._basicAuthHeader : this._sessionHeader,
});
this._fieldDict = { byId: {}, byName: {} };
if (Array.isArray(res.body)) {
for (let field of res.body) {
this._fieldDict.byName[field.name] = {
id: field.id,
type: field.schema !== undefined ? field.schema.type : "Unknown",
itemType: field.schema !== undefined ? field.schema.items : "Unknown",
};
this._fieldDict.byId[field.id] = {
name: field.name,
type: field.schema !== undefined ? field.schema.type : "Unknown",
itemType: field.schema !== undefined ? field.schema.items : "Unknown",
};
}
}
return this._fieldDict;
}
async getAllowedFieldValues(projectKey, issueType, fieldName) {
let searchParams = {
expand: "projects.issuetypes.fields",
projectKeys: projectKey,
issuetypeNames: issueType,
};
let res = await bs.request(this._server, JiraResources.createmeta, {
method: "GET",
searchParams,
headers: this._sessionId === null ? this._basicAuthHeader : this._sessionHeader,
});
// Convert field name to field ID
let dict = await this.getFieldDict();
let fieldInfo = dict.byName[fieldName];
if (fieldInfo === undefined) {
throw Error(`Unknown field ${fieldName}`);
}
let field = res.body.projects[0].issuetypes[0].fields[fieldInfo.id];
if (field === undefined || field.allowedValues === undefined) {
return [];
}
let allowed = [];
for (let info of field.allowedValues) {
allowed.push(info.value);
}
return allowed;
}
async getComponents(projectKey) {
let res = await bs.request(this._server, `${JiraResources.components}/${projectKey}/components`, {
method: "GET",
headers: this._sessionId === null
? this._basicAuthHeader
: this._sessionHeader,
});
let components = {};
for (let component of res.body) {
components[component.name] = component.id;
}
return components;
}
async getProjects(component) {
let res = await bs.request(this._server, JiraResources.project, {
method: "GET",
headers: this._sessionId === null ? this._basicAuthHeader : this._sessionHeader,
searchParams: { expand: "lead" },
});
let projects = res.body;
if (component !== undefined) {
return projects.filter((el) => el.projectCategory.name === component);
}
return projects;
}
// TODO: add getProject
async updateProject(project, body) {
await bs.request(this._server, `${JiraResources.project}/${project}`, {
method: "PUT",
headers: this._sessionId === null ? this._basicAuthHeader : this._sessionHeader,
body,
});
}
async updateProjectLead(project, lead) {
await this.updateProject(project, { lead });
}
async createIssue(projectKey, issueType, component, fields) {
let components = await this.getComponents(projectKey);
let issue = {
fields: {
project: { key: projectKey },
issuetype: { name: issueType },
components: [{ id: components[component] }],
},
};
// Convert any field names to field IDs
let dict = await this.getFieldDict();
for (let fname in fields) {
let fid = dict.byName[fname]?.id;
if (fid !== undefined) {
issue.fields[fid] = fields[fname];
}
else {
issue.fields[fname] = fields[fname];
}
}
let res = await bs.request(this._server, JiraResources.issue, {
method: "POST",
body: issue,
headers: this._sessionId === null ? this._basicAuthHeader : this._sessionHeader,
});
return res.body.key;
}
async updateIssue(key, fields, notifyUsers = true) {
let issue = {
fields: {},
};
// Convert any field names to field IDs
let dict = await this.getFieldDict();
for (let fname in fields) {
let fid = dict.byName[fname]?.id;
if (fid !== undefined) {
issue.fields[fid] = fields[fname];
}
else {
issue.fields[fname] = fields[fname];
}
}
let res = await bs.request(this._server, `${JiraResources.issue}/${key}`, {
method: "PUT",
body: issue,
searchParams: notifyUsers ? undefined : { notifyUsers: "false" },
headers: this._sessionId === null ? this._basicAuthHeader : this._sessionHeader,
});
return res.body.key;
}
async getIssue(idOrKey) {
let res = await bs.request(this._server, `${JiraResources.issue}/${idOrKey}`, {
method: "GET",
headers: this._sessionId === null
? this._basicAuthHeader
: this._sessionHeader,
});
let issue = {};
// Convert any field IDs to field name
let dict = await this.getFieldDict();
for (let fid in res.body.fields) {
let fname = dict.byId[fid]?.name;
if (fname !== undefined) {
issue[fname] = res.body.fields[fid];
}
else {
issue[fid] = res.body.fields[fid];
}
}
// Add id to list of fields
issue["id"] = res.body.id;
return issue;
}
async issueReporter(key, reporter, notifyUsers = true) {
await this.updateIssue(key, { reporter: { name: reporter } }, notifyUsers);
}
async assignIssue(key, assignee, notifyUsers = true) {
await this.updateIssue(key, {
assignee: {
name: assignee,
},
}, notifyUsers);
}
async updateLabels(key, action, labels, notifyUsers = true) {
let issue = {
update: {
labels: [],
},
};
issue.update.labels = [];
for (let label of labels) {
issue.update.labels.push({ [action]: label });
}
let res = await bs.request(this._server, `${JiraResources.issue}/${key}`, {
method: "PUT",
body: issue,
searchParams: notifyUsers ? undefined : { notifyUsers: "false" },
headers: this._sessionId === null ? this._basicAuthHeader : this._sessionHeader,
});
return res.body.key;
}
async addComment(idOrKey, comment) {
await bs.request(this._server, `${JiraResources.issue}/${idOrKey}/comment`, {
method: "POST",
body: {
body: comment,
},
headers: this._sessionId === null
? this._basicAuthHeader
: this._sessionHeader,
});
}
async addWatcher(idOrKey, watcher) {
await bs.request(this._server, `${JiraResources.issue}/${idOrKey}/watchers`, {
method: "POST",
body: watcher,
headers: this._sessionId === null
? this._basicAuthHeader
: this._sessionHeader,
});
}
async removeWatcher(idOrKey, watcher) {
await bs.request(this._server, `${JiraResources.issue}/${idOrKey}/watchers`, {
method: "DELETE",
body: watcher,
headers: this._sessionId === null
? this._basicAuthHeader
: this._sessionHeader,
searchParams: { username: watcher },
});
}
async getTransitions(idOrKey) {
let res = await bs.request(this._server, `${JiraResources.issue}/${idOrKey}/transitions`, {
method: "GET",
headers: this._sessionId === null
? this._basicAuthHeader
: this._sessionHeader,
});
let transitions = {};
for (let transition of res.body.transitions) {
transitions[transition.name] = transition.id;
}
return transitions;
}
async doTransition(idOrKey, transitionIdOrName, fields, comment) {
// transition may be the Transition ID or name so check
let availableTransitions = await this.getTransitions(idOrKey);
let transitionId = availableTransitions[transitionIdOrName];
if (transitionId === undefined) {
transitionId = transitionIdOrName;
}
let dfields = {};
let dict = await this.getFieldDict();
if (fields !== undefined) {
// Convert any field names to field IDs
await this.getFieldDict();
for (let fname in fields) {
let fid = dict.byName[fname]?.id;
if (fid !== undefined) {
dfields[fid] = { name: fields[fname] };
}
else {
dfields[fname] = { name: fields[fname] };
}
}
}
let dcomment = { comment: [{ add: { body: comment } }] };
let body = {
update: comment === undefined ? undefined : dcomment,
fields: fields === undefined || fields.length === 0 ? undefined : dfields,
transition: { id: transitionId },
};
await bs.request(this._server, `${JiraResources.issue}/${idOrKey}/transitions`, {
method: "POST",
body,
headers: this._sessionId === null
? this._basicAuthHeader
: this._sessionHeader,
});
}
async runJql(jql) {
let issues = [];
let startAt = 0;
let maxResults = 1000; // 1000 is the max you can get
while (true) {
let res = await bs
.request(this._server, JiraResources.search, {
method: "GET",
headers: this._sessionId === null
? this._basicAuthHeader
: this._sessionHeader,
searchParams: {
jql,
startAt: startAt.toString(),
maxResults: maxResults.toString(),
fields: "key",
},
})
.catch((e) => {
this.error(e);
});
if (res === undefined) {
break;
}
// Append the results to what we already have
let results = res.body;
for (let issue of results.issues) {
issues.push(issue.key);
}
// Increment by maxResults
startAt += maxResults;
// If we are beyond the total then we have everything so break,
// otherwise go again
if (startAt > results.total) {
break;
}
}
return issues;
}
async getUserDashboardIds(userId) {
let res = await bs.request(this._server, `/${SCRIPTRUNNER_DASHBOARDS_N_FILTERS_URL}/params`, {
method: "POST",
body: {
FIELD_FROM_USER_ID: userId,
},
headers: this._sessionId === null
? this._basicAuthHeader
: this._sessionHeader,
});
let dashboardIds = [];
let data = res.body;
for (let obj of data) {
if (obj.name === "FIELD_DASHBOARD_IDS") {
for (let value of obj.values) {
dashboardIds.push(value[0]);
}
}
}
return dashboardIds;
}
async getUserFilterIds(userId) {
let res = await bs.request(this._server, `/${SCRIPTRUNNER_DASHBOARDS_N_FILTERS_URL}/params`, {
method: "POST",
body: {
FIELD_FROM_USER_ID: userId,
},
headers: this._sessionId === null
? this._basicAuthHeader
: this._sessionHeader,
});
let filterIds = [];
let data = res.body;
for (let obj of data) {
if (obj.name === "FIELD_FILTER_IDS") {
for (let value of obj.values) {
filterIds.push(value[0].toString());
}
}
}
return filterIds;
}
async migrateDashboards(fromUserId, toUserId, dashboardIds) {
await bs.request(this._server, `/${SCRIPTRUNNER_DASHBOARDS_N_FILTERS_URL}`, {
method: "POST",
body: {
FIELD_FROM_USER_ID: fromUserId,
FIELD_TO_USER_ID: toUserId,
FIELD_DASHBOARD_IDS: dashboardIds,
FIELD_FILTER_IDS: [],
},
headers: this._sessionId === null
? this._basicAuthHeader
: this._sessionHeader,
});
}
async migrateFilters(fromUserId, toUserId, filterIds) {
await bs.request(this._server, `/${SCRIPTRUNNER_DASHBOARDS_N_FILTERS_URL}`, {
method: "POST",
body: {
FIELD_FROM_USER_ID: fromUserId,
FIELD_TO_USER_ID: toUserId,
FIELD_DASHBOARD_IDS: [],
FIELD_FILTER_IDS: filterIds,
},
headers: this._sessionId === null
? this._basicAuthHeader
: this._sessionHeader,
});
}
async getUser(user, byKey, includeGroups = false) {
let searchParams = {};
if (byKey) {
searchParams["key"] = user;
}
else {
searchParams["username"] = user;
}
if (includeGroups) {
searchParams["expand"] = "groups";
}
let res = await bs.request(this._server, JiraResources.user, {
method: "GET",
searchParams,
headers: this._sessionId === null ? this._basicAuthHeader : this._sessionHeader,
});
return res.body;
}
async addUserToGroup(user, group) {
let res = await bs.request(this._server, `${JiraResources.group}/user`, {
method: "POST",
searchParams: { groupname: group },
headers: this._sessionId === null ? this._basicAuthHeader : this._sessionHeader,
body: { name: user },
});
return res.body;
}
async getUserGroups(user) {
let details = await this.getUser(user, false, true);
let groups = [];
let groupItems = details?.groups?.items;
if (groups !== undefined) {
for (let group of groupItems) {
groups.push(group.name);
}
}
return groups;
}
async addUserToApplication(user, applicationKey) {
await bs
.request(this._server, `${JiraResources.user}/application`, {
method: "POST",
searchParams: { username: user, applicationKey: applicationKey },
headers: this._sessionId === null
? this._basicAuthHeader
: this._sessionHeader,
body: {},
})
.catch((e) => {
this.error("Received errors (%j)", e);
});
}
async restApiCall(method, path, body) {
let res = await bs.request(this._server, path, {
method,
headers: this._sessionId === null ? this._basicAuthHeader : this._sessionHeader,
body,
});
return res;
}
}
export { Jira, JiraResources };
//# sourceMappingURL=plugin.mjs.map