convert Linear puller to TypeScript
parent
a69f6c10e2
commit
6462a71807
|
@ -0,0 +1,105 @@
|
|||
(() => {
|
||||
let creds = new Credentials();
|
||||
|
||||
var action = new PlugIn.Action(async () => {
|
||||
try {
|
||||
let url = "https://api.linear.app/graphql";
|
||||
let req = URL.FetchRequest.fromString(url);
|
||||
if (req === null) {
|
||||
throw "could not parse the URL for Linear's API"
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
// Step 1: make sure we have creds //
|
||||
/////////////////////////////////////
|
||||
let stored = creds.read(url);
|
||||
|
||||
let key = null;
|
||||
if (stored === null || app.optionKeyDown) {
|
||||
let credsForm = new Form();
|
||||
credsForm.addField(new Form.Field.Password("key", "API Key"));
|
||||
|
||||
await credsForm.show("Please create a personal API key in the Linear settings and paste it here\n(hold option while activating this workflow in the future to reset this)", "Save Key");
|
||||
key = (credsForm.values as { key: string }).key;
|
||||
|
||||
creds.write(url, "-", key);
|
||||
} else {
|
||||
key = stored.password;
|
||||
}
|
||||
|
||||
///////////////////////////
|
||||
// Step 2: get the tasks //
|
||||
///////////////////////////
|
||||
req.method = "POST";
|
||||
req.bodyString = '{"query":"{ viewer { assignedIssues(filter: {state: {type: {nin: [\\"completed\\",\\"canceled\\"]}}}) { nodes { identifier title url team { name } project { name url } } } } }"}';
|
||||
req.headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": key,
|
||||
};
|
||||
|
||||
let resp = await (req.fetch().catch((err) => {
|
||||
console.error("Problem fetching tasks:", err);
|
||||
let alert = new Alert("Problem fetching from Linear", err);
|
||||
alert.show();
|
||||
throw err;
|
||||
}));
|
||||
|
||||
if (resp.bodyString === null) {
|
||||
throw "body string was null. Did the request succeed?"
|
||||
}
|
||||
|
||||
let body = JSON.parse(resp.bodyString);
|
||||
|
||||
//////////////////////////////////
|
||||
// Step 3: make the tasks in OF //
|
||||
//////////////////////////////////
|
||||
let toFocus: Array<Project> = [];
|
||||
for (let linearTask of body.data.viewer.assignedIssues.nodes) {
|
||||
let teamsTag = flattenedTags.byName("teams") || new Tag("teams");
|
||||
let teamTag = teamsTag.tagNamed(linearTask.team.name) || new Tag(linearTask.team.name, teamsTag);
|
||||
|
||||
let linearTag = flattenedTags.byName("from Linear") || new Tag("from Linear");
|
||||
|
||||
let projectName = `${linearTask.team.name} Non-Project Tasks`;
|
||||
if (linearTask.project !== null) {
|
||||
projectName = linearTask.project.name;
|
||||
}
|
||||
|
||||
let project = flattenedProjects.byName(projectName) || new Project(projectName);
|
||||
project.addTag(teamTag);
|
||||
project.addTag(linearTag);
|
||||
project.containsSingletonActions = true;
|
||||
toFocus.push(project);
|
||||
if (linearTask.project && project.note.indexOf(linearTask.project.url) === -1) {
|
||||
if (project.note !== "") {
|
||||
project.appendStringToNote(`\n\n${linearTask.project.url}`);
|
||||
} else {
|
||||
project.appendStringToNote(linearTask.project.url)
|
||||
}
|
||||
}
|
||||
|
||||
let taskName = `${linearTask.identifier}: ${linearTask.title}`;
|
||||
let task = project.taskNamed(taskName) || new Task(taskName, project);
|
||||
task.addTag(teamTag);
|
||||
task.addTag(linearTag);
|
||||
if (task.note.indexOf(linearTask.url) === -1) {
|
||||
if (task.note !== "") {
|
||||
task.appendStringToNote(`\n\n${linearTask.url}`);
|
||||
} else {
|
||||
task.appendStringToNote(linearTask.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (app.platformName === "macOS") {
|
||||
document.windows[0].perspective = Perspective.BuiltIn.Projects;
|
||||
document.windows[0].focus = toFocus as SectionArray;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
return action;
|
||||
})();
|
|
@ -1,3 +1,9 @@
|
|||
declare const app: Application
|
||||
|
||||
declare const console: Console
|
||||
|
||||
declare const document: DatabaseDocument
|
||||
|
||||
declare const flattenedProjects : ProjectArray
|
||||
|
||||
declare const flattenedTags : TagArray
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
declare class Alert {
|
||||
constructor (title: string, message: string);
|
||||
show(callback: Function | null): Promise<number>;
|
||||
show(callback?: Function | null): Promise<number>;
|
||||
addOption(string: string);
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,7 @@ declare class Console {
|
|||
|
||||
declare class Credentials {
|
||||
constructor ();
|
||||
read(service: string): object | null;
|
||||
read(service: string): { user: string, password: string } | null;
|
||||
write(service: string, username: string, password: string);
|
||||
remove(service: string);
|
||||
readBookmark(service: string): URL.Bookmark | null;
|
||||
|
@ -319,7 +319,7 @@ declare namespace Tag {
|
|||
}
|
||||
|
||||
declare class Tag extends ActiveObject {
|
||||
constructor (name: string, position: Tag | Tag.ChildInsertionLocation | null);
|
||||
constructor (name: string, position?: Tag | Tag.ChildInsertionLocation | null);
|
||||
tagNamed(name: string): Tag | null;
|
||||
childNamed(name: string): Tag | null;
|
||||
apply(f: Function): ApplyResult | null;
|
||||
|
@ -447,7 +447,7 @@ declare namespace Project {
|
|||
}
|
||||
|
||||
declare class Project extends DatabaseObject {
|
||||
constructor (name: string, position: Folder | Folder.ChildInsertionLocation | null);
|
||||
constructor (name: string, position?: Folder | Folder.ChildInsertionLocation | null);
|
||||
taskNamed(name: string): Task | null;
|
||||
appendStringToNote(stringToAppend: string);
|
||||
addAttachment(attachment: FileWrapper);
|
||||
|
@ -798,7 +798,7 @@ declare namespace Form.Field {
|
|||
|
||||
declare namespace Form.Field {
|
||||
class Password extends Form.Field {
|
||||
constructor (key: string, displayName: string | null, value: string | null);
|
||||
constructor (key: string, displayName?: string | null, value?: string | null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Reference in New Issue