Skip to content

Commit

Permalink
Basic working functionality.
Browse files Browse the repository at this point in the history
  • Loading branch information
willson556 committed Jan 14, 2024
1 parent 96b3d84 commit bb26797
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 367 deletions.
297 changes: 29 additions & 268 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@
"type": "module",
"dependencies": {
"@types/webextension-polyfill": "^0.10.0",
"jira.js": "^3.0.2",
"autocompleter": "^9.1.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.49.2",
"tributejs": "^5.1.3",
"ts-data-class": "^0.10.0",
"webextension-polyfill": "^0.10.0"
},
"devDependencies": {
Expand Down
85 changes: 85 additions & 0 deletions src/lib/jira.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { JiraCredentialStore, JiraCredentials } from './settings';

export class JiraIssue {
constructor(init?: Partial<JiraIssue>) {
Object.assign(this, init);
}

key!: string;
summary!: string;
assignee!: string;
}

export class JiraIssueRetriever {
private Credentials: JiraCredentials | null = null;

async init(): Promise<void> {
this.Credentials = await JiraCredentialStore.load();

if (!this.Credentials) {
throw new Error('Jira credentials not found');
}
}

async searchIssues(jql: string, startAt: number) : Promise<{issues: JiraIssue[], total: number}> {
if (!this.Credentials) {
throw new Error('Not initialized');
}

const params = {
'jql': jql,
'fields': 'summary,description,assignee',
'startAt': startAt.toString(),
'maxResults': '100',
}

var url = new URL(this.Credentials.instanceUrl + '/rest/api/3/search');
url.search = new URLSearchParams(params).toString();

const response = await fetch(url.toString(), {
method: 'GET',
headers: {
'Authorization': `Basic ${btoa(`${this.Credentials.username}:${this.Credentials.apiToken}`)}`,
'Accept': 'application/json',
}
});

if (!response.ok) {
throw new Error(`Failed to fetch issues: ${response.statusText}`);
}

const rawIssueResponse = await response.json();

console.log(rawIssueResponse);

const rawIssues = rawIssueResponse.issues;
const issues = rawIssues.map((rawIssue: any) => {
return new JiraIssue({
key: rawIssue.key,
summary: rawIssue.fields.summary,
assignee: rawIssue.fields.assignee?.displayName || '',
});
});

return { issues: issues, total: rawIssueResponse.total };
}

async getRecentlyUpdatedIssues(): Promise<JiraIssue[]> {
if (!this.Credentials) {
throw new Error('Not initialized');
}

const query = 'updated >= -30d order by updated DESC';

const initialResults = await this.searchIssues(query, 0);
const total = initialResults.total;
var issues = initialResults.issues;

while (issues.length < total) {
const nextResults = await this.searchIssues(query, issues.length);
issues = issues.concat(nextResults.issues);
}

return issues;
}
}
11 changes: 1 addition & 10 deletions src/utils/settings.ts → src/lib/settings.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import browser from 'webextension-polyfill';
import { DTClass, DTParsers } from 'ts-data-class';

export class JiraCredentials /*extends DTClass<JiraCredentials> */ {
export class JiraCredentials {
constructor(init?: Partial<JiraCredentials>) {
Object.assign(this, init);
}

instanceUrl!: string;
username!: string;
apiToken!: string;

// protected get parsers(): DTParsers<JiraCredentials> {
// return {
// instanceUrl: (v) => (typeof v === "string" ? v : ""),
// username: (v) => (typeof v === "string" ? v : ""),
// apiToken: (v) => (typeof v === "string" ? v : ""),
// };
// }
}

export class JiraCredentialStore {
Expand Down
2 changes: 1 addition & 1 deletion src/manifest/v3.mts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const manifest: ManifestTypeV3 = {
matches: ["<all_urls>"],
},
],
permissions:[ "storage" ],
permissions:[ "storage", "webRequest" ],
host_permissions: [
"https://*.atlassian.net/*",
]
Expand Down
16 changes: 9 additions & 7 deletions src/pages/background/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import browser from 'webextension-polyfill';
import { JiraIssue, JiraIssueRetriever } from '@src/utils/jira';
import {JiraIssueRetriever} from '@src/lib/jira';

console.log("background script loaded");

const jiraIssueRetriever = new JiraIssueRetriever();

browser.runtime.onMessage.addListener(async function (message) {
browser.runtime.onMessage.addListener(function (message, sender, sendResponse) {
console.log("here");
if (message.type === "jiraSuggestions") {
await jiraIssueRetriever.init();
const issues = await jiraIssueRetriever.getRecentlyUpdatedIssues();
return issues;
const jiraIssueRetriever = new JiraIssueRetriever();
jiraIssueRetriever.init().then(async () => {
const issues = await jiraIssueRetriever.getRecentlyUpdatedIssues();
sendResponse(issues);
});

return true;
}
});

Expand Down
52 changes: 39 additions & 13 deletions src/pages/content/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react";
import { createRoot } from "react-dom/client";
import Tribute from "tributejs";
import { JiraIssueRetriever, JiraIssue } from "@src/utils/jira";
import { JiraIssue } from "@src/lib/jira";
import browser from 'webextension-polyfill';
import autocomplete, { AutocompleteItem } from "autocompleter";

import "./style.css";

Expand All @@ -14,20 +14,46 @@ async function init() {
}

const issues: JiraIssue[] = await browser.runtime.sendMessage({ type: "jiraSuggestions" });

console.log(issues);
var tribute = new Tribute({
values: issues.map((issue: JiraIssue) => {
return {
key: issue.key + ": " + issue.summary,
value: issue.key + ": " + issue.summary,
};
})
type MyItem = JiraIssue & AutocompleteItem;
const te = document.getElementsByTagName("textarea")[0];

const autocompleteContainer = document.createElement("div");
autocompleteContainer.classList.add("autocomplete");
autocompleteContainer.role = "listbox";
autocompleteContainer.style.backgroundColor = "white";
autocompleteContainer.style.zIndex = "1";
document.body.appendChild(autocompleteContainer);

autocomplete<MyItem>({
input: te,
emptyMsg: "No issues found",
fetch: (text: string, update: (items: JiraIssue[]) => void) => {
text = text.toLowerCase();
const suggestions = issues.filter(n => n.summary.toLowerCase().includes(text));
update(suggestions);
},
onSelect: (item: JiraIssue) => {
te.value = `${item.key} - ${item.summary}`;
},
render: (item: JiraIssue, currentValue: string) => {
const div = document.createElement("div");
div.classList.add("autocomplete-entry");
div.textContent = `${item.key} - ${item.summary}`;

if (item.assignee) {
div.textContent += ` (${item.assignee})`;
}

return div;
},
click: e => e.fetch(),
container: autocompleteContainer,
});

const te = document.getElementsByTagName("textarea")[0];
console.log(te);
tribute.attach(te);


}

document.addEventListener("DOMContentLoaded", init);
12 changes: 12 additions & 0 deletions src/pages/content/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.autocomplete-entry {
background-color: #FFFFFF;
padding: 5px;
}

.autocomplete-entry.selected {
background-color: #009ceb;
}

.autocomplete-entry:hover {
background-color: #cbd1d3;
}
2 changes: 1 addition & 1 deletion src/pages/popup/Popup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react";
import { useForm } from "react-hook-form";
import { JiraCredentialStore, JiraCredentials } from "@src/utils/settings";
import { JiraCredentialStore, JiraCredentials } from "@src/lib/settings";

import logo from "@assets/img/logo.svg";

Expand Down
Empty file removed src/utils/autocomplete.ts
Empty file.
64 changes: 0 additions & 64 deletions src/utils/jira.ts

This file was deleted.

0 comments on commit bb26797

Please sign in to comment.