@fink-andreas/pi-linear-tools 0.4.3 → 0.5.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/CHANGELOG.md +27 -10
- package/extensions/pi-linear-tools.js +67 -99
- package/package.json +1 -1
- package/src/handlers.js +63 -3
- package/src/linear.js +985 -184
package/src/linear.js
CHANGED
|
@@ -153,6 +153,64 @@ const PROJECTS_LOOKUP_QUERY = `
|
|
|
153
153
|
}
|
|
154
154
|
`;
|
|
155
155
|
|
|
156
|
+
const PROJECT_MINIMAL_QUERY = `
|
|
157
|
+
query ProjectMinimal($id: String!) {
|
|
158
|
+
project(id: $id) {
|
|
159
|
+
id
|
|
160
|
+
name
|
|
161
|
+
slugId
|
|
162
|
+
archivedAt
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
`;
|
|
166
|
+
|
|
167
|
+
const PROJECT_MILESTONES_QUERY = `
|
|
168
|
+
query ProjectMilestones($id: String!, $first: Int!) {
|
|
169
|
+
project(id: $id) {
|
|
170
|
+
id
|
|
171
|
+
name
|
|
172
|
+
projectMilestones(first: $first) {
|
|
173
|
+
nodes {
|
|
174
|
+
id
|
|
175
|
+
name
|
|
176
|
+
description
|
|
177
|
+
progress
|
|
178
|
+
sortOrder
|
|
179
|
+
targetDate
|
|
180
|
+
status
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
`;
|
|
186
|
+
|
|
187
|
+
const TEAM_MINIMAL_QUERY = `
|
|
188
|
+
query TeamMinimal($id: String!) {
|
|
189
|
+
team(id: $id) {
|
|
190
|
+
id
|
|
191
|
+
key
|
|
192
|
+
name
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
`;
|
|
196
|
+
|
|
197
|
+
const TEAM_STATES_QUERY = `
|
|
198
|
+
query TeamStates($id: String!, $first: Int!) {
|
|
199
|
+
team(id: $id) {
|
|
200
|
+
id
|
|
201
|
+
key
|
|
202
|
+
name
|
|
203
|
+
states(first: $first) {
|
|
204
|
+
nodes {
|
|
205
|
+
id
|
|
206
|
+
name
|
|
207
|
+
type
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
`;
|
|
213
|
+
|
|
156
214
|
const PROJECT_CREATE_MUTATION = `
|
|
157
215
|
mutation ProjectCreate($input: ProjectCreateInput!) {
|
|
158
216
|
projectCreate(input: $input) {
|
|
@@ -349,12 +407,383 @@ const DOCUMENT_CREATE_MUTATION = `
|
|
|
349
407
|
}
|
|
350
408
|
`;
|
|
351
409
|
|
|
352
|
-
const DOCUMENT_UPDATE_MUTATION = `
|
|
353
|
-
mutation DocumentUpdate($id: String!, $input: DocumentUpdateInput!) {
|
|
354
|
-
documentUpdate(id: $id, input: $input) {
|
|
410
|
+
const DOCUMENT_UPDATE_MUTATION = `
|
|
411
|
+
mutation DocumentUpdate($id: String!, $input: DocumentUpdateInput!) {
|
|
412
|
+
documentUpdate(id: $id, input: $input) {
|
|
413
|
+
success
|
|
414
|
+
document {
|
|
415
|
+
id
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
`;
|
|
420
|
+
|
|
421
|
+
const ISSUE_MINIMAL_QUERY = `
|
|
422
|
+
query IssueMinimal($id: String!) {
|
|
423
|
+
issue(id: $id) {
|
|
424
|
+
id
|
|
425
|
+
identifier
|
|
426
|
+
title
|
|
427
|
+
description
|
|
428
|
+
url
|
|
429
|
+
branchName
|
|
430
|
+
priority
|
|
431
|
+
estimate
|
|
432
|
+
createdAt
|
|
433
|
+
updatedAt
|
|
434
|
+
state {
|
|
435
|
+
id
|
|
436
|
+
name
|
|
437
|
+
type
|
|
438
|
+
}
|
|
439
|
+
team {
|
|
440
|
+
id
|
|
441
|
+
key
|
|
442
|
+
name
|
|
443
|
+
}
|
|
444
|
+
project {
|
|
445
|
+
id
|
|
446
|
+
name
|
|
447
|
+
}
|
|
448
|
+
projectMilestone {
|
|
449
|
+
id
|
|
450
|
+
name
|
|
451
|
+
}
|
|
452
|
+
assignee {
|
|
453
|
+
id
|
|
454
|
+
name
|
|
455
|
+
displayName
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
`;
|
|
460
|
+
|
|
461
|
+
const ISSUE_MINIMAL_BY_TEAM_AND_NUMBER_QUERY = `
|
|
462
|
+
query IssueMinimalByTeamAndNumber($teamKey: String!, $number: Float!) {
|
|
463
|
+
issues(first: 1, filter: { team: { key: { eq: $teamKey } }, number: { eq: $number } }) {
|
|
464
|
+
nodes {
|
|
465
|
+
id
|
|
466
|
+
identifier
|
|
467
|
+
title
|
|
468
|
+
description
|
|
469
|
+
url
|
|
470
|
+
branchName
|
|
471
|
+
priority
|
|
472
|
+
estimate
|
|
473
|
+
createdAt
|
|
474
|
+
updatedAt
|
|
475
|
+
state {
|
|
476
|
+
id
|
|
477
|
+
name
|
|
478
|
+
type
|
|
479
|
+
}
|
|
480
|
+
team {
|
|
481
|
+
id
|
|
482
|
+
key
|
|
483
|
+
name
|
|
484
|
+
}
|
|
485
|
+
project {
|
|
486
|
+
id
|
|
487
|
+
name
|
|
488
|
+
}
|
|
489
|
+
projectMilestone {
|
|
490
|
+
id
|
|
491
|
+
name
|
|
492
|
+
}
|
|
493
|
+
assignee {
|
|
494
|
+
id
|
|
495
|
+
name
|
|
496
|
+
displayName
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
`;
|
|
502
|
+
|
|
503
|
+
const ISSUE_DETAILS_QUERY = `
|
|
504
|
+
query IssueDetails($id: String!) {
|
|
505
|
+
issue(id: $id) {
|
|
506
|
+
id
|
|
507
|
+
identifier
|
|
508
|
+
title
|
|
509
|
+
description
|
|
510
|
+
url
|
|
511
|
+
branchName
|
|
512
|
+
priority
|
|
513
|
+
estimate
|
|
514
|
+
createdAt
|
|
515
|
+
updatedAt
|
|
516
|
+
state {
|
|
517
|
+
id
|
|
518
|
+
name
|
|
519
|
+
color
|
|
520
|
+
type
|
|
521
|
+
}
|
|
522
|
+
team {
|
|
523
|
+
id
|
|
524
|
+
key
|
|
525
|
+
name
|
|
526
|
+
}
|
|
527
|
+
project {
|
|
528
|
+
id
|
|
529
|
+
name
|
|
530
|
+
}
|
|
531
|
+
projectMilestone {
|
|
532
|
+
id
|
|
533
|
+
name
|
|
534
|
+
}
|
|
535
|
+
assignee {
|
|
536
|
+
id
|
|
537
|
+
name
|
|
538
|
+
displayName
|
|
539
|
+
}
|
|
540
|
+
creator {
|
|
541
|
+
id
|
|
542
|
+
name
|
|
543
|
+
displayName
|
|
544
|
+
}
|
|
545
|
+
labels(first: 50) {
|
|
546
|
+
nodes {
|
|
547
|
+
id
|
|
548
|
+
name
|
|
549
|
+
color
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
parent {
|
|
553
|
+
id
|
|
554
|
+
identifier
|
|
555
|
+
title
|
|
556
|
+
state {
|
|
557
|
+
id
|
|
558
|
+
name
|
|
559
|
+
color
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
children(first: 50) {
|
|
563
|
+
nodes {
|
|
564
|
+
id
|
|
565
|
+
identifier
|
|
566
|
+
title
|
|
567
|
+
state {
|
|
568
|
+
id
|
|
569
|
+
name
|
|
570
|
+
color
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
attachments(first: 50) {
|
|
575
|
+
nodes {
|
|
576
|
+
id
|
|
577
|
+
title
|
|
578
|
+
url
|
|
579
|
+
subtitle
|
|
580
|
+
sourceType
|
|
581
|
+
createdAt
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
`;
|
|
587
|
+
|
|
588
|
+
const ISSUE_DETAILS_WITH_COMMENTS_QUERY = `
|
|
589
|
+
query IssueDetailsWithComments($id: String!) {
|
|
590
|
+
issue(id: $id) {
|
|
591
|
+
id
|
|
592
|
+
identifier
|
|
593
|
+
title
|
|
594
|
+
description
|
|
595
|
+
url
|
|
596
|
+
branchName
|
|
597
|
+
priority
|
|
598
|
+
estimate
|
|
599
|
+
createdAt
|
|
600
|
+
updatedAt
|
|
601
|
+
state {
|
|
602
|
+
id
|
|
603
|
+
name
|
|
604
|
+
color
|
|
605
|
+
type
|
|
606
|
+
}
|
|
607
|
+
team {
|
|
608
|
+
id
|
|
609
|
+
key
|
|
610
|
+
name
|
|
611
|
+
}
|
|
612
|
+
project {
|
|
613
|
+
id
|
|
614
|
+
name
|
|
615
|
+
}
|
|
616
|
+
projectMilestone {
|
|
617
|
+
id
|
|
618
|
+
name
|
|
619
|
+
}
|
|
620
|
+
assignee {
|
|
621
|
+
id
|
|
622
|
+
name
|
|
623
|
+
displayName
|
|
624
|
+
}
|
|
625
|
+
creator {
|
|
626
|
+
id
|
|
627
|
+
name
|
|
628
|
+
displayName
|
|
629
|
+
}
|
|
630
|
+
labels(first: 50) {
|
|
631
|
+
nodes {
|
|
632
|
+
id
|
|
633
|
+
name
|
|
634
|
+
color
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
parent {
|
|
638
|
+
id
|
|
639
|
+
identifier
|
|
640
|
+
title
|
|
641
|
+
state {
|
|
642
|
+
id
|
|
643
|
+
name
|
|
644
|
+
color
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
children(first: 50) {
|
|
648
|
+
nodes {
|
|
649
|
+
id
|
|
650
|
+
identifier
|
|
651
|
+
title
|
|
652
|
+
state {
|
|
653
|
+
id
|
|
654
|
+
name
|
|
655
|
+
color
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
comments(first: 100) {
|
|
660
|
+
nodes {
|
|
661
|
+
id
|
|
662
|
+
body
|
|
663
|
+
createdAt
|
|
664
|
+
updatedAt
|
|
665
|
+
user {
|
|
666
|
+
id
|
|
667
|
+
name
|
|
668
|
+
displayName
|
|
669
|
+
}
|
|
670
|
+
externalUser {
|
|
671
|
+
id
|
|
672
|
+
name
|
|
673
|
+
displayName
|
|
674
|
+
}
|
|
675
|
+
parent {
|
|
676
|
+
id
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
attachments(first: 50) {
|
|
681
|
+
nodes {
|
|
682
|
+
id
|
|
683
|
+
title
|
|
684
|
+
url
|
|
685
|
+
subtitle
|
|
686
|
+
sourceType
|
|
687
|
+
createdAt
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
`;
|
|
693
|
+
|
|
694
|
+
const ISSUE_CREATE_MUTATION = `
|
|
695
|
+
mutation IssueCreate($input: IssueCreateInput!) {
|
|
696
|
+
issueCreate(input: $input) {
|
|
697
|
+
success
|
|
698
|
+
issue {
|
|
699
|
+
id
|
|
700
|
+
identifier
|
|
701
|
+
title
|
|
702
|
+
description
|
|
703
|
+
url
|
|
704
|
+
branchName
|
|
705
|
+
priority
|
|
706
|
+
estimate
|
|
707
|
+
createdAt
|
|
708
|
+
updatedAt
|
|
709
|
+
state {
|
|
710
|
+
id
|
|
711
|
+
name
|
|
712
|
+
type
|
|
713
|
+
}
|
|
714
|
+
team {
|
|
715
|
+
id
|
|
716
|
+
key
|
|
717
|
+
name
|
|
718
|
+
}
|
|
719
|
+
project {
|
|
720
|
+
id
|
|
721
|
+
name
|
|
722
|
+
}
|
|
723
|
+
projectMilestone {
|
|
724
|
+
id
|
|
725
|
+
name
|
|
726
|
+
}
|
|
727
|
+
assignee {
|
|
728
|
+
id
|
|
729
|
+
name
|
|
730
|
+
displayName
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
`;
|
|
736
|
+
|
|
737
|
+
const ISSUE_UPDATE_MUTATION = `
|
|
738
|
+
mutation IssueUpdate($id: String!, $input: IssueUpdateInput!) {
|
|
739
|
+
issueUpdate(id: $id, input: $input) {
|
|
740
|
+
success
|
|
741
|
+
issue {
|
|
742
|
+
id
|
|
743
|
+
identifier
|
|
744
|
+
title
|
|
745
|
+
description
|
|
746
|
+
url
|
|
747
|
+
branchName
|
|
748
|
+
priority
|
|
749
|
+
estimate
|
|
750
|
+
createdAt
|
|
751
|
+
updatedAt
|
|
752
|
+
state {
|
|
753
|
+
id
|
|
754
|
+
name
|
|
755
|
+
type
|
|
756
|
+
}
|
|
757
|
+
team {
|
|
758
|
+
id
|
|
759
|
+
key
|
|
760
|
+
name
|
|
761
|
+
}
|
|
762
|
+
project {
|
|
763
|
+
id
|
|
764
|
+
name
|
|
765
|
+
}
|
|
766
|
+
projectMilestone {
|
|
767
|
+
id
|
|
768
|
+
name
|
|
769
|
+
}
|
|
770
|
+
assignee {
|
|
771
|
+
id
|
|
772
|
+
name
|
|
773
|
+
displayName
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
`;
|
|
779
|
+
|
|
780
|
+
const ISSUE_DELETE_MUTATION = `
|
|
781
|
+
mutation IssueDelete($id: String!) {
|
|
782
|
+
issueDelete(id: $id) {
|
|
355
783
|
success
|
|
356
|
-
|
|
784
|
+
entity {
|
|
357
785
|
id
|
|
786
|
+
identifier
|
|
358
787
|
}
|
|
359
788
|
}
|
|
360
789
|
}
|
|
@@ -490,10 +919,15 @@ async function executeOptimizedQuery(client, query, variables) {
|
|
|
490
919
|
};
|
|
491
920
|
}
|
|
492
921
|
|
|
493
|
-
|
|
494
|
-
|
|
922
|
+
function getRawRequest(client) {
|
|
923
|
+
return (
|
|
495
924
|
(typeof client.client?.rawRequest === 'function' ? client.client.rawRequest.bind(client.client) : null) ||
|
|
496
|
-
(typeof client.rawRequest === 'function' ? client.rawRequest.bind(client) : null)
|
|
925
|
+
(typeof client.rawRequest === 'function' ? client.rawRequest.bind(client) : null)
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
async function executeGraphQL(client, query, variables = {}) {
|
|
930
|
+
const rawRequest = getRawRequest(client);
|
|
497
931
|
|
|
498
932
|
if (!rawRequest) {
|
|
499
933
|
throw new Error('GraphQL rawRequest is unavailable on this Linear client');
|
|
@@ -520,6 +954,9 @@ function transformRawIssue(rawIssue) {
|
|
|
520
954
|
url: rawIssue.url,
|
|
521
955
|
branchName: rawIssue.branchName,
|
|
522
956
|
priority: rawIssue.priority,
|
|
957
|
+
estimate: rawIssue.estimate ?? null,
|
|
958
|
+
createdAt: rawIssue.createdAt ?? null,
|
|
959
|
+
updatedAt: rawIssue.updatedAt ?? null,
|
|
523
960
|
state: rawIssue.state ? { id: rawIssue.state.id, name: rawIssue.state.name, type: rawIssue.state.type } : null,
|
|
524
961
|
team: rawIssue.team ? { id: rawIssue.team.id, key: rawIssue.team.key, name: rawIssue.team.name } : null,
|
|
525
962
|
project: rawIssue.project ? { id: rawIssue.project.id, name: rawIssue.project.name } : null,
|
|
@@ -528,6 +965,288 @@ function transformRawIssue(rawIssue) {
|
|
|
528
965
|
};
|
|
529
966
|
}
|
|
530
967
|
|
|
968
|
+
function parseIssueIdentifierLookup(lookup) {
|
|
969
|
+
const match = String(lookup || '').trim().match(/^([A-Za-z0-9]+)-(\d+)$/);
|
|
970
|
+
if (!match) {
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
return {
|
|
975
|
+
teamKey: match[1].toUpperCase(),
|
|
976
|
+
number: Number.parseInt(match[2], 10),
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
async function fetchIssueMinimalById(client, issueId) {
|
|
981
|
+
if (getRawRequest(client)) {
|
|
982
|
+
try {
|
|
983
|
+
const data = await executeGraphQL(client, ISSUE_MINIMAL_QUERY, { id: issueId });
|
|
984
|
+
const transformed = transformRawIssue(data?.issue ?? null);
|
|
985
|
+
if (transformed) {
|
|
986
|
+
return transformed;
|
|
987
|
+
}
|
|
988
|
+
} catch {
|
|
989
|
+
// Fall back to SDK read below.
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
const sdkIssue = await client.issue?.(issueId);
|
|
994
|
+
return sdkIssue ? transformIssue(sdkIssue) : null;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
async function fetchIssueMinimalByIdentifier(client, identifier) {
|
|
998
|
+
if (getRawRequest(client)) {
|
|
999
|
+
const parsed = parseIssueIdentifierLookup(identifier);
|
|
1000
|
+
if (parsed) {
|
|
1001
|
+
try {
|
|
1002
|
+
const data = await executeGraphQL(client, ISSUE_MINIMAL_BY_TEAM_AND_NUMBER_QUERY, parsed);
|
|
1003
|
+
const transformed = transformRawIssue(data?.issues?.nodes?.[0] ?? null);
|
|
1004
|
+
if (transformed) {
|
|
1005
|
+
return transformed;
|
|
1006
|
+
}
|
|
1007
|
+
} catch {
|
|
1008
|
+
// Fall back to SDK read below.
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
const sdkIssue = await client.issue?.(identifier);
|
|
1014
|
+
return sdkIssue ? transformIssue(sdkIssue) : null;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
async function fetchIssueMinimal(client, lookup) {
|
|
1018
|
+
return isLinearId(lookup)
|
|
1019
|
+
? fetchIssueMinimalById(client, lookup)
|
|
1020
|
+
: fetchIssueMinimalByIdentifier(client, lookup);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
async function performIssueUpdate(client, issueId, updateInput) {
|
|
1024
|
+
if (getRawRequest(client)) {
|
|
1025
|
+
const payload = await executeGraphQL(client, ISSUE_UPDATE_MUTATION, {
|
|
1026
|
+
id: issueId,
|
|
1027
|
+
input: updateInput,
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
if (!payload?.issueUpdate?.success) {
|
|
1031
|
+
throw new Error('Failed to update issue');
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
return transformRawIssue(payload.issueUpdate.issue ?? null);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const sdkIssue = await client.issue(issueId);
|
|
1038
|
+
if (!sdkIssue) {
|
|
1039
|
+
throw new Error(`Issue not found: ${issueId}`);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const result = await sdkIssue.update(updateInput);
|
|
1043
|
+
if (!result.success) {
|
|
1044
|
+
throw new Error('Failed to update issue');
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
return transformIssue(result.issue);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function transformRawIssueDetails(rawIssue, options = {}) {
|
|
1051
|
+
const { includeComments = true } = options;
|
|
1052
|
+
if (!rawIssue) return null;
|
|
1053
|
+
|
|
1054
|
+
return {
|
|
1055
|
+
identifier: rawIssue.identifier,
|
|
1056
|
+
title: rawIssue.title,
|
|
1057
|
+
description: rawIssue.description ?? null,
|
|
1058
|
+
url: rawIssue.url ?? null,
|
|
1059
|
+
branchName: rawIssue.branchName ?? null,
|
|
1060
|
+
priority: rawIssue.priority ?? null,
|
|
1061
|
+
estimate: rawIssue.estimate ?? null,
|
|
1062
|
+
createdAt: rawIssue.createdAt ?? null,
|
|
1063
|
+
updatedAt: rawIssue.updatedAt ?? null,
|
|
1064
|
+
state: rawIssue.state ? {
|
|
1065
|
+
name: rawIssue.state.name,
|
|
1066
|
+
color: rawIssue.state.color ?? null,
|
|
1067
|
+
type: rawIssue.state.type ?? null,
|
|
1068
|
+
} : null,
|
|
1069
|
+
team: rawIssue.team ? { id: rawIssue.team.id, key: rawIssue.team.key, name: rawIssue.team.name } : null,
|
|
1070
|
+
project: rawIssue.project ? { id: rawIssue.project.id, name: rawIssue.project.name } : null,
|
|
1071
|
+
projectMilestone: rawIssue.projectMilestone ? { id: rawIssue.projectMilestone.id, name: rawIssue.projectMilestone.name } : null,
|
|
1072
|
+
assignee: rawIssue.assignee ? { id: rawIssue.assignee.id, name: rawIssue.assignee.name, displayName: rawIssue.assignee.displayName } : null,
|
|
1073
|
+
creator: rawIssue.creator ? { id: rawIssue.creator.id, name: rawIssue.creator.name, displayName: rawIssue.creator.displayName } : null,
|
|
1074
|
+
labels: (rawIssue.labels?.nodes || []).map((label) => ({
|
|
1075
|
+
id: label.id,
|
|
1076
|
+
name: label.name,
|
|
1077
|
+
color: label.color ?? null,
|
|
1078
|
+
})),
|
|
1079
|
+
parent: rawIssue.parent ? {
|
|
1080
|
+
identifier: rawIssue.parent.identifier,
|
|
1081
|
+
title: rawIssue.parent.title,
|
|
1082
|
+
state: rawIssue.parent.state ? {
|
|
1083
|
+
name: rawIssue.parent.state.name,
|
|
1084
|
+
color: rawIssue.parent.state.color ?? null,
|
|
1085
|
+
} : null,
|
|
1086
|
+
} : null,
|
|
1087
|
+
children: (rawIssue.children?.nodes || []).map((child) => ({
|
|
1088
|
+
identifier: child.identifier,
|
|
1089
|
+
title: child.title,
|
|
1090
|
+
state: child.state ? {
|
|
1091
|
+
name: child.state.name,
|
|
1092
|
+
color: child.state.color ?? null,
|
|
1093
|
+
} : null,
|
|
1094
|
+
})),
|
|
1095
|
+
comments: includeComments
|
|
1096
|
+
? (rawIssue.comments?.nodes || []).map((comment) => ({
|
|
1097
|
+
id: comment.id,
|
|
1098
|
+
body: comment.body,
|
|
1099
|
+
createdAt: comment.createdAt,
|
|
1100
|
+
updatedAt: comment.updatedAt,
|
|
1101
|
+
user: comment.user ? {
|
|
1102
|
+
name: comment.user.name,
|
|
1103
|
+
displayName: comment.user.displayName,
|
|
1104
|
+
} : null,
|
|
1105
|
+
externalUser: comment.externalUser ? {
|
|
1106
|
+
name: comment.externalUser.name,
|
|
1107
|
+
displayName: comment.externalUser.displayName,
|
|
1108
|
+
} : null,
|
|
1109
|
+
parent: comment.parent ? { id: comment.parent.id } : null,
|
|
1110
|
+
}))
|
|
1111
|
+
: [],
|
|
1112
|
+
attachments: (rawIssue.attachments?.nodes || []).map((attachment) => ({
|
|
1113
|
+
id: attachment.id,
|
|
1114
|
+
title: attachment.title,
|
|
1115
|
+
url: attachment.url,
|
|
1116
|
+
subtitle: attachment.subtitle,
|
|
1117
|
+
sourceType: attachment.sourceType,
|
|
1118
|
+
createdAt: attachment.createdAt,
|
|
1119
|
+
})),
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function transformRawProjectMinimal(rawProject) {
|
|
1124
|
+
if (!rawProject) return null;
|
|
1125
|
+
|
|
1126
|
+
return {
|
|
1127
|
+
id: rawProject.id,
|
|
1128
|
+
name: rawProject.name,
|
|
1129
|
+
slugId: rawProject.slugId ?? null,
|
|
1130
|
+
archivedAt: rawProject.archivedAt ?? null,
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function transformRawMilestone(rawMilestone, project = null) {
|
|
1135
|
+
if (!rawMilestone) return null;
|
|
1136
|
+
|
|
1137
|
+
return {
|
|
1138
|
+
id: rawMilestone.id,
|
|
1139
|
+
name: rawMilestone.name,
|
|
1140
|
+
description: rawMilestone.description ?? null,
|
|
1141
|
+
progress: rawMilestone.progress ?? null,
|
|
1142
|
+
order: rawMilestone.sortOrder ?? null,
|
|
1143
|
+
targetDate: rawMilestone.targetDate ?? null,
|
|
1144
|
+
status: rawMilestone.status ?? null,
|
|
1145
|
+
project: project ? { id: project.id, name: project.name } : null,
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
async function fetchProjectMinimal(client, projectId) {
|
|
1150
|
+
// If input looks like a UUID but can't be resolved directly,
|
|
1151
|
+
// it might be a project name that accidentally matches the UUID pattern.
|
|
1152
|
+
// Return null to signal that resolveProjectRef should fall back to name search.
|
|
1153
|
+
if (isLinearId(projectId)) {
|
|
1154
|
+
if (!getRawRequest(client)) {
|
|
1155
|
+
const sdkProject = await client.project?.(projectId);
|
|
1156
|
+
if (sdkProject) {
|
|
1157
|
+
return transformRawProjectMinimal(sdkProject);
|
|
1158
|
+
}
|
|
1159
|
+
// SDK can't resolve it - return null so resolveProjectRef falls back to name lookup
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
const data = await executeGraphQL(client, PROJECT_MINIMAL_QUERY, { id: projectId });
|
|
1164
|
+
if (data?.project) {
|
|
1165
|
+
return transformRawProjectMinimal(data.project);
|
|
1166
|
+
}
|
|
1167
|
+
// GraphQL can't resolve it - return null so resolveProjectRef falls back to name lookup
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// For non-UUID inputs (names), return null to let resolveProjectRef handle it directly
|
|
1172
|
+
return null;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
async function fetchTeamMinimal(client, teamId) {
|
|
1176
|
+
if (getRawRequest(client)) {
|
|
1177
|
+
try {
|
|
1178
|
+
const data = await executeGraphQL(client, TEAM_MINIMAL_QUERY, { id: teamId });
|
|
1179
|
+
const team = data?.team;
|
|
1180
|
+
if (team) {
|
|
1181
|
+
return { id: team.id, key: team.key, name: team.name };
|
|
1182
|
+
}
|
|
1183
|
+
} catch {
|
|
1184
|
+
// Fall back to SDK read below.
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const sdkTeam = await client.team?.(teamId);
|
|
1189
|
+
return sdkTeam ? { id: sdkTeam.id, key: sdkTeam.key, name: sdkTeam.name } : null;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
async function fetchTeamStatesByQuery(client, teamId, options = {}) {
|
|
1193
|
+
const { first = 50 } = options;
|
|
1194
|
+
|
|
1195
|
+
if (getRawRequest(client)) {
|
|
1196
|
+
try {
|
|
1197
|
+
const data = await executeGraphQL(client, TEAM_STATES_QUERY, { id: teamId, first });
|
|
1198
|
+
if (data?.team) {
|
|
1199
|
+
return (data.team.states?.nodes || []).map((state) => ({
|
|
1200
|
+
id: state.id,
|
|
1201
|
+
name: state.name,
|
|
1202
|
+
type: state.type,
|
|
1203
|
+
}));
|
|
1204
|
+
}
|
|
1205
|
+
} catch {
|
|
1206
|
+
// Fall back to SDK read below.
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
const team = await client.team?.(teamId);
|
|
1211
|
+
if (!team) {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
const result = await team.states();
|
|
1216
|
+
return (result.nodes || []).map((state) => ({
|
|
1217
|
+
id: state.id,
|
|
1218
|
+
name: state.name,
|
|
1219
|
+
type: state.type,
|
|
1220
|
+
}));
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
async function fetchProjectMilestonesByQuery(client, projectId, options = {}) {
|
|
1224
|
+
const { first = 250 } = options;
|
|
1225
|
+
|
|
1226
|
+
if (!getRawRequest(client)) {
|
|
1227
|
+
const project = await client.project?.(projectId);
|
|
1228
|
+
if (!project) {
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
const result = await project.projectMilestones();
|
|
1233
|
+
const nodes = result.nodes || [];
|
|
1234
|
+
return Promise.all(nodes.map(transformMilestone));
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const data = await executeGraphQL(client, PROJECT_MILESTONES_QUERY, {
|
|
1238
|
+
id: projectId,
|
|
1239
|
+
first,
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
if (!data?.project) {
|
|
1243
|
+
return null;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const project = { id: data.project.id, name: data.project.name };
|
|
1247
|
+
return (data.project.projectMilestones?.nodes || []).map((milestone) => transformRawMilestone(milestone, project));
|
|
1248
|
+
}
|
|
1249
|
+
|
|
531
1250
|
// ===== RATE LIMIT TRACKING =====
|
|
532
1251
|
|
|
533
1252
|
/**
|
|
@@ -1084,6 +1803,52 @@ function resolveProjectMilestoneIdFromInput(milestones, milestoneInput) {
|
|
|
1084
1803
|
throw new Error(`Milestone not found in project: ${target}`);
|
|
1085
1804
|
}
|
|
1086
1805
|
|
|
1806
|
+
/**
|
|
1807
|
+
* Resolve a milestone reference (name or ID) to a milestone object with id and name.
|
|
1808
|
+
* Requires project context to search for milestones by name.
|
|
1809
|
+
* @param {LinearClient} client - Linear SDK client
|
|
1810
|
+
* @param {string} milestoneRef - Milestone name or ID
|
|
1811
|
+
* @param {string} projectId - Project ID to search within
|
|
1812
|
+
* @returns {Promise<{id: string, name: string}>}
|
|
1813
|
+
*/
|
|
1814
|
+
export async function resolveMilestoneRef(client, milestoneRef, projectId) {
|
|
1815
|
+
const ref = String(milestoneRef || '').trim();
|
|
1816
|
+
if (!ref) {
|
|
1817
|
+
throw new Error('Missing milestone reference');
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// If it's already a Linear ID (UUID format with 16+ hex chars), try to fetch it directly
|
|
1821
|
+
if (/^[0-9a-fA-F-]{16,}$/.test(ref)) {
|
|
1822
|
+
try {
|
|
1823
|
+
const milestone = await client.projectMilestone(ref);
|
|
1824
|
+
if (milestone) {
|
|
1825
|
+
return { id: milestone.id, name: milestone.name };
|
|
1826
|
+
}
|
|
1827
|
+
} catch {
|
|
1828
|
+
// fall through to name lookup
|
|
1829
|
+
}
|
|
1830
|
+
throw new Error(`Milestone not found: ${ref}`);
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// Search by name in the project's milestones
|
|
1834
|
+
const milestones = await fetchProjectMilestones(client, projectId);
|
|
1835
|
+
|
|
1836
|
+
// Try exact name match first
|
|
1837
|
+
const exactMatch = milestones.find((m) => m.name === ref);
|
|
1838
|
+
if (exactMatch) {
|
|
1839
|
+
return { id: exactMatch.id, name: exactMatch.name };
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
// Try case-insensitive match
|
|
1843
|
+
const lowerRef = ref.toLowerCase();
|
|
1844
|
+
const caseInsensitiveMatch = milestones.find((m) => m.name?.toLowerCase() === lowerRef);
|
|
1845
|
+
if (caseInsensitiveMatch) {
|
|
1846
|
+
return { id: caseInsensitiveMatch.id, name: caseInsensitiveMatch.name };
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
throw new Error(`Milestone not found: ${ref}. Available milestones: ${milestones.map((m) => m.name).join(', ')}`);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1087
1852
|
function normalizeIssueRefList(value) {
|
|
1088
1853
|
if (value === undefined || value === null) return [];
|
|
1089
1854
|
if (Array.isArray(value)) {
|
|
@@ -1368,17 +2133,18 @@ export async function resolveTeamRef(client, teamRef) {
|
|
|
1368
2133
|
throw new Error('Missing team reference');
|
|
1369
2134
|
}
|
|
1370
2135
|
|
|
1371
|
-
// If it looks like a Linear ID (UUID), try
|
|
2136
|
+
// If it looks like a Linear ID (UUID), try a minimal GraphQL lookup first.
|
|
1372
2137
|
if (isLinearId(ref)) {
|
|
1373
2138
|
try {
|
|
1374
|
-
const direct = await client
|
|
2139
|
+
const direct = await fetchTeamMinimal(client, ref);
|
|
1375
2140
|
if (direct) {
|
|
1376
|
-
return
|
|
2141
|
+
return direct;
|
|
1377
2142
|
}
|
|
1378
2143
|
} catch {
|
|
1379
2144
|
// fall back to cached/full-team lookup below
|
|
1380
2145
|
}
|
|
1381
2146
|
|
|
2147
|
+
const teams = await fetchTeams(client);
|
|
1382
2148
|
const byId = teams.find((t) => t.id === ref);
|
|
1383
2149
|
if (byId) {
|
|
1384
2150
|
return byId;
|
|
@@ -1422,14 +2188,13 @@ export async function resolveIssue(client, issueRef) {
|
|
|
1422
2188
|
return withLinearErrorHandling(async () => {
|
|
1423
2189
|
const lookup = normalizeIssueLookupInput(issueRef);
|
|
1424
2190
|
|
|
1425
|
-
// The SDK's client.issue() method accepts both UUIDs and identifiers (ABC-123)
|
|
1426
2191
|
try {
|
|
1427
|
-
const issue = await client
|
|
2192
|
+
const issue = await fetchIssueMinimal(client, lookup);
|
|
1428
2193
|
if (issue) {
|
|
1429
|
-
return
|
|
2194
|
+
return issue;
|
|
1430
2195
|
}
|
|
1431
|
-
} catch
|
|
1432
|
-
// Fall through to error
|
|
2196
|
+
} catch {
|
|
2197
|
+
// Fall through to not-found error below
|
|
1433
2198
|
}
|
|
1434
2199
|
|
|
1435
2200
|
throw new Error(`Issue not found: ${lookup}`);
|
|
@@ -1448,18 +2213,11 @@ export async function getTeamWorkflowStates(client, teamRef) {
|
|
|
1448
2213
|
const cached = getCache(teamStatesCache, cacheKey);
|
|
1449
2214
|
if (cached) return cached;
|
|
1450
2215
|
|
|
1451
|
-
const
|
|
1452
|
-
if (!
|
|
2216
|
+
const mapped = await fetchTeamStatesByQuery(client, teamRef);
|
|
2217
|
+
if (!mapped) {
|
|
1453
2218
|
throw new Error(`Team not found: ${teamRef}`);
|
|
1454
2219
|
}
|
|
1455
2220
|
|
|
1456
|
-
const states = await team.states();
|
|
1457
|
-
const mapped = (states.nodes || []).map(s => ({
|
|
1458
|
-
id: s.id,
|
|
1459
|
-
name: s.name,
|
|
1460
|
-
type: s.type,
|
|
1461
|
-
}));
|
|
1462
|
-
|
|
1463
2221
|
setCache(teamStatesCache, cacheKey, mapped, CACHE_TTL_MS.teamStates);
|
|
1464
2222
|
return mapped;
|
|
1465
2223
|
}, 'getTeamWorkflowStates');
|
|
@@ -1479,12 +2237,12 @@ export async function resolveProjectRef(client, projectRef, options = {}) {
|
|
|
1479
2237
|
throw new Error('Missing project reference');
|
|
1480
2238
|
}
|
|
1481
2239
|
|
|
1482
|
-
// If it looks like a Linear ID (UUID), try
|
|
2240
|
+
// If it looks like a Linear ID (UUID), try a minimal GraphQL lookup first.
|
|
1483
2241
|
if (isLinearId(ref)) {
|
|
1484
2242
|
try {
|
|
1485
|
-
const direct = await client
|
|
2243
|
+
const direct = await fetchProjectMinimal(client, ref);
|
|
1486
2244
|
if (direct) {
|
|
1487
|
-
return
|
|
2245
|
+
return direct;
|
|
1488
2246
|
}
|
|
1489
2247
|
} catch {
|
|
1490
2248
|
// fall back to cached/full-project lookup below
|
|
@@ -1925,109 +2683,127 @@ export async function updateDocument(client, documentRef, patch = {}) {
|
|
|
1925
2683
|
* @param {boolean} [options.includeComments=true] - Include comments in response
|
|
1926
2684
|
* @returns {Promise<Object>} Issue details
|
|
1927
2685
|
*/
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
const { includeComments = true } = options;
|
|
2686
|
+
async function fetchIssueDetailsViaSdk(client, issueRef, options = {}) {
|
|
2687
|
+
const { includeComments = true } = options;
|
|
1931
2688
|
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
const sdkIssue = await client.issue(lookup);
|
|
2689
|
+
const lookup = normalizeIssueLookupInput(issueRef);
|
|
2690
|
+
const sdkIssue = await client.issue(lookup);
|
|
1935
2691
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
2692
|
+
if (!sdkIssue) {
|
|
2693
|
+
throw new Error(`Issue not found: ${lookup}`);
|
|
2694
|
+
}
|
|
1939
2695
|
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
]);
|
|
2696
|
+
const [
|
|
2697
|
+
state,
|
|
2698
|
+
team,
|
|
2699
|
+
project,
|
|
2700
|
+
projectMilestone,
|
|
2701
|
+
assignee,
|
|
2702
|
+
creator,
|
|
2703
|
+
labelsResult,
|
|
2704
|
+
parent,
|
|
2705
|
+
childrenResult,
|
|
2706
|
+
commentsResult,
|
|
2707
|
+
attachmentsResult,
|
|
2708
|
+
] = await Promise.all([
|
|
2709
|
+
sdkIssue.state?.catch?.(() => null) ?? sdkIssue.state,
|
|
2710
|
+
sdkIssue.team?.catch?.(() => null) ?? sdkIssue.team,
|
|
2711
|
+
sdkIssue.project?.catch?.(() => null) ?? sdkIssue.project,
|
|
2712
|
+
sdkIssue.projectMilestone?.catch?.(() => null) ?? sdkIssue.projectMilestone,
|
|
2713
|
+
sdkIssue.assignee?.catch?.(() => null) ?? sdkIssue.assignee,
|
|
2714
|
+
sdkIssue.creator?.catch?.(() => null) ?? sdkIssue.creator,
|
|
2715
|
+
sdkIssue.labels?.()?.catch?.(() => ({ nodes: [] })) ?? sdkIssue.labels?.() ?? { nodes: [] },
|
|
2716
|
+
sdkIssue.parent?.catch?.(() => null) ?? sdkIssue.parent,
|
|
2717
|
+
sdkIssue.children?.()?.catch?.(() => ({ nodes: [] })) ?? sdkIssue.children?.() ?? { nodes: [] },
|
|
2718
|
+
includeComments ? (sdkIssue.comments?.()?.catch?.(() => ({ nodes: [] })) ?? sdkIssue.comments?.() ?? { nodes: [] }) : Promise.resolve({ nodes: [] }),
|
|
2719
|
+
sdkIssue.attachments?.()?.catch?.(() => ({ nodes: [] })) ?? sdkIssue.attachments?.() ?? { nodes: [] },
|
|
2720
|
+
]);
|
|
1966
2721
|
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
}
|
|
2722
|
+
let transformedParent = null;
|
|
2723
|
+
if (parent) {
|
|
2724
|
+
const parentState = await parent.state?.catch?.(() => null) ?? parent.state;
|
|
2725
|
+
transformedParent = {
|
|
2726
|
+
identifier: parent.identifier,
|
|
2727
|
+
title: parent.title,
|
|
2728
|
+
state: parentState ? { name: parentState.name, color: parentState.color } : null,
|
|
2729
|
+
};
|
|
2730
|
+
}
|
|
1977
2731
|
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2732
|
+
const children = (childrenResult.nodes || []).map(c => ({
|
|
2733
|
+
identifier: c.identifier,
|
|
2734
|
+
title: c.title,
|
|
2735
|
+
state: c.state ? { name: c.state.name, color: c.state.color } : null,
|
|
2736
|
+
}));
|
|
2737
|
+
|
|
2738
|
+
const comments = (commentsResult.nodes || []).map(c => ({
|
|
2739
|
+
id: c.id,
|
|
2740
|
+
body: c.body,
|
|
2741
|
+
createdAt: c.createdAt,
|
|
2742
|
+
updatedAt: c.updatedAt,
|
|
2743
|
+
user: c.user ? { name: c.user.name, displayName: c.user.displayName } : null,
|
|
2744
|
+
externalUser: c.externalUser ? { name: c.externalUser.name, displayName: c.externalUser.displayName } : null,
|
|
2745
|
+
parent: c.parent ? { id: c.parent.id } : null,
|
|
2746
|
+
}));
|
|
2747
|
+
|
|
2748
|
+
const attachments = (attachmentsResult.nodes || []).map(a => ({
|
|
2749
|
+
id: a.id,
|
|
2750
|
+
title: a.title,
|
|
2751
|
+
url: a.url,
|
|
2752
|
+
subtitle: a.subtitle,
|
|
2753
|
+
sourceType: a.sourceType,
|
|
2754
|
+
createdAt: a.createdAt,
|
|
2755
|
+
}));
|
|
2756
|
+
|
|
2757
|
+
const labels = (labelsResult.nodes || []).map(l => ({
|
|
2758
|
+
id: l.id,
|
|
2759
|
+
name: l.name,
|
|
2760
|
+
color: l.color,
|
|
2761
|
+
}));
|
|
1983
2762
|
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
2763
|
+
return {
|
|
2764
|
+
identifier: sdkIssue.identifier,
|
|
2765
|
+
title: sdkIssue.title,
|
|
2766
|
+
description: sdkIssue.description,
|
|
2767
|
+
url: sdkIssue.url,
|
|
2768
|
+
branchName: sdkIssue.branchName,
|
|
2769
|
+
priority: sdkIssue.priority,
|
|
2770
|
+
estimate: sdkIssue.estimate,
|
|
2771
|
+
createdAt: sdkIssue.createdAt,
|
|
2772
|
+
updatedAt: sdkIssue.updatedAt,
|
|
2773
|
+
state: state ? { name: state.name, color: state.color, type: state.type } : null,
|
|
2774
|
+
team: team ? { id: team.id, key: team.key, name: team.name } : null,
|
|
2775
|
+
project: project ? { id: project.id, name: project.name } : null,
|
|
2776
|
+
projectMilestone: projectMilestone ? { id: projectMilestone.id, name: projectMilestone.name } : null,
|
|
2777
|
+
assignee: assignee ? { id: assignee.id, name: assignee.name, displayName: assignee.displayName } : null,
|
|
2778
|
+
creator: creator ? { id: creator.id, name: creator.name, displayName: creator.displayName } : null,
|
|
2779
|
+
labels,
|
|
2780
|
+
parent: transformedParent,
|
|
2781
|
+
children,
|
|
2782
|
+
comments,
|
|
2783
|
+
attachments,
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
1993
2786
|
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
url: a.url,
|
|
1998
|
-
subtitle: a.subtitle,
|
|
1999
|
-
sourceType: a.sourceType,
|
|
2000
|
-
createdAt: a.createdAt,
|
|
2001
|
-
}));
|
|
2787
|
+
export async function fetchIssueDetails(client, issueRef, options = {}) {
|
|
2788
|
+
return withLinearErrorHandling(async () => {
|
|
2789
|
+
const { includeComments = true } = options;
|
|
2002
2790
|
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
color: l.color,
|
|
2007
|
-
}));
|
|
2791
|
+
if (!getRawRequest(client)) {
|
|
2792
|
+
return fetchIssueDetailsViaSdk(client, issueRef, options);
|
|
2793
|
+
}
|
|
2008
2794
|
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
project: project ? { id: project.id, name: project.name } : null,
|
|
2022
|
-
projectMilestone: projectMilestone ? { id: projectMilestone.id, name: projectMilestone.name } : null,
|
|
2023
|
-
assignee: assignee ? { id: assignee.id, name: assignee.name, displayName: assignee.displayName } : null,
|
|
2024
|
-
creator: creator ? { id: creator.id, name: creator.name, displayName: creator.displayName } : null,
|
|
2025
|
-
labels,
|
|
2026
|
-
parent: transformedParent,
|
|
2027
|
-
children,
|
|
2028
|
-
comments,
|
|
2029
|
-
attachments,
|
|
2030
|
-
};
|
|
2795
|
+
const lookup = normalizeIssueLookupInput(issueRef);
|
|
2796
|
+
const issueId = isLinearId(lookup)
|
|
2797
|
+
? lookup
|
|
2798
|
+
: (await resolveIssue(client, lookup)).id;
|
|
2799
|
+
const query = includeComments ? ISSUE_DETAILS_WITH_COMMENTS_QUERY : ISSUE_DETAILS_QUERY;
|
|
2800
|
+
const data = await executeGraphQL(client, query, { id: issueId });
|
|
2801
|
+
|
|
2802
|
+
if (!data?.issue) {
|
|
2803
|
+
throw new Error(`Issue not found: ${lookup}`);
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
return transformRawIssueDetails(data.issue, { includeComments });
|
|
2031
2807
|
}, 'fetchIssueDetails');
|
|
2032
2808
|
}
|
|
2033
2809
|
|
|
@@ -2115,17 +2891,17 @@ export async function fetchIssueActivity(client, issueRef, options = {}) {
|
|
|
2115
2891
|
*/
|
|
2116
2892
|
export async function setIssueState(client, issueId, stateId) {
|
|
2117
2893
|
return withLinearErrorHandling(async () => {
|
|
2118
|
-
const
|
|
2119
|
-
if (
|
|
2120
|
-
|
|
2894
|
+
const updated = await performIssueUpdate(client, issueId, { stateId });
|
|
2895
|
+
if (updated) {
|
|
2896
|
+
return updated;
|
|
2121
2897
|
}
|
|
2122
2898
|
|
|
2123
|
-
const
|
|
2124
|
-
if (!
|
|
2125
|
-
throw new Error(
|
|
2899
|
+
const refreshed = await fetchIssueMinimalById(client, issueId);
|
|
2900
|
+
if (!refreshed) {
|
|
2901
|
+
throw new Error(`Issue not found: ${issueId}`);
|
|
2126
2902
|
}
|
|
2127
2903
|
|
|
2128
|
-
return
|
|
2904
|
+
return refreshed;
|
|
2129
2905
|
}, 'setIssueState');
|
|
2130
2906
|
}
|
|
2131
2907
|
|
|
@@ -2175,6 +2951,14 @@ export async function createIssue(client, input) {
|
|
|
2175
2951
|
createInput.priority = parsed;
|
|
2176
2952
|
}
|
|
2177
2953
|
|
|
2954
|
+
if (input.estimate !== undefined) {
|
|
2955
|
+
const parsed = Number.parseInt(String(input.estimate), 10);
|
|
2956
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
2957
|
+
throw new Error(`Invalid estimate: ${input.estimate}. Must be a non-negative integer.`);
|
|
2958
|
+
}
|
|
2959
|
+
createInput.estimate = parsed;
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2178
2962
|
if (input.assigneeId !== undefined) {
|
|
2179
2963
|
createInput.assigneeId = input.assigneeId;
|
|
2180
2964
|
}
|
|
@@ -2187,33 +2971,55 @@ export async function createIssue(client, input) {
|
|
|
2187
2971
|
createInput.stateId = input.stateId;
|
|
2188
2972
|
}
|
|
2189
2973
|
|
|
2190
|
-
|
|
2974
|
+
if (getRawRequest(client)) {
|
|
2975
|
+
const payload = await executeGraphQL(client, ISSUE_CREATE_MUTATION, { input: createInput });
|
|
2976
|
+
if (!payload?.issueCreate?.success) {
|
|
2977
|
+
throw new Error('Failed to create issue');
|
|
2978
|
+
}
|
|
2191
2979
|
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2980
|
+
const createdIssue = transformRawIssue(payload.issueCreate.issue ?? null);
|
|
2981
|
+
if (createdIssue) {
|
|
2982
|
+
return createdIssue;
|
|
2983
|
+
}
|
|
2984
|
+
} else {
|
|
2985
|
+
const result = await client.createIssue(createInput);
|
|
2195
2986
|
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|| result._issue?.id
|
|
2200
|
-
|| null;
|
|
2987
|
+
if (!result.success) {
|
|
2988
|
+
throw new Error('Failed to create issue');
|
|
2989
|
+
}
|
|
2201
2990
|
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2991
|
+
const createdIssueId =
|
|
2992
|
+
result.issue?.id
|
|
2993
|
+
|| result._issue?.id
|
|
2994
|
+
|| null;
|
|
2995
|
+
|
|
2996
|
+
if (createdIssueId) {
|
|
2997
|
+
try {
|
|
2998
|
+
const fullIssue = await fetchIssueMinimalById(client, createdIssueId);
|
|
2999
|
+
if (fullIssue) {
|
|
3000
|
+
return fullIssue;
|
|
3001
|
+
}
|
|
3002
|
+
} catch {
|
|
3003
|
+
// continue to fallback
|
|
2208
3004
|
}
|
|
2209
|
-
} catch {
|
|
2210
|
-
// continue to fallback
|
|
2211
3005
|
}
|
|
3006
|
+
|
|
3007
|
+
return {
|
|
3008
|
+
id: createdIssueId,
|
|
3009
|
+
identifier: null,
|
|
3010
|
+
title,
|
|
3011
|
+
description: input.description ?? null,
|
|
3012
|
+
url: null,
|
|
3013
|
+
priority: input.priority ?? null,
|
|
3014
|
+
state: null,
|
|
3015
|
+
team: null,
|
|
3016
|
+
project: null,
|
|
3017
|
+
assignee: null,
|
|
3018
|
+
};
|
|
2212
3019
|
}
|
|
2213
3020
|
|
|
2214
|
-
// Minimal fallback when SDK payload does not expose a resolvable issue ID.
|
|
2215
3021
|
return {
|
|
2216
|
-
id:
|
|
3022
|
+
id: null,
|
|
2217
3023
|
identifier: null,
|
|
2218
3024
|
title,
|
|
2219
3025
|
description: input.description ?? null,
|
|
@@ -2285,6 +3091,9 @@ function buildFallbackUpdatedIssue(targetIssue, updateInput) {
|
|
|
2285
3091
|
if (Object.prototype.hasOwnProperty.call(updateInput, 'priority')) {
|
|
2286
3092
|
fallback.priority = updateInput.priority;
|
|
2287
3093
|
}
|
|
3094
|
+
if (Object.prototype.hasOwnProperty.call(updateInput, 'estimate')) {
|
|
3095
|
+
fallback.estimate = updateInput.estimate;
|
|
3096
|
+
}
|
|
2288
3097
|
|
|
2289
3098
|
if (Object.prototype.hasOwnProperty.call(updateInput, 'assigneeId')) {
|
|
2290
3099
|
const assigneeId = updateInput.assigneeId;
|
|
@@ -2355,6 +3164,15 @@ export async function updateIssue(client, issueRef, patch = {}) {
|
|
|
2355
3164
|
updateInput.priority = parsed;
|
|
2356
3165
|
}
|
|
2357
3166
|
|
|
3167
|
+
|
|
3168
|
+
if (patch.estimate !== undefined) {
|
|
3169
|
+
const parsed = Number.parseInt(String(patch.estimate), 10);
|
|
3170
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
3171
|
+
throw new Error(`Invalid estimate: ${patch.estimate}. Must be a non-negative integer.`);
|
|
3172
|
+
}
|
|
3173
|
+
updateInput.estimate = parsed;
|
|
3174
|
+
}
|
|
3175
|
+
|
|
2358
3176
|
if (patch.state !== undefined) {
|
|
2359
3177
|
// Need to resolve state ID from team's workflow states
|
|
2360
3178
|
const team = targetIssue.team;
|
|
@@ -2409,14 +3227,7 @@ export async function updateIssue(client, issueRef, patch = {}) {
|
|
|
2409
3227
|
|
|
2410
3228
|
for (const childRef of parentOfRefs) {
|
|
2411
3229
|
const childIssue = await resolveIssue(client, childRef);
|
|
2412
|
-
|
|
2413
|
-
if (!childSdkIssue) {
|
|
2414
|
-
throw new Error(`Issue not found: ${childRef}`);
|
|
2415
|
-
}
|
|
2416
|
-
const rel = await childSdkIssue.update({ parentId: targetIssue.id });
|
|
2417
|
-
if (!rel.success) {
|
|
2418
|
-
throw new Error(`Failed to set parent for issue: ${childRef}`);
|
|
2419
|
-
}
|
|
3230
|
+
await performIssueUpdate(client, childIssue.id, { parentId: targetIssue.id });
|
|
2420
3231
|
}
|
|
2421
3232
|
|
|
2422
3233
|
for (const blockerRef of blockedByRefs) {
|
|
@@ -2455,16 +3266,7 @@ export async function updateIssue(client, issueRef, patch = {}) {
|
|
|
2455
3266
|
}
|
|
2456
3267
|
|
|
2457
3268
|
if (Object.keys(updateInput).length > 0) {
|
|
2458
|
-
|
|
2459
|
-
const sdkIssue = await client.issue(targetIssue.id);
|
|
2460
|
-
if (!sdkIssue) {
|
|
2461
|
-
throw new Error(`Issue not found: ${targetIssue.id}`);
|
|
2462
|
-
}
|
|
2463
|
-
|
|
2464
|
-
const result = await sdkIssue.update(updateInput);
|
|
2465
|
-
if (!result.success) {
|
|
2466
|
-
throw new Error('Failed to update issue');
|
|
2467
|
-
}
|
|
3269
|
+
await performIssueUpdate(client, targetIssue.id, updateInput);
|
|
2468
3270
|
}
|
|
2469
3271
|
|
|
2470
3272
|
for (const relationInput of relationCreates) {
|
|
@@ -2479,8 +3281,7 @@ export async function updateIssue(client, issueRef, patch = {}) {
|
|
|
2479
3281
|
let updatedIssue = null;
|
|
2480
3282
|
let usedRateLimitFallback = false;
|
|
2481
3283
|
try {
|
|
2482
|
-
|
|
2483
|
-
updatedIssue = await transformIssue(updatedSdkIssue);
|
|
3284
|
+
updatedIssue = await fetchIssueMinimalById(client, targetIssue.id);
|
|
2484
3285
|
} catch (refreshError) {
|
|
2485
3286
|
const refreshMessage = String(refreshError?.message || refreshError || 'unknown');
|
|
2486
3287
|
const isRefreshRateLimited = refreshError?.type === 'Ratelimited' || refreshMessage.toLowerCase().includes('rate limit');
|
|
@@ -2581,7 +3382,7 @@ async function transformMilestone(sdkMilestone) {
|
|
|
2581
3382
|
name: sdkMilestone.name,
|
|
2582
3383
|
description: sdkMilestone.description,
|
|
2583
3384
|
progress: sdkMilestone.progress,
|
|
2584
|
-
order: sdkMilestone.
|
|
3385
|
+
order: sdkMilestone.sortOrder,
|
|
2585
3386
|
targetDate: sdkMilestone.targetDate,
|
|
2586
3387
|
status: sdkMilestone.status,
|
|
2587
3388
|
project: project ? { id: project.id, name: project.name } : null,
|
|
@@ -2703,16 +3504,11 @@ function invalidateProjectsCache(client) {
|
|
|
2703
3504
|
*/
|
|
2704
3505
|
export async function fetchProjectMilestones(client, projectId) {
|
|
2705
3506
|
return withLinearErrorHandling(async () => {
|
|
2706
|
-
const
|
|
2707
|
-
if (!
|
|
3507
|
+
const milestones = await fetchProjectMilestonesByQuery(client, projectId);
|
|
3508
|
+
if (!milestones) {
|
|
2708
3509
|
throw new Error(`Project not found: ${projectId}`);
|
|
2709
3510
|
}
|
|
2710
3511
|
|
|
2711
|
-
const result = await project.projectMilestones();
|
|
2712
|
-
const nodes = result.nodes || [];
|
|
2713
|
-
|
|
2714
|
-
const milestones = await Promise.all(nodes.map(transformMilestone));
|
|
2715
|
-
|
|
2716
3512
|
debug('Fetched project milestones', {
|
|
2717
3513
|
projectId,
|
|
2718
3514
|
milestoneCount: milestones.length,
|
|
@@ -2766,7 +3562,7 @@ export async function fetchMilestoneDetails(client, milestoneId) {
|
|
|
2766
3562
|
name: milestone.name,
|
|
2767
3563
|
description: milestone.description,
|
|
2768
3564
|
progress: milestone.progress,
|
|
2769
|
-
order: milestone.
|
|
3565
|
+
order: milestone.sortOrder,
|
|
2770
3566
|
targetDate: milestone.targetDate,
|
|
2771
3567
|
status: milestone.status,
|
|
2772
3568
|
project: project ? { id: project.id, name: project.name } : null,
|
|
@@ -2847,7 +3643,7 @@ export async function createProjectMilestone(client, input) {
|
|
|
2847
3643
|
name: created?.name || name,
|
|
2848
3644
|
description: created?.description ?? input.description ?? null,
|
|
2849
3645
|
progress: created?.progress ?? 0,
|
|
2850
|
-
order: created?.
|
|
3646
|
+
order: created?.sortOrder ?? null,
|
|
2851
3647
|
targetDate: created?.targetDate ?? input.targetDate ?? null,
|
|
2852
3648
|
status: created?.status ?? input.status ?? 'backlogged',
|
|
2853
3649
|
project: null,
|
|
@@ -2944,16 +3740,21 @@ export async function deleteIssue(client, issueRef) {
|
|
|
2944
3740
|
return withLinearErrorHandling(async () => {
|
|
2945
3741
|
const targetIssue = await resolveIssue(client, issueRef);
|
|
2946
3742
|
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
3743
|
+
let success = false;
|
|
3744
|
+
if (getRawRequest(client)) {
|
|
3745
|
+
const payload = await executeGraphQL(client, ISSUE_DELETE_MUTATION, { id: targetIssue.id });
|
|
3746
|
+
success = payload?.issueDelete?.success === true;
|
|
3747
|
+
} else {
|
|
3748
|
+
const sdkIssue = await client.issue(targetIssue.id);
|
|
3749
|
+
if (!sdkIssue) {
|
|
3750
|
+
throw new Error(`Issue not found: ${targetIssue.id}`);
|
|
3751
|
+
}
|
|
3752
|
+
const result = await sdkIssue.delete();
|
|
3753
|
+
success = result.success;
|
|
2951
3754
|
}
|
|
2952
3755
|
|
|
2953
|
-
const result = await sdkIssue.delete();
|
|
2954
|
-
|
|
2955
3756
|
return {
|
|
2956
|
-
success
|
|
3757
|
+
success,
|
|
2957
3758
|
issueId: targetIssue.id,
|
|
2958
3759
|
identifier: targetIssue.identifier,
|
|
2959
3760
|
};
|