Skip to content

Instantly share code, notes, and snippets.

@ryan-nauman
Created February 1, 2024 21:01
Show Gist options
  • Save ryan-nauman/737ffd49215ad2615ed675f11fa32c80 to your computer and use it in GitHub Desktop.
Save ryan-nauman/737ffd49215ad2615ed675f11fa32c80 to your computer and use it in GitHub Desktop.
script kit jira search
// Name: Jira Search
import '@johnlindquist/kit';
import { Buffer } from 'buffer';
function adfConvert(node) {
switch (node.type) {
case 'doc':
return node.content.map((node) => adfConvert(node)).join('\n\n');
case 'text':
return `${convertMarks(node)}`;
case 'paragraph':
return node.content.map((node) => adfConvert(node)).join('');
case 'heading':
return `${'#'.repeat(node.attrs.level)} ${node.content
.map((node) => adfConvert(node))
.join('')}`;
case 'hardBreak':
return '\n';
case 'inlineCard':
case 'blockCard':
case 'embedCard':
return `[${node.attrs.url}](${node.attrs.url})`;
case 'blockquote':
return `> ${node.content.map((node) => adfConvert(node)).join('\n> ')}`;
case 'bulletList':
case 'orderedList':
return `${node.content
.map((subNode) => {
const converted = adfConvert.call(node, subNode);
if (node.type === 'orderedList') {
if (!node.attrs) {
node.attrs = {
order: 1,
};
}
node.attrs.order += 1;
}
return converted;
})
.join('\n')}`;
case 'listItem': {
const order = this.attrs ? this.attrs.order || 1 : 1;
const symbol = this.type === 'bulletList' ? '*' : `${order}.`;
return ` ${symbol} ${node.content
.map((node) => adfConvert(node).trimEnd())
.join(` `)}`;
}
case 'codeBlock': {
const language = node.attrs ? ` ${node.attrs.language}` : '';
return `\`\`\`${language}\n${node.content
.map((node) => adfConvert(node))
.join('\n')}\n\`\`\``;
}
case 'rule':
return '\n\n---\n';
case 'emoji':
return node.attrs.shortName;
case 'table':
return node.content.map((node) => adfConvert(node)).join('');
case 'tableRow': {
let output = '|';
let thCount = 0;
output += node.content
.map((subNode) => {
thCount += subNode.type === 'tableHeader' ? 1 : 0;
return adfConvert(subNode);
})
.join('');
output += thCount ? `\n${'|:-:'.repeat(thCount)}|\n` : '\n';
return output;
}
case 'tableHeader':
return `${node.content.map((node) => adfConvert(node)).join('')}|`;
case 'tableCell':
return `${node.content.map((node) => adfConvert(node)).join('')}|`;
default:
return '';
}
}
function convertMarks(node) {
if (!node.hasOwnProperty('marks') || !Array.isArray(node.marks)) {
return node.text;
}
return node.marks.reduce((converted, mark) => {
switch (mark.type) {
case 'code':
converted = `\`${converted}\``;
break;
case 'em':
converted = `_${converted}_`;
break;
case 'link':
converted = `[${converted}](${mark.attrs.href})`;
break;
case 'strike':
converted = `~${converted}~`;
break;
case 'strong':
converted = `**${converted}**`;
break;
default:
break;
}
return converted;
}, node.text);
}
function encodeToBase64Node(input: string): string {
return Buffer.from(input).toString('base64');
}
const ATLASSIAN_EMAIL = await env('ATLASSIAN_EMAIL', async () => {
return await arg('Enter your jira email address');
});
const ATLASSIAN_TOKEN = await env('ATLASSIAN_TOKEN', async () => {
return await arg('Enter your jira api token');
});
const BROWSE_ISSUE_URL_PREFIX = 'https://studio-x.atlassian.net/browse/';
const SEARCH_API_PREFIX = 'https://studio-x.atlassian.net/rest/api/3/search';
const searchMethod = await arg('How would you like to search jira?', [
{ name: 'Text search', value: 'text' },
{ name: 'Key (e.g. VOLT-1)', value: 'key' },
{ name: 'JQL (e.g. project = VOLT)', value: 'jql' },
]);
const searchQuery = await arg('Enter the search value');
let jql;
switch (searchMethod) {
case 'text':
jql = `text ~ "${searchQuery}"`;
break;
case 'key':
jql = `key = ${searchQuery}`;
break;
case 'jql':
jql = searchQuery;
break;
}
let { data } = await post<Jira>(
`${SEARCH_API_PREFIX}`,
{
jql,
maxResults: 10,
fields: ['summary', 'assignee', 'creator', 'description'],
fieldsByKeys: false,
},
{
headers: {
Authorization: `Basic ${encodeToBase64Node(
`${ATLASSIAN_EMAIL}:${ATLASSIAN_TOKEN}`,
)}`,
},
},
);
let issueKey = await arg(
`Select Issue:`,
data.issues.map(({ fields, key }) => {
return {
name: key,
description: fields.summary,
value: key,
preview: () => {
const adf = adfConvert(fields?.description);
return md(`# ${key} ${fields?.summary}
🧑‍🍳 ${fields?.assignee?.displayName || 'Unassigned'}
📖 ${fields?.creator?.displayName || 'Unknown'}
#### Description
${adf}
`);
},
};
}),
);
await $`open ${BROWSE_ISSUE_URL_PREFIX}${issueKey}`;
export interface Jira {
expand: string;
issues: Issue[];
maxResults: number;
startAt: number;
total: number;
}
export interface Issue {
expand: string;
fields: IssueFields;
id: string;
key: string;
self: string;
}
export interface IssueFields {
aggregateprogress: Progress;
aggregatetimeestimate: null;
aggregatetimeoriginalestimate: null;
aggregatetimespent: null;
assignee: Creator | null;
components: Priority[];
created: string;
creator: Creator;
customfield_10100: string;
customfield_10101: null | string;
customfield_10105: null;
customfield_10106: null;
customfield_10200: string;
customfield_10201: null;
customfield_10202: null;
customfield_10203: null;
customfield_10204: null;
customfield_10205: null;
customfield_10206: null;
customfield_10207: null;
customfield_10208: null;
customfield_10209: null;
customfield_10210: null;
customfield_10211: null;
customfield_10212: null;
customfield_10213: null;
customfield_10214: null;
customfield_10215: Customfield10215;
customfield_10216: null | string;
customfield_10217: null | string;
customfield_10220: Customfield10107_Class[] | null;
customfield_10222: null;
customfield_10223: null;
customfield_10224: Customfield10224 | null;
customfield_10225: number | null;
customfield_10226: null;
customfield_10227: null;
customfield_10228: null;
customfield_10229: null;
customfield_10230: null;
customfield_10231: any[];
customfield_10232: null;
customfield_10235: null;
customfield_10236: null;
customfield_10237: null;
customfield_10238: null;
customfield_10239: null;
customfield_10240: null;
customfield_10241: null;
customfield_10242: null;
customfield_10243: null;
customfield_10244: null;
customfield_10245: null;
customfield_10246: null;
customfield_10247: null;
customfield_10248: null;
customfield_10249: null;
customfield_10250: null;
customfield_10251: null;
customfield_10252: null;
customfield_10253: null;
customfield_10254: null;
customfield_10255: null;
customfield_10256: any[];
customfield_10257: null;
customfield_10258: Customfield10258_Class | null;
customfield_10259: Customfield10258_Class | null;
customfield_10260: Customfield10258_Class | null;
customfield_10261: Customfield10258_Class | null;
customfield_10262: null;
customfield_10263: null;
customfield_10264: null;
customfield_10265: null;
customfield_10266: null;
customfield_10267: null;
customfield_10268: null;
customfield_10269: null;
customfield_10271: null;
customfield_10272: null;
customfield_10273: null;
customfield_10274: null;
customfield_10275: null;
customfield_10276: null;
customfield_10277: null;
customfield_10278: null;
customfield_10279: null;
customfield_10280: null;
customfield_10281: null;
customfield_10282: null;
customfield_10283: null;
customfield_10284: null;
customfield_10285: null;
customfield_10286: null;
customfield_10287: null;
customfield_10288: null;
customfield_10289: null;
customfield_10290: null;
customfield_10291: null;
customfield_10292: null;
customfield_10293: null;
customfield_10294: null;
customfield_10296: Creator[] | null;
customfield_10297: null;
customfield_10298: null;
customfield_10299: null;
customfield_10300: null;
customfield_10301: null;
customfield_10303: null;
customfield_10304: null;
customfield_10305: null;
customfield_10306: null;
customfield_10308: null;
customfield_10310: null;
customfield_10311: null;
customfield_10312: null;
customfield_10313: null;
customfield_10314: null;
customfield_10315: null;
customfield_10316: null;
customfield_10317: null;
customfield_10318: null;
customfield_10319: null;
customfield_10320: null;
customfield_10321: null;
customfield_10347: null;
customfield_10348: null;
customfield_10349: null;
customfield_10350: null;
customfield_10351: null;
customfield_10352: null;
customfield_10353: null;
customfield_10354: null;
customfield_10355: null;
customfield_10356: null;
customfield_10362: null;
customfield_10363: null;
customfield_10364: null;
customfield_10365: null;
customfield_10366: Customfield10107_Class | null;
customfield_10370: null;
customfield_10371: null;
customfield_10372: null;
customfield_10373: null;
customfield_10374: null;
customfield_10375: null;
customfield_10376: null;
customfield_10377: null;
customfield_10378: null;
customfield_10379: null;
customfield_10380: null;
customfield_10381: null;
customfield_10382: null;
customfield_10383: null;
customfield_10434: Customfield10258_Class | null;
customfield_10436: null;
customfield_10465: null;
customfield_10466: null;
description: DescriptionClass;
duedate: null;
environment: Environment | null;
fixVersions: any[];
issuelinks: Issuelink[];
issuetype: Issuetype;
labels: string[];
lastViewed: null | string;
priority: Priority;
progress: Progress;
project: Project;
reporter: Creator;
resolution: Priority | null;
resolutiondate: null | string;
security: null;
status: Status;
statuscategorychangedate: string;
subtasks: Parent[];
summary: string;
timeestimate: null;
timeoriginalestimate: null;
timespent: null;
updated: string;
versions: any[];
votes: Votes;
watches: Watches;
workratio: number;
customfield_10107?: Customfield10107_Class;
parent?: Parent;
}
export interface Progress {
progress: number;
total: number;
}
export interface Creator {
accountId: string;
accountType: AccountType;
active: boolean;
avatarUrls: AvatarUrls;
displayName: string;
self: string;
timeZone: TimeZone;
emailAddress?: EmailAddress;
}
export enum AccountType {
App = 'app',
Atlassian = 'atlassian',
}
export interface AvatarUrls {
'16x16': string;
'24x24': string;
'32x32': string;
'48x48': string;
}
export enum EmailAddress {
RyanNaumanThinkonwardCOM = '[email protected]',
}
export enum TimeZone {
AmericaChicago = 'America/Chicago',
AmericaNewYork = 'America/New_York',
CanadaPacific = 'Canada/Pacific',
USPacific = 'US/Pacific',
}
export interface Priority {
id: string;
name: PriorityName;
self: string;
description?: DescriptionEnum;
iconUrl?: string;
}
export enum DescriptionEnum {
GreenHopperManagedResolution = 'GreenHopper Managed Resolution',
UsedToTagIssuesAsBackend = 'used to tag issues as backend',
UsedToTagStoriesAsFrontend = 'used to tag stories as frontend ',
}
export enum PriorityName {
Be = 'BE',
Critical = 'Critical',
Done = 'Done',
Fe = 'FE',
High = 'High',
Low = 'Low',
Medium = 'Medium',
Voltron = 'voltron',
}
export interface Customfield10107_Class {
id: string;
self: string;
value: Value;
}
export enum Value {
BugVoltron = 'Bug - Voltron',
Impediment = 'Impediment',
ToEstimate = 'TO ESTIMATE',
UserStoryVoltron = 'User Story - Voltron',
}
export interface Customfield10215 {
hasEpicLinkFieldDependency: boolean;
nonEditableReason: NonEditableReason;
showField: boolean;
}
export interface NonEditableReason {
message: Message;
reason: Reason;
}
export enum Message {
ToSetAnEpicAsTheParentUseTheEpicLinkInstead = 'To set an epic as the parent, use the epic link instead',
ToSetTheParentForASubTaskUseTheSubTaskLink = 'To set the parent for a sub-task, use the sub-task link.',
}
export enum Reason {
EpicLinkShouldBeUsed = 'EPIC_LINK_SHOULD_BE_USED',
SubtaskLinkShouldBeUsed = 'SUBTASK_LINK_SHOULD_BE_USED',
}
export enum Customfield10224 {
Checklist12 = 'Checklist: 1/2',
Empty = '',
}
export interface Customfield10258_Class {
errorMessage: ErrorMessage;
i18nErrorMessage: I18NErrorMessage;
}
export enum ErrorMessage {
TheServiceProjectYouAreTryingToViewDoesNotExist = 'The service project you are trying to view does not exist.',
}
export interface I18NErrorMessage {
i18nKey: I18NKey;
parameters: any[];
}
export enum I18NKey {
SDJsmAgentServicedeskErrorServicedeskMissing = 'sd.jsm.agent.servicedesk.error.servicedesk.missing',
}
export interface DescriptionClass {
content: DescriptionContent[];
type: DescriptionType;
version: number;
}
export interface DescriptionContent {
content?: PurpleContent[];
type: IndigoType;
attrs?: PurpleAttrs;
}
export interface PurpleAttrs {
layout?: Layout;
order?: number;
level?: number;
isNumberColumnEnabled?: boolean;
localId?: string;
language?: string;
width?: number;
}
export enum Layout {
AlignStart = 'align-start',
Default = 'default',
}
export interface PurpleContent {
text?: string;
type: StickyType;
marks?: FluffyMark[];
attrs?: FluffyAttrs;
content?: FluffyContent[];
}
export interface FluffyAttrs {
collection?: string;
height?: number;
id?: string;
type?: AttrsType;
width?: number;
url?: string;
shortName?: string;
text?: string;
timestamp?: string;
accessLevel?: string;
}
export enum AttrsType {
File = 'file',
}
export interface FluffyContent {
content: TentacledContent[];
type: TentacledType;
attrs?: TentacledAttrs;
}
export interface TentacledAttrs {
colwidth?: number[];
layout?: Layout;
width?: number;
}
export interface TentacledContent {
text?: string;
type: PurpleType;
marks?: FluffyMark[];
content?: StickyContent[];
attrs?: StickyAttrs;
}
export interface StickyAttrs {
collection: string;
height: number;
id: string;
type: AttrsType;
width: number;
}
export interface StickyContent {
marks?: FluffyMark[];
text?: string;
type: FluffyType;
attrs?: IndigoAttrs;
content?: IndigoContent[];
}
export interface IndigoAttrs {
color: Color;
localId: string;
style: string;
text: Text;
}
export enum Color {
Green = 'green',
Red = 'red',
Yellow = 'yellow',
}
export enum Text {
InUse = 'in use',
Refactor = 'Refactor',
Unused = 'unused',
}
export interface IndigoContent {
content: IndecentContent[];
type: PurpleType;
}
export interface IndecentContent {
text?: string;
type: PurpleType;
marks?: PurpleMark[];
}
export interface PurpleMark {
type: MarkType;
}
export enum MarkType {
Code = 'code',
Em = 'em',
Link = 'link',
Strong = 'strong',
}
export enum PurpleType {
BulletList = 'bulletList',
HardBreak = 'hardBreak',
Media = 'media',
Paragraph = 'paragraph',
Text = 'text',
}
export interface FluffyMark {
type: MarkType;
attrs?: MarkAttrs;
}
export interface MarkAttrs {
href: string;
}
export enum FluffyType {
ListItem = 'listItem',
Status = 'status',
Text = 'text',
}
export enum TentacledType {
MediaSingle = 'mediaSingle',
Paragraph = 'paragraph',
TableCell = 'tableCell',
TableHeader = 'tableHeader',
}
export enum StickyType {
Date = 'date',
Emoji = 'emoji',
HardBreak = 'hardBreak',
InlineCard = 'inlineCard',
ListItem = 'listItem',
Media = 'media',
Mention = 'mention',
TableRow = 'tableRow',
Text = 'text',
}
export enum IndigoType {
BulletList = 'bulletList',
CodeBlock = 'codeBlock',
Heading = 'heading',
MediaSingle = 'mediaSingle',
OrderedList = 'orderedList',
Paragraph = 'paragraph',
Rule = 'rule',
Table = 'table',
}
export enum DescriptionType {
Doc = 'doc',
}
export interface Environment {
content: EnvironmentContent[];
type: DescriptionType;
version: number;
}
export interface EnvironmentContent {
content: HilariousContent[];
type: PurpleType;
}
export interface HilariousContent {
text: string;
type: PurpleType;
}
export interface Issuelink {
id: string;
inwardIssue?: Parent;
self: string;
type: TypeClass;
outwardIssue?: Parent;
}
export interface Parent {
fields: ParentFields;
id: string;
key: string;
self: string;
}
export interface ParentFields {
issuetype: Issuetype;
priority: Priority;
status: Status;
summary: string;
}
export interface Issuetype {
description: string;
hierarchyLevel: number;
iconUrl: string;
id: string;
name: IssuetypeName;
self: string;
subtask: boolean;
avatarId?: number;
}
export enum IssuetypeName {
Bug = 'Bug',
Epic = 'Epic',
Story = 'Story',
SubTask = 'Sub-task',
Task = 'Task',
}
export interface Status {
description: string;
iconUrl: string;
id: string;
name: StatusName;
self: string;
statusCategory: StatusCategory;
}
export enum StatusName {
Closed = 'Closed',
InProgress = 'In Progress',
InReview = 'In Review',
InTest = 'In Test',
Ready = 'Ready',
ToDo = 'To Do',
}
export interface StatusCategory {
colorName: ColorName;
id: number;
key: StatusCategoryKey;
name: StatusCategoryName;
self: string;
}
export enum ColorName {
BlueGray = 'blue-gray',
Green = 'green',
Yellow = 'yellow',
}
export enum StatusCategoryKey {
Done = 'done',
Indeterminate = 'indeterminate',
New = 'new',
}
export enum StatusCategoryName {
Done = 'Done',
InProgress = 'In Progress',
ToDo = 'To Do',
}
export interface TypeClass {
id: string;
inward: string;
name: string;
outward: string;
self: string;
}
export interface Project {
avatarUrls: AvatarUrls;
id: string;
key: ProjectKey;
name: ProjectName;
projectTypeKey: ProjectTypeKey;
self: string;
simplified: boolean;
}
export enum ProjectKey {
Volt = 'VOLT',
}
export enum ProjectName {
Voltron = 'Voltron',
}
export enum ProjectTypeKey {
Software = 'software',
}
export interface Votes {
hasVoted: boolean;
self: string;
votes: number;
}
export interface Watches {
isWatching: boolean;
self: string;
watchCount: number;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment