pipe-kan 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/pipe-kan.js CHANGED
@@ -28,7 +28,17 @@ function issueType(fields) {
28
28
  return typeof name === "string" ? name : undefined;
29
29
  }
30
30
  function epicKey(fields) {
31
- return typeof fields?.parent?.key === "string" ? fields.parent.key : undefined;
31
+ if (typeof fields?.parent?.key === "string")
32
+ return fields.parent.key;
33
+ const link = fields?.["Epic Link"];
34
+ if (typeof link === "string" && link)
35
+ return link;
36
+ if (link && typeof link === "object" && "key" in link) {
37
+ const key = link.key;
38
+ if (typeof key === "string" && key)
39
+ return key;
40
+ }
41
+ return;
32
42
  }
33
43
  function labelName(item) {
34
44
  if (typeof item === "string" && item)
@@ -73,7 +83,8 @@ function toEpic(face) {
73
83
  ...face.status ? { status: face.status } : {},
74
84
  ...face.priority ? { priority: face.priority } : {},
75
85
  ...face.assignee ? { assignee: face.assignee } : {},
76
- ...face.dueDate ? { dueDate: face.dueDate } : {}
86
+ ...face.dueDate ? { dueDate: face.dueDate } : {},
87
+ ...face.labels ? { labels: face.labels } : {}
77
88
  };
78
89
  }
79
90
  function toCard(issue, key) {
@@ -252,6 +263,9 @@ function createStoreCli(store) {
252
263
  const issues = store.list(`project="DEMO" AND parent="${key}"`);
253
264
  return JSON.stringify(issues, null, 2);
254
265
  },
266
+ async listChildren(keys) {
267
+ return JSON.stringify(store.childrenOf(keys), null, 2);
268
+ },
255
269
  async move(key, status) {
256
270
  return store.move(key, status);
257
271
  },
@@ -342,6 +356,25 @@ function createJiraCli(opts = {}) {
342
356
  }
343
357
  return result.stdout;
344
358
  },
359
+ async listChildren(keys) {
360
+ if (!keys.length)
361
+ return "[]";
362
+ const list = keys.map((key) => `"${key}"`).join(", ");
363
+ const result = await run([
364
+ "issue",
365
+ "list",
366
+ "-q",
367
+ `parent in (${list}) OR "Epic Link" in (${list})`,
368
+ "--raw"
369
+ ]);
370
+ if (result.code !== 0) {
371
+ const text = result.stderr || result.stdout || "jira issue list failed";
372
+ if (emptyList(text))
373
+ return "[]";
374
+ throw new Error(text);
375
+ }
376
+ return result.stdout;
377
+ },
345
378
  async move(key, status) {
346
379
  const result = await run(["issue", "move", key, status]);
347
380
  if (result.code !== 0) {
@@ -505,11 +538,35 @@ function flattenIssue(raw, url) {
505
538
  }
506
539
 
507
540
  // src/app.ts
541
+ function columnsOf(raw) {
542
+ return Object.fromEntries(issuesToBoard(raw).columns.map((column) => [column.title, column.cards]));
543
+ }
544
+ function stampMissingEpic(board, epic) {
545
+ for (const column of board.columns) {
546
+ for (const card of column.cards) {
547
+ if (!card.epic)
548
+ card.epic = epic;
549
+ }
550
+ }
551
+ return board;
552
+ }
508
553
  function createApp(opts) {
509
554
  const cli = opts.cli ?? createStoreCli(opts.store);
510
555
  let flags = DEFAULT_FLAGS;
511
556
  let payload = [];
512
557
  let epicsPayload = [];
558
+ let childrenRaw = [];
559
+ let hasChildrenCache = false;
560
+ let childrenError;
561
+ function listedEpicKeys() {
562
+ return issuesToBoard(epicsPayload).epics.map((epic) => epic.key);
563
+ }
564
+ function cacheFromStore() {
565
+ epicsPayload = opts.store.list(flagsToJql("-tEpic"));
566
+ childrenRaw = opts.store.childrenOf(listedEpicKeys());
567
+ hasChildrenCache = true;
568
+ childrenError = undefined;
569
+ }
513
570
  const app = {
514
571
  get flags() {
515
572
  return flags;
@@ -518,37 +575,61 @@ function createApp(opts) {
518
575
  const board = issuesToBoard(payload);
519
576
  return {
520
577
  columns: board.columns,
521
- epics: mergeEpics(issuesToBoard(epicsPayload).epics, board.epics)
578
+ epics: mergeEpics(issuesToBoard(epicsPayload).epics, board.epics),
579
+ ...hasChildrenCache ? { children: columnsOf(childrenRaw) } : {},
580
+ ...childrenError ? { error: childrenError } : {}
522
581
  };
523
582
  },
524
- hydrate(raw) {
583
+ hydrate(raw, hydrateOpts) {
525
584
  payload = Array.isArray(raw) ? raw : [];
526
585
  epicsPayload = [];
586
+ childrenRaw = [];
587
+ hasChildrenCache = false;
588
+ childrenError = undefined;
589
+ if (hydrateOpts?.fromStore)
590
+ cacheFromStore();
527
591
  return app.board();
528
592
  },
529
593
  async refresh(next) {
530
594
  if (next !== undefined)
531
595
  flags = next;
596
+ childrenError = undefined;
532
597
  const [issues, epics] = await Promise.all([
533
598
  cli.list(flags),
534
599
  cli.listEpics()
535
600
  ]);
536
601
  payload = JSON.parse(issues);
537
602
  epicsPayload = JSON.parse(epics);
603
+ const keys = listedEpicKeys();
604
+ try {
605
+ childrenRaw = JSON.parse(await cli.listChildren(keys));
606
+ hasChildrenCache = true;
607
+ } catch (err) {
608
+ hasChildrenCache = false;
609
+ childrenRaw = [];
610
+ childrenError = err instanceof Error ? err.message : "Epic children list failed";
611
+ }
538
612
  return app.board();
539
613
  },
540
614
  async children(epic) {
541
- const board = issuesToBoard(JSON.parse(await cli.listEpic(epic)));
542
- for (const column of board.columns) {
543
- for (const card of column.cards) {
544
- if (!card.epic)
545
- card.epic = epic;
546
- }
615
+ if (hasChildrenCache) {
616
+ const cards = Object.fromEntries(Object.entries(columnsOf(childrenRaw)).map(([title, list]) => [
617
+ title,
618
+ list.filter((card) => card.epic === epic).map((card) => ({
619
+ ...card,
620
+ epic: card.epic ?? epic
621
+ }))
622
+ ]));
623
+ return {
624
+ columns: Object.entries(cards).filter(([, list]) => list.length).map(([title, list]) => ({ id: title, title, cards: list })),
625
+ epics: []
626
+ };
547
627
  }
548
- return board;
628
+ return stampMissingEpic(issuesToBoard(JSON.parse(await cli.listEpic(epic))), epic);
549
629
  },
550
630
  async move(key, status) {
551
- const current = payload.find((issue) => issue.key === key)?.fields?.status?.name;
631
+ const fromPayload = payload.find((issue) => issue.key === key)?.fields?.status?.name;
632
+ const current = fromPayload ?? issuesToBoard(epicsPayload).epics.find((epic) => epic.key === key)?.status ?? app.board().epics.find((epic) => epic.key === key)?.status;
552
633
  if (current === status) {
553
634
  return { ok: true, noop: true, board: app.board() };
554
635
  }
@@ -660,6 +741,14 @@ class IssueStore {
660
741
  list(jql = "") {
661
742
  return this.issues.filter((issue) => matchJql(issue, jql));
662
743
  }
744
+ childrenOf(keys) {
745
+ const listed = new Set(keys);
746
+ return this.issues.filter((issue) => {
747
+ if (fieldValue(issue, "type").toLowerCase() === "epic")
748
+ return false;
749
+ return listed.has(fieldValue(issue, "parent"));
750
+ });
751
+ }
663
752
  transitions(key) {
664
753
  const issue = this.get(key);
665
754
  if (!issue)
@@ -708,7 +797,7 @@ async function createBoardApp(opts) {
708
797
  app.hydrate(opts.raw);
709
798
  } else {
710
799
  const local = createStoreCli(store);
711
- app.hydrate(JSON.parse(await local.list(app.flags)));
800
+ app.hydrate(JSON.parse(await local.list(app.flags)), { fromStore: true });
712
801
  }
713
802
  return { app, store, kind: bin ? "jira" : "store" };
714
803
  }