github-issue-tower-defence-management 1.112.6 → 1.113.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/bin/adapter/entry-points/cli/index.js +1 -0
  3. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  4. package/bin/adapter/entry-points/console/consoleReadApi.js +47 -1
  5. package/bin/adapter/entry-points/console/consoleReadApi.js.map +1 -1
  6. package/bin/adapter/entry-points/console/ui-dist/assets/{index-DKFbU8lr.css → index-CEJmPNRK.css} +1 -1
  7. package/bin/adapter/entry-points/console/ui-dist/assets/{index-DUygHx5u.js → index-DfNrA5uV.js} +36 -36
  8. package/bin/adapter/entry-points/console/ui-dist/index.html +2 -2
  9. package/bin/adapter/entry-points/console/webServer.js +6 -0
  10. package/bin/adapter/entry-points/console/webServer.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/adapter/entry-points/cli/index.ts +5 -1
  13. package/src/adapter/entry-points/console/consoleReadApi.test.ts +89 -0
  14. package/src/adapter/entry-points/console/consoleReadApi.ts +71 -0
  15. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +44 -0
  16. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +31 -8
  17. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.stories.tsx +41 -0
  18. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.test.tsx +56 -0
  19. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +15 -0
  20. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.test.tsx +46 -0
  21. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.tsx +8 -0
  22. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.stories.tsx +41 -0
  23. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.test.tsx +72 -0
  24. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.tsx +71 -0
  25. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +5 -0
  26. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +20 -0
  27. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.ts +17 -0
  28. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +41 -0
  29. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +36 -5
  30. package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +9 -0
  31. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +9 -0
  32. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +1 -0
  33. package/src/adapter/entry-points/console/ui/src/index.css +12 -0
  34. package/src/adapter/entry-points/console/ui-dist/assets/{index-DKFbU8lr.css → index-CEJmPNRK.css} +1 -1
  35. package/src/adapter/entry-points/console/ui-dist/assets/{index-DUygHx5u.js → index-DfNrA5uV.js} +36 -36
  36. package/src/adapter/entry-points/console/ui-dist/index.html +2 -2
  37. package/src/adapter/entry-points/console/webServer.test.ts +52 -1
  38. package/src/adapter/entry-points/console/webServer.ts +13 -0
  39. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  40. package/types/adapter/entry-points/console/consoleReadApi.d.ts +20 -0
  41. package/types/adapter/entry-points/console/consoleReadApi.d.ts.map +1 -1
  42. package/types/adapter/entry-points/console/webServer.d.ts +2 -1
  43. package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
@@ -38,6 +38,52 @@ describe('ConsolePullRequestDetail', () => {
38
38
  expect(getByText('27 files')).toBeInTheDocument();
39
39
  });
40
40
 
41
+ it('renders the CI status badge derived from the pull request fields', () => {
42
+ const { getByText } = render(
43
+ <ConsolePullRequestDetail
44
+ pullRequest={pullRequest}
45
+ body={pullRequest.summary?.body ?? ''}
46
+ bodyIsLoading={false}
47
+ files={consoleChangedFilesFixture}
48
+ filesAreLoading={false}
49
+ filesError={null}
50
+ commits={consoleCommitsFixture}
51
+ commitsAreLoading={false}
52
+ commitsError={null}
53
+ now={now}
54
+ />,
55
+ );
56
+ expect(getByText('CI passing')).toBeInTheDocument();
57
+ });
58
+
59
+ it('renders failing CI, conflict and out-of-date badges for an unhealthy pull request', () => {
60
+ const { getByText } = render(
61
+ <ConsolePullRequestDetail
62
+ pullRequest={{
63
+ ...pullRequest,
64
+ isConflicted: true,
65
+ isPassedAllCiJob: false,
66
+ isCiStateSuccess: false,
67
+ isBranchOutOfDate: true,
68
+ missingRequiredCheckNames: ['build'],
69
+ }}
70
+ body={pullRequest.summary?.body ?? ''}
71
+ bodyIsLoading={false}
72
+ files={consoleChangedFilesFixture}
73
+ filesAreLoading={false}
74
+ filesError={null}
75
+ commits={consoleCommitsFixture}
76
+ commitsAreLoading={false}
77
+ commitsError={null}
78
+ now={now}
79
+ />,
80
+ );
81
+ expect(getByText('CI failing')).toBeInTheDocument();
82
+ expect(getByText(/missing: build/)).toBeInTheDocument();
83
+ expect(getByText('Conflict')).toBeInTheDocument();
84
+ expect(getByText('Out of date')).toBeInTheDocument();
85
+ });
86
+
41
87
  it('renders a copy URL button for the pull request url', () => {
42
88
  const { getByRole } = render(
43
89
  <ConsolePullRequestDetail
@@ -10,6 +10,7 @@ import { ConsoleChangedFileList } from './ConsoleChangedFileList';
10
10
  import { ConsoleCommitList } from './ConsoleCommitList';
11
11
  import { ConsoleCopyUrlButton } from './ConsoleCopyUrlButton';
12
12
  import type { ConsoleAddInlineComment } from './ConsoleFileDiff';
13
+ import { ConsolePullRequestStatusBadges } from './ConsolePullRequestStatusBadges';
13
14
 
14
15
  export type ConsolePullRequestSectionProps = {
15
16
  pullRequest: ConsoleRelatedPullRequest;
@@ -59,6 +60,13 @@ export const ConsolePullRequestDetail = ({
59
60
  {pullRequest.isDraft && (
60
61
  <span className="console-pr-section-state">draft</span>
61
62
  )}
63
+ <ConsolePullRequestStatusBadges
64
+ isConflicted={pullRequest.isConflicted}
65
+ isPassedAllCiJob={pullRequest.isPassedAllCiJob}
66
+ isCiStateSuccess={pullRequest.isCiStateSuccess}
67
+ isBranchOutOfDate={pullRequest.isBranchOutOfDate}
68
+ missingRequiredCheckNames={pullRequest.missingRequiredCheckNames}
69
+ />
62
70
  <ConsoleCopyUrlButton url={pullRequest.url} label="Copy PR URL" />
63
71
  <div className="console-pr-statbar">
64
72
  {pullRequest.branchName !== null && (
@@ -0,0 +1,41 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ConsolePullRequestStatusBadges } from './ConsolePullRequestStatusBadges';
3
+
4
+ const meta: Meta<typeof ConsolePullRequestStatusBadges> = {
5
+ title: 'Console/ConsolePullRequestStatusBadges',
6
+ component: ConsolePullRequestStatusBadges,
7
+ };
8
+
9
+ export default meta;
10
+
11
+ type Story = StoryObj<typeof ConsolePullRequestStatusBadges>;
12
+
13
+ export const Passing: Story = {
14
+ args: {
15
+ isConflicted: false,
16
+ isPassedAllCiJob: true,
17
+ isCiStateSuccess: true,
18
+ isBranchOutOfDate: false,
19
+ missingRequiredCheckNames: [],
20
+ },
21
+ };
22
+
23
+ export const FailingWithMissingChecks: Story = {
24
+ args: {
25
+ isConflicted: false,
26
+ isPassedAllCiJob: false,
27
+ isCiStateSuccess: false,
28
+ isBranchOutOfDate: false,
29
+ missingRequiredCheckNames: ['build', 'test'],
30
+ },
31
+ };
32
+
33
+ export const ConflictedAndOutOfDate: Story = {
34
+ args: {
35
+ isConflicted: true,
36
+ isPassedAllCiJob: false,
37
+ isCiStateSuccess: false,
38
+ isBranchOutOfDate: true,
39
+ missingRequiredCheckNames: ['build', 'test'],
40
+ },
41
+ };
@@ -0,0 +1,72 @@
1
+ import { render } from '@testing-library/react';
2
+ import { ConsolePullRequestStatusBadges } from './ConsolePullRequestStatusBadges';
3
+
4
+ describe('ConsolePullRequestStatusBadges', () => {
5
+ it('renders a passing CI badge when all CI jobs passed and the CI state is success', () => {
6
+ const { getByText, queryByText } = render(
7
+ <ConsolePullRequestStatusBadges
8
+ isConflicted={false}
9
+ isPassedAllCiJob={true}
10
+ isCiStateSuccess={true}
11
+ isBranchOutOfDate={false}
12
+ missingRequiredCheckNames={[]}
13
+ />,
14
+ );
15
+ expect(getByText('CI passing')).toBeInTheDocument();
16
+ expect(queryByText('Conflict')).toBeNull();
17
+ expect(queryByText('Out of date')).toBeNull();
18
+ });
19
+
20
+ it('renders a failing CI badge with missing required checks', () => {
21
+ const { getByText } = render(
22
+ <ConsolePullRequestStatusBadges
23
+ isConflicted={false}
24
+ isPassedAllCiJob={false}
25
+ isCiStateSuccess={true}
26
+ isBranchOutOfDate={false}
27
+ missingRequiredCheckNames={['build', 'test']}
28
+ />,
29
+ );
30
+ expect(getByText('CI failing')).toBeInTheDocument();
31
+ expect(getByText(/missing: build, test/)).toBeInTheDocument();
32
+ });
33
+
34
+ it('renders the conflict badge when the pull request is conflicted', () => {
35
+ const { getByText } = render(
36
+ <ConsolePullRequestStatusBadges
37
+ isConflicted={true}
38
+ isPassedAllCiJob={true}
39
+ isCiStateSuccess={true}
40
+ isBranchOutOfDate={false}
41
+ missingRequiredCheckNames={[]}
42
+ />,
43
+ );
44
+ expect(getByText('Conflict')).toBeInTheDocument();
45
+ });
46
+
47
+ it('renders the out-of-date badge when the branch is behind the base branch', () => {
48
+ const { getByText } = render(
49
+ <ConsolePullRequestStatusBadges
50
+ isConflicted={false}
51
+ isPassedAllCiJob={true}
52
+ isCiStateSuccess={true}
53
+ isBranchOutOfDate={true}
54
+ missingRequiredCheckNames={[]}
55
+ />,
56
+ );
57
+ expect(getByText('Out of date')).toBeInTheDocument();
58
+ });
59
+
60
+ it('treats a successful CI state but unfinished jobs as failing', () => {
61
+ const { getByText } = render(
62
+ <ConsolePullRequestStatusBadges
63
+ isConflicted={false}
64
+ isPassedAllCiJob={false}
65
+ isCiStateSuccess={true}
66
+ isBranchOutOfDate={false}
67
+ missingRequiredCheckNames={[]}
68
+ />,
69
+ );
70
+ expect(getByText('CI failing')).toBeInTheDocument();
71
+ });
72
+ });
@@ -0,0 +1,71 @@
1
+ import { colorFromEnum } from '../../logic/colors';
2
+ import type { ConsoleColor } from '../../logic/types';
3
+
4
+ export type ConsolePullRequestStatusBadgesProps = {
5
+ isConflicted: boolean;
6
+ isPassedAllCiJob: boolean;
7
+ isCiStateSuccess: boolean;
8
+ isBranchOutOfDate: boolean;
9
+ missingRequiredCheckNames: string[];
10
+ };
11
+
12
+ const badgeStyle = (color: ConsoleColor) => {
13
+ const palette = colorFromEnum(color);
14
+ return {
15
+ color: palette.fg,
16
+ borderColor: palette.border,
17
+ backgroundColor: palette.bg,
18
+ };
19
+ };
20
+
21
+ export const ConsolePullRequestStatusBadges = ({
22
+ isConflicted,
23
+ isPassedAllCiJob,
24
+ isCiStateSuccess,
25
+ isBranchOutOfDate,
26
+ missingRequiredCheckNames,
27
+ }: ConsolePullRequestStatusBadgesProps) => {
28
+ const ciPassing = isPassedAllCiJob && isCiStateSuccess;
29
+ const ciColor: ConsoleColor = ciPassing ? 'GREEN' : 'RED';
30
+ const ciLabel = ciPassing ? 'CI passing' : 'CI failing';
31
+ const missingChecksLabel =
32
+ !ciPassing && missingRequiredCheckNames.length > 0
33
+ ? `missing: ${missingRequiredCheckNames.join(', ')}`
34
+ : null;
35
+
36
+ return (
37
+ <span className="console-detail-pr-status">
38
+ <span
39
+ className="console-detail-status-chip console-detail-ci-chip"
40
+ style={badgeStyle(ciColor)}
41
+ title={missingChecksLabel ?? ciLabel}
42
+ >
43
+ {ciLabel}
44
+ {missingChecksLabel !== null && (
45
+ <span className="console-detail-ci-missing">
46
+ {' '}
47
+ ({missingChecksLabel})
48
+ </span>
49
+ )}
50
+ </span>
51
+ {isConflicted && (
52
+ <span
53
+ className="console-detail-status-chip console-detail-conflict-chip"
54
+ style={badgeStyle('RED')}
55
+ title="This pull request has merge conflicts"
56
+ >
57
+ Conflict
58
+ </span>
59
+ )}
60
+ {isBranchOutOfDate && (
61
+ <span
62
+ className="console-detail-status-chip console-detail-outofdate-chip"
63
+ style={badgeStyle('YELLOW')}
64
+ title="This branch is out of date with the base branch"
65
+ >
66
+ Out of date
67
+ </span>
68
+ )}
69
+ </span>
70
+ );
71
+ };
@@ -9,6 +9,7 @@ import type {
9
9
  ConsoleComment,
10
10
  ConsoleCommit,
11
11
  ConsoleIssueState,
12
+ ConsolePullRequestStatus,
12
13
  ConsoleRelatedPullRequest,
13
14
  } from '../logic/types';
14
15
  import { useConsoleToken } from './useConsoleToken';
@@ -20,6 +21,7 @@ export type ConsoleCaches = {
20
21
  commits: ResourceCache<ConsoleCommit[]>;
21
22
  relatedPrs: ResourceCache<ConsoleRelatedPullRequest[]>;
22
23
  state: ResourceCache<ConsoleIssueState>;
24
+ prStatus: ResourceCache<ConsolePullRequestStatus>;
23
25
  client: ConsoleApiClient;
24
26
  };
25
27
 
@@ -37,6 +39,9 @@ export const useConsoleCaches = (): ConsoleCaches => {
37
39
  client.fetchRelatedPrs,
38
40
  ),
39
41
  state: new ResourceCache<ConsoleIssueState>(client.fetchIssueState),
42
+ prStatus: new ResourceCache<ConsolePullRequestStatus>(
43
+ client.fetchPullRequestStatus,
44
+ ),
40
45
  };
41
46
  }, [appendToken]);
42
47
  };
@@ -6,6 +6,7 @@ import type {
6
6
  ConsoleCommit,
7
7
  ConsoleIssueState,
8
8
  ConsoleListItem,
9
+ ConsolePullRequestStatus,
9
10
  ConsoleRelatedPullRequest,
10
11
  } from '../logic/types';
11
12
  import type { ConsoleCaches } from './useConsoleCaches';
@@ -63,6 +64,14 @@ const buildCaches = (related: ConsoleRelatedPullRequest[]): ConsoleCaches => {
63
64
  merged: false,
64
65
  isPullRequest: true,
65
66
  }),
67
+ fetchPullRequestStatus: async (): Promise<ConsolePullRequestStatus> => ({
68
+ found: true,
69
+ isConflicted: true,
70
+ isPassedAllCiJob: false,
71
+ isCiStateSuccess: false,
72
+ isBranchOutOfDate: true,
73
+ missingRequiredCheckNames: ['build'],
74
+ }),
66
75
  };
67
76
  return {
68
77
  client,
@@ -72,6 +81,7 @@ const buildCaches = (related: ConsoleRelatedPullRequest[]): ConsoleCaches => {
72
81
  commits: new ResourceCache(client.fetchPrCommits),
73
82
  relatedPrs: new ResourceCache(client.fetchRelatedPrs),
74
83
  state: new ResourceCache(client.fetchIssueState),
84
+ prStatus: new ResourceCache(client.fetchPullRequestStatus),
75
85
  };
76
86
  };
77
87
 
@@ -85,9 +95,19 @@ describe('useConsoleItemDetailData', () => {
85
95
  expect(result.current.body).toBe('body');
86
96
  expect(result.current.files.length).toBe(1);
87
97
  expect(result.current.commits.length).toBe(1);
98
+ expect(result.current.pullRequestStatus?.found).toBe(true);
99
+ expect(result.current.pullRequestStatus?.isConflicted).toBe(true);
88
100
  });
89
101
  });
90
102
 
103
+ it('does not expose pull request status for an issue item', () => {
104
+ const caches = buildCaches([]);
105
+ const { result } = renderHook(() =>
106
+ useConsoleItemDetailData(caches, issueItem),
107
+ );
108
+ expect(result.current.pullRequestStatus).toBeNull();
109
+ });
110
+
91
111
  it('loads related pull request views for an issue item', async () => {
92
112
  const related: ConsoleRelatedPullRequest[] = [
93
113
  {
@@ -6,6 +6,7 @@ import type {
6
6
  ConsoleCommit,
7
7
  ConsoleIssueState,
8
8
  ConsoleListItem,
9
+ ConsolePullRequestStatus,
9
10
  ConsoleRelatedPullRequest,
10
11
  } from '../logic/types';
11
12
  import type { ConsoleCaches } from './useConsoleCaches';
@@ -20,6 +21,14 @@ const DEFAULT_STATE: ConsoleIssueState = {
20
21
  merged: false,
21
22
  isPullRequest: false,
22
23
  };
24
+ const DEFAULT_PR_STATUS: ConsolePullRequestStatus = {
25
+ found: false,
26
+ isConflicted: false,
27
+ isPassedAllCiJob: false,
28
+ isCiStateSuccess: false,
29
+ isBranchOutOfDate: false,
30
+ missingRequiredCheckNames: [],
31
+ };
23
32
 
24
33
  export type ConsoleItemDetailData = {
25
34
  state: ConsoleIssueState | null;
@@ -35,6 +44,7 @@ export type ConsoleItemDetailData = {
35
44
  commits: ConsoleCommit[];
36
45
  commitsAreLoading: boolean;
37
46
  commitsError: string | null;
47
+ pullRequestStatus: ConsolePullRequestStatus | null;
38
48
  relatedPullRequests: ConsoleRelatedPullRequestView[];
39
49
  };
40
50
 
@@ -66,6 +76,12 @@ export const useConsoleItemDetailData = (
66
76
  isPr ? url : null,
67
77
  EMPTY_COMMITS,
68
78
  );
79
+ const prStatus = useConsoleResource(
80
+ caches.prStatus,
81
+ isPr ? key : null,
82
+ isPr ? url : null,
83
+ DEFAULT_PR_STATUS,
84
+ );
69
85
  const relatedPrs = useConsoleResource(
70
86
  caches.relatedPrs,
71
87
  !isPr ? key : null,
@@ -162,6 +178,7 @@ export const useConsoleItemDetailData = (
162
178
  commits: commits.data,
163
179
  commitsAreLoading: commits.isLoading,
164
180
  commitsError: commits.error,
181
+ pullRequestStatus: isPr ? prStatus.data : null,
165
182
  relatedPullRequests: relatedViews,
166
183
  };
167
184
  };
@@ -48,6 +48,7 @@ describe('createConsoleApiClient', () => {
48
48
  readers.push((url) => client.fetchPrCommits(url));
49
49
  readers.push((url) => client.fetchRelatedPrs(url));
50
50
  readers.push((url) => client.fetchIssueState(url));
51
+ readers.push((url) => client.fetchPullRequestStatus(url));
51
52
  const expectedPaths = [
52
53
  '/api/itembody',
53
54
  '/api/comments',
@@ -55,6 +56,7 @@ describe('createConsoleApiClient', () => {
55
56
  '/api/prcommits',
56
57
  '/api/relatedprs',
57
58
  '/api/issuetitle',
59
+ '/api/pullrequeststatus',
58
60
  ];
59
61
  for (let index = 0; index < readers.length; index += 1) {
60
62
  const fetchMock = mockFetchOnce({});
@@ -161,6 +163,45 @@ describe('createConsoleApiClient', () => {
161
163
  expect(related[0].missingRequiredCheckNames).toEqual(['build']);
162
164
  });
163
165
 
166
+ it('parses the pull request status when found', async () => {
167
+ mockFetchOnce({
168
+ found: true,
169
+ status: {
170
+ isConflicted: true,
171
+ isPassedAllCiJob: false,
172
+ isCiStateSuccess: false,
173
+ isBranchOutOfDate: true,
174
+ missingRequiredCheckNames: ['build', 'test'],
175
+ },
176
+ });
177
+ const client = createConsoleApiClient(appendToken);
178
+ expect(
179
+ await client.fetchPullRequestStatus('https://github.com/o/r/pull/1'),
180
+ ).toEqual({
181
+ found: true,
182
+ isConflicted: true,
183
+ isPassedAllCiJob: false,
184
+ isCiStateSuccess: false,
185
+ isBranchOutOfDate: true,
186
+ missingRequiredCheckNames: ['build', 'test'],
187
+ });
188
+ });
189
+
190
+ it('returns a not-found pull request status when the server reports none', async () => {
191
+ mockFetchOnce({ found: false, status: null });
192
+ const client = createConsoleApiClient(appendToken);
193
+ expect(
194
+ await client.fetchPullRequestStatus('https://github.com/o/r/pull/1'),
195
+ ).toEqual({
196
+ found: false,
197
+ isConflicted: false,
198
+ isPassedAllCiJob: false,
199
+ isCiStateSuccess: false,
200
+ isBranchOutOfDate: false,
201
+ missingRequiredCheckNames: [],
202
+ });
203
+ });
204
+
164
205
  it('throws on a non-ok response', async () => {
165
206
  mockFetchOnce({}, false);
166
207
  const client = createConsoleApiClient(appendToken);
@@ -3,6 +3,7 @@ import type {
3
3
  ConsoleComment,
4
4
  ConsoleCommit,
5
5
  ConsoleIssueState,
6
+ ConsolePullRequestStatus,
6
7
  ConsoleRelatedPullRequest,
7
8
  } from '../logic/types';
8
9
 
@@ -13,6 +14,7 @@ export type ConsoleApiClient = {
13
14
  fetchPrCommits: (url: string) => Promise<ConsoleCommit[]>;
14
15
  fetchRelatedPrs: (url: string) => Promise<ConsoleRelatedPullRequest[]>;
15
16
  fetchIssueState: (url: string) => Promise<ConsoleIssueState>;
17
+ fetchPullRequestStatus: (url: string) => Promise<ConsolePullRequestStatus>;
16
18
  };
17
19
 
18
20
  export type ConsoleReviewRequest = {
@@ -129,6 +131,11 @@ const parseSummary = (value: unknown): ConsoleRelatedPullRequest['summary'] => {
129
131
  };
130
132
  };
131
133
 
134
+ const parseStringArray = (value: unknown): string[] =>
135
+ Array.isArray(value)
136
+ ? value.filter((name): name is string => typeof name === 'string')
137
+ : [];
138
+
132
139
  const parseRelatedPrs = (payload: unknown): ConsoleRelatedPullRequest[] => {
133
140
  if (!isRecord(payload) || !Array.isArray(payload.relatedPullRequests)) {
134
141
  return [];
@@ -143,15 +150,35 @@ const parseRelatedPrs = (payload: unknown): ConsoleRelatedPullRequest[] => {
143
150
  isCiStateSuccess: getBoolean(pr.isCiStateSuccess),
144
151
  isResolvedAllReviewComments: getBoolean(pr.isResolvedAllReviewComments),
145
152
  isBranchOutOfDate: getBoolean(pr.isBranchOutOfDate),
146
- missingRequiredCheckNames: Array.isArray(pr.missingRequiredCheckNames)
147
- ? pr.missingRequiredCheckNames.filter(
148
- (name): name is string => typeof name === 'string',
149
- )
150
- : [],
153
+ missingRequiredCheckNames: parseStringArray(pr.missingRequiredCheckNames),
151
154
  summary: parseSummary(pr.summary),
152
155
  }));
153
156
  };
154
157
 
158
+ const parsePullRequestStatus = (payload: unknown): ConsolePullRequestStatus => {
159
+ if (!isRecord(payload) || !isRecord(payload.status)) {
160
+ return {
161
+ found: false,
162
+ isConflicted: false,
163
+ isPassedAllCiJob: false,
164
+ isCiStateSuccess: false,
165
+ isBranchOutOfDate: false,
166
+ missingRequiredCheckNames: [],
167
+ };
168
+ }
169
+ const status = payload.status;
170
+ return {
171
+ found: true,
172
+ isConflicted: getBoolean(status.isConflicted),
173
+ isPassedAllCiJob: getBoolean(status.isPassedAllCiJob),
174
+ isCiStateSuccess: getBoolean(status.isCiStateSuccess),
175
+ isBranchOutOfDate: getBoolean(status.isBranchOutOfDate),
176
+ missingRequiredCheckNames: parseStringArray(
177
+ status.missingRequiredCheckNames,
178
+ ),
179
+ };
180
+ };
181
+
155
182
  const parseState = (payload: unknown): ConsoleIssueState => {
156
183
  if (!isRecord(payload)) {
157
184
  return { state: 'open', merged: false, isPullRequest: false };
@@ -180,6 +207,10 @@ export const createConsoleApiClient = (
180
207
  parseRelatedPrs(await requestJson(appendToken, '/api/relatedprs', url)),
181
208
  fetchIssueState: async (url) =>
182
209
  parseState(await requestJson(appendToken, '/api/issuetitle', url)),
210
+ fetchPullRequestStatus: async (url) =>
211
+ parsePullRequestStatus(
212
+ await requestJson(appendToken, '/api/pullrequeststatus', url),
213
+ ),
183
214
  });
184
215
 
185
216
  const readOperationErrorReason = async (
@@ -63,6 +63,15 @@ export type ConsoleIssueState = {
63
63
  isPullRequest: boolean;
64
64
  };
65
65
 
66
+ export type ConsolePullRequestStatus = {
67
+ found: boolean;
68
+ isConflicted: boolean;
69
+ isPassedAllCiJob: boolean;
70
+ isCiStateSuccess: boolean;
71
+ isBranchOutOfDate: boolean;
72
+ missingRequiredCheckNames: string[];
73
+ };
74
+
66
75
  export type ConsoleComment = {
67
76
  author: string;
68
77
  body: string;
@@ -40,6 +40,14 @@ const buildCaches = (overrides: CachesOverrides = {}): ConsoleCaches => {
40
40
  merged: false,
41
41
  isPullRequest: true,
42
42
  }),
43
+ fetchPullRequestStatus: async () => ({
44
+ found: true,
45
+ isConflicted: false,
46
+ isPassedAllCiJob: true,
47
+ isCiStateSuccess: true,
48
+ isBranchOutOfDate: false,
49
+ missingRequiredCheckNames: [],
50
+ }),
43
51
  };
44
52
  return {
45
53
  client,
@@ -49,6 +57,7 @@ const buildCaches = (overrides: CachesOverrides = {}): ConsoleCaches => {
49
57
  commits: new ResourceCache(client.fetchPrCommits),
50
58
  relatedPrs: new ResourceCache(client.fetchRelatedPrs),
51
59
  state: new ResourceCache(client.fetchIssueState),
60
+ prStatus: new ResourceCache(client.fetchPullRequestStatus),
52
61
  };
53
62
  };
54
63
 
@@ -150,6 +150,7 @@ export const ConsoleItemDetailContainer = ({
150
150
  commits={detail.commits}
151
151
  commitsAreLoading={detail.commitsAreLoading}
152
152
  commitsError={detail.commitsError}
153
+ pullRequestStatus={detail.pullRequestStatus}
153
154
  relatedPullRequests={detail.relatedPullRequests}
154
155
  now={now}
155
156
  buildImageProxyUrl={resolveImageProxyUrl}
@@ -335,6 +335,18 @@ body {
335
335
  font-size: 0.75rem;
336
336
  }
337
337
 
338
+ .console-detail-pr-status {
339
+ display: inline-flex;
340
+ flex-wrap: wrap;
341
+ align-items: center;
342
+ gap: 6px;
343
+ }
344
+
345
+ .console-detail-ci-missing {
346
+ font-weight: 400;
347
+ opacity: 0.85;
348
+ }
349
+
338
350
  .console-detail-labels {
339
351
  display: flex;
340
352
  flex-wrap: wrap;