github-issue-tower-defence-management 1.113.0 → 1.114.1
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 +14 -0
- package/README.md +2 -2
- package/bin/adapter/entry-points/console/consoleReadApi.js +11 -1
- package/bin/adapter/entry-points/console/consoleReadApi.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/{index-CEJmPNRK.css → index-Blb_xqIx.css} +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-DQyZ8KpG.js +101 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +2 -2
- package/bin/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.js +36 -27
- package/bin/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.js.map +1 -1
- package/bin/adapter/repositories/FileSystemSessionRecordReader.js +69 -0
- package/bin/adapter/repositories/FileSystemSessionRecordReader.js.map +1 -0
- package/bin/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.js +9 -2
- package/bin/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.js.map +1 -1
- package/bin/domain/usecases/ResolveInteractiveLiveSessionsUseCase.js +15 -0
- package/bin/domain/usecases/ResolveInteractiveLiveSessionsUseCase.js.map +1 -1
- package/bin/domain/usecases/adapter-interfaces/SessionRecordReader.js +3 -0
- package/bin/domain/usecases/adapter-interfaces/SessionRecordReader.js.map +1 -0
- package/package.json +1 -1
- package/src/adapter/entry-points/console/consoleReadApi.test.ts +82 -0
- package/src/adapter/entry-points/console/consoleReadApi.ts +21 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.stories.tsx +16 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.test.tsx +61 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.tsx +59 -5
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.stories.tsx +76 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.test.tsx +56 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.tsx +39 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.tsx +4 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.stories.tsx +24 -4
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.test.tsx +13 -3
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +6 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.tsx +4 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.ts +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleReferenceLink.test.ts +48 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleReferenceLink.ts +39 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +25 -2
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +2 -1
- package/src/adapter/entry-points/console/ui/src/features/console/logic/references.test.ts +80 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/references.ts +44 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +12 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleReferenceLinkContainer.tsx +25 -0
- package/src/adapter/entry-points/console/ui/src/features/console/testing/fixtures.ts +40 -0
- package/src/adapter/entry-points/console/ui/src/index.css +23 -0
- package/src/adapter/entry-points/console/ui-dist/assets/{index-CEJmPNRK.css → index-Blb_xqIx.css} +1 -1
- package/src/adapter/entry-points/console/ui-dist/assets/index-DQyZ8KpG.js +101 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +2 -2
- package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.test.ts +107 -26
- package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.ts +49 -29
- package/src/adapter/repositories/FileSystemSessionRecordReader.test.ts +59 -0
- package/src/adapter/repositories/FileSystemSessionRecordReader.ts +31 -0
- package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.test.ts +51 -0
- package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.ts +10 -1
- package/src/domain/entities/InteractiveLiveSession.ts +1 -0
- package/src/domain/entities/LiveSessionProcessSnapshot.ts +1 -0
- package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +4 -0
- package/src/domain/usecases/ResolveInteractiveLiveSessionsUseCase.test.ts +66 -59
- package/src/domain/usecases/ResolveInteractiveLiveSessionsUseCase.ts +19 -0
- package/src/domain/usecases/adapter-interfaces/SessionRecordReader.ts +3 -0
- package/types/adapter/entry-points/console/consoleReadApi.d.ts +1 -0
- package/types/adapter/entry-points/console/consoleReadApi.d.ts.map +1 -1
- package/types/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.d.ts +4 -11
- package/types/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.d.ts.map +1 -1
- package/types/adapter/repositories/FileSystemSessionRecordReader.d.ts +5 -0
- package/types/adapter/repositories/FileSystemSessionRecordReader.d.ts.map +1 -0
- package/types/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.d.ts +3 -1
- package/types/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.d.ts.map +1 -1
- package/types/domain/entities/InteractiveLiveSession.d.ts +1 -0
- package/types/domain/entities/InteractiveLiveSession.d.ts.map +1 -1
- package/types/domain/entities/LiveSessionProcessSnapshot.d.ts +1 -0
- package/types/domain/entities/LiveSessionProcessSnapshot.d.ts.map +1 -1
- package/types/domain/usecases/ResolveInteractiveLiveSessionsUseCase.d.ts +1 -0
- package/types/domain/usecases/ResolveInteractiveLiveSessionsUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/SessionRecordReader.d.ts +4 -0
- package/types/domain/usecases/adapter-interfaces/SessionRecordReader.d.ts.map +1 -0
- package/bin/adapter/entry-points/console/ui-dist/assets/index-DfNrA5uV.js +0 -101
- package/src/adapter/entry-points/console/ui-dist/assets/index-DfNrA5uV.js +0 -101
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef } from 'react';
|
|
1
|
+
import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
2
3
|
import {
|
|
3
4
|
type ImageProxyUrlBuilder,
|
|
4
5
|
rewriteGitHubImageSources,
|
|
@@ -7,21 +8,54 @@ import {
|
|
|
7
8
|
renderMarkdownToSafeHtml,
|
|
8
9
|
splitMarkdownSegments,
|
|
9
10
|
} from '../../lib/markdown';
|
|
11
|
+
import { parseGitHubReferenceUrl } from '../../logic/references';
|
|
10
12
|
import { ConsoleMermaidDiagram } from './ConsoleMermaidDiagram';
|
|
11
13
|
|
|
14
|
+
export type ConsoleReferenceLinkRenderer = (
|
|
15
|
+
href: string,
|
|
16
|
+
fallbackText: string,
|
|
17
|
+
) => ReactNode;
|
|
18
|
+
|
|
12
19
|
export type ConsoleMarkdownViewProps = {
|
|
13
20
|
body: string;
|
|
14
21
|
buildImageProxyUrl?: ImageProxyUrlBuilder;
|
|
22
|
+
renderReferenceLink?: ConsoleReferenceLinkRenderer;
|
|
15
23
|
};
|
|
16
24
|
|
|
17
25
|
type ConsoleMarkdownHtmlBlockProps = {
|
|
18
26
|
source: string;
|
|
19
27
|
buildImageProxyUrl?: ImageProxyUrlBuilder;
|
|
28
|
+
renderReferenceLink?: ConsoleReferenceLinkRenderer;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type ReferenceMount = {
|
|
32
|
+
key: string;
|
|
33
|
+
host: HTMLElement;
|
|
34
|
+
href: string;
|
|
35
|
+
fallbackText: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const collectReferenceMounts = (container: HTMLElement): ReferenceMount[] => {
|
|
39
|
+
const anchors = container.querySelectorAll<HTMLAnchorElement>('a[href]');
|
|
40
|
+
const mounts: ReferenceMount[] = [];
|
|
41
|
+
anchors.forEach((anchor, index) => {
|
|
42
|
+
const href = anchor.getAttribute('href') ?? '';
|
|
43
|
+
if (parseGitHubReferenceUrl(href) === null) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const fallbackText = anchor.textContent ?? href;
|
|
47
|
+
const host = document.createElement('span');
|
|
48
|
+
host.className = 'console-markdown-reference-host';
|
|
49
|
+
anchor.replaceWith(host);
|
|
50
|
+
mounts.push({ key: `${index}:${href}`, host, href, fallbackText });
|
|
51
|
+
});
|
|
52
|
+
return mounts;
|
|
20
53
|
};
|
|
21
54
|
|
|
22
55
|
const ConsoleMarkdownHtmlBlock = ({
|
|
23
56
|
source,
|
|
24
57
|
buildImageProxyUrl,
|
|
58
|
+
renderReferenceLink,
|
|
25
59
|
}: ConsoleMarkdownHtmlBlockProps) => {
|
|
26
60
|
const html = useMemo(() => {
|
|
27
61
|
const safeHtml = renderMarkdownToSafeHtml(source);
|
|
@@ -31,20 +65,39 @@ const ConsoleMarkdownHtmlBlock = ({
|
|
|
31
65
|
return rewriteGitHubImageSources(safeHtml, buildImageProxyUrl);
|
|
32
66
|
}, [source, buildImageProxyUrl]);
|
|
33
67
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
68
|
+
const [referenceMounts, setReferenceMounts] = useState<ReferenceMount[]>([]);
|
|
34
69
|
|
|
35
70
|
useEffect(() => {
|
|
36
71
|
const container = containerRef.current;
|
|
37
|
-
if (container
|
|
38
|
-
|
|
72
|
+
if (container === null) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
container.innerHTML = html;
|
|
76
|
+
if (renderReferenceLink === undefined) {
|
|
77
|
+
setReferenceMounts([]);
|
|
78
|
+
return;
|
|
39
79
|
}
|
|
40
|
-
|
|
80
|
+
setReferenceMounts(collectReferenceMounts(container));
|
|
81
|
+
}, [html, renderReferenceLink]);
|
|
41
82
|
|
|
42
|
-
return
|
|
83
|
+
return (
|
|
84
|
+
<div ref={containerRef} className="console-markdown">
|
|
85
|
+
{renderReferenceLink !== undefined &&
|
|
86
|
+
referenceMounts.map((mount) =>
|
|
87
|
+
createPortal(
|
|
88
|
+
renderReferenceLink(mount.href, mount.fallbackText),
|
|
89
|
+
mount.host,
|
|
90
|
+
mount.key,
|
|
91
|
+
),
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
43
95
|
};
|
|
44
96
|
|
|
45
97
|
export const ConsoleMarkdownContent = ({
|
|
46
98
|
body,
|
|
47
99
|
buildImageProxyUrl,
|
|
100
|
+
renderReferenceLink,
|
|
48
101
|
}: ConsoleMarkdownViewProps) => {
|
|
49
102
|
const segments = useMemo(() => splitMarkdownSegments(body), [body]);
|
|
50
103
|
|
|
@@ -62,6 +115,7 @@ export const ConsoleMarkdownContent = ({
|
|
|
62
115
|
key={segment.key}
|
|
63
116
|
source={segment.source}
|
|
64
117
|
buildImageProxyUrl={buildImageProxyUrl}
|
|
118
|
+
renderReferenceLink={renderReferenceLink}
|
|
65
119
|
/>
|
|
66
120
|
),
|
|
67
121
|
)}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { ConsoleReferenceLink } from './ConsoleReferenceLink';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof ConsoleReferenceLink> = {
|
|
5
|
+
title: 'Console/ConsoleReferenceLink',
|
|
6
|
+
component: ConsoleReferenceLink,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default meta;
|
|
10
|
+
|
|
11
|
+
type Story = StoryObj<typeof ConsoleReferenceLink>;
|
|
12
|
+
|
|
13
|
+
export const OpenPullRequest: Story = {
|
|
14
|
+
args: {
|
|
15
|
+
href: 'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/pull/995',
|
|
16
|
+
fallbackText:
|
|
17
|
+
'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/pull/995',
|
|
18
|
+
state: {
|
|
19
|
+
state: 'open',
|
|
20
|
+
merged: false,
|
|
21
|
+
isPullRequest: true,
|
|
22
|
+
title: 'Rename serveConsole subcommand to serveWeb',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const MergedPullRequest: Story = {
|
|
28
|
+
args: {
|
|
29
|
+
href: 'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/pull/851',
|
|
30
|
+
fallbackText:
|
|
31
|
+
'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/pull/851',
|
|
32
|
+
state: {
|
|
33
|
+
state: 'closed',
|
|
34
|
+
merged: true,
|
|
35
|
+
isPullRequest: true,
|
|
36
|
+
title: 'Add serveConsole subcommand under entry-points',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const OpenIssue: Story = {
|
|
42
|
+
args: {
|
|
43
|
+
href: 'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/845',
|
|
44
|
+
fallbackText:
|
|
45
|
+
'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/845',
|
|
46
|
+
state: {
|
|
47
|
+
state: 'open',
|
|
48
|
+
merged: false,
|
|
49
|
+
isPullRequest: false,
|
|
50
|
+
title: 'Scaffold React console UI under entry-points with build bundling',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const ClosedIssue: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
href: 'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/692',
|
|
58
|
+
fallbackText:
|
|
59
|
+
'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/692',
|
|
60
|
+
state: {
|
|
61
|
+
state: 'closed',
|
|
62
|
+
merged: false,
|
|
63
|
+
isPullRequest: false,
|
|
64
|
+
title: 'Publish the generated documentation site to GitHub Pages',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const UnresolvedFallback: Story = {
|
|
70
|
+
args: {
|
|
71
|
+
href: 'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/pull/995',
|
|
72
|
+
fallbackText:
|
|
73
|
+
'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/pull/995',
|
|
74
|
+
state: null,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { ConsoleReferenceLink } from './ConsoleReferenceLink';
|
|
3
|
+
|
|
4
|
+
describe('ConsoleReferenceLink', () => {
|
|
5
|
+
const href = 'https://github.com/octo/repo/pull/7';
|
|
6
|
+
|
|
7
|
+
it('renders the state icon and fetched title when state is resolved', () => {
|
|
8
|
+
const { container, getByText } = render(
|
|
9
|
+
<ConsoleReferenceLink
|
|
10
|
+
href={href}
|
|
11
|
+
fallbackText={href}
|
|
12
|
+
state={{
|
|
13
|
+
state: 'open',
|
|
14
|
+
merged: false,
|
|
15
|
+
isPullRequest: true,
|
|
16
|
+
title: 'Decorate PR links',
|
|
17
|
+
}}
|
|
18
|
+
/>,
|
|
19
|
+
);
|
|
20
|
+
expect(getByText('Decorate PR links')).toBeInTheDocument();
|
|
21
|
+
expect(container.querySelector('.console-item-icon')).not.toBeNull();
|
|
22
|
+
const anchor = container.querySelector('a');
|
|
23
|
+
expect(anchor?.getAttribute('href')).toBe(href);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('falls back to the plain link when state is null', () => {
|
|
27
|
+
const { container, getByText } = render(
|
|
28
|
+
<ConsoleReferenceLink
|
|
29
|
+
href={href}
|
|
30
|
+
fallbackText="plain text"
|
|
31
|
+
state={null}
|
|
32
|
+
/>,
|
|
33
|
+
);
|
|
34
|
+
expect(getByText('plain text')).toBeInTheDocument();
|
|
35
|
+
expect(container.querySelector('.console-item-icon')).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('falls back to the plain link when the resolved title is empty', () => {
|
|
39
|
+
const { container } = render(
|
|
40
|
+
<ConsoleReferenceLink
|
|
41
|
+
href={href}
|
|
42
|
+
fallbackText="plain text"
|
|
43
|
+
state={{
|
|
44
|
+
state: 'open',
|
|
45
|
+
merged: false,
|
|
46
|
+
isPullRequest: true,
|
|
47
|
+
title: '',
|
|
48
|
+
}}
|
|
49
|
+
/>,
|
|
50
|
+
);
|
|
51
|
+
expect(container.querySelector('.console-item-icon')).toBeNull();
|
|
52
|
+
expect(
|
|
53
|
+
container.querySelector('.console-markdown-reference-plain'),
|
|
54
|
+
).not.toBeNull();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { referenceStateToIconInput } from '../../logic/references';
|
|
2
|
+
import type { ConsoleIssueState } from '../../logic/types';
|
|
3
|
+
import { ConsoleItemIcon } from '../detail/ConsoleItemIcon';
|
|
4
|
+
|
|
5
|
+
export type ConsoleReferenceLinkProps = {
|
|
6
|
+
href: string;
|
|
7
|
+
fallbackText: string;
|
|
8
|
+
state: ConsoleIssueState | null;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const ConsoleReferenceLink = ({
|
|
12
|
+
href,
|
|
13
|
+
fallbackText,
|
|
14
|
+
state,
|
|
15
|
+
}: ConsoleReferenceLinkProps) => {
|
|
16
|
+
if (state === null || state.title.trim() === '') {
|
|
17
|
+
return (
|
|
18
|
+
<a
|
|
19
|
+
className="console-markdown-reference console-markdown-reference-plain"
|
|
20
|
+
href={href}
|
|
21
|
+
target="_blank"
|
|
22
|
+
rel="noopener noreferrer"
|
|
23
|
+
>
|
|
24
|
+
{fallbackText}
|
|
25
|
+
</a>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return (
|
|
29
|
+
<a
|
|
30
|
+
className="console-markdown-reference"
|
|
31
|
+
href={href}
|
|
32
|
+
target="_blank"
|
|
33
|
+
rel="noopener noreferrer"
|
|
34
|
+
>
|
|
35
|
+
<ConsoleItemIcon {...referenceStateToIconInput(state)} />
|
|
36
|
+
<span className="console-markdown-reference-title">{state.title}</span>
|
|
37
|
+
</a>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -2,6 +2,7 @@ import { useState } from 'react';
|
|
|
2
2
|
import type { ImageProxyUrlBuilder } from '../../lib/imageProxy';
|
|
3
3
|
import { formatRelativeTime } from '../../logic/relativeTime';
|
|
4
4
|
import type { ConsoleComment } from '../../logic/types';
|
|
5
|
+
import type { ConsoleReferenceLinkRenderer } from '../content/ConsoleMarkdownContent';
|
|
5
6
|
import { ConsoleMarkdownContent } from '../content/ConsoleMarkdownContent';
|
|
6
7
|
|
|
7
8
|
export type ConsoleCommentListProps = {
|
|
@@ -10,6 +11,7 @@ export type ConsoleCommentListProps = {
|
|
|
10
11
|
error: string | null;
|
|
11
12
|
now: number;
|
|
12
13
|
buildImageProxyUrl?: ImageProxyUrlBuilder;
|
|
14
|
+
renderReferenceLink?: ConsoleReferenceLinkRenderer;
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
export const ConsoleCommentList = ({
|
|
@@ -18,6 +20,7 @@ export const ConsoleCommentList = ({
|
|
|
18
20
|
error,
|
|
19
21
|
now,
|
|
20
22
|
buildImageProxyUrl,
|
|
23
|
+
renderReferenceLink,
|
|
21
24
|
}: ConsoleCommentListProps) => {
|
|
22
25
|
const [showAll, setShowAll] = useState<boolean>(false);
|
|
23
26
|
|
|
@@ -64,6 +67,7 @@ export const ConsoleCommentList = ({
|
|
|
64
67
|
<ConsoleMarkdownContent
|
|
65
68
|
body={comment.body}
|
|
66
69
|
buildImageProxyUrl={buildImageProxyUrl}
|
|
70
|
+
renderReferenceLink={renderReferenceLink}
|
|
67
71
|
/>
|
|
68
72
|
</article>
|
|
69
73
|
))}
|
|
@@ -68,7 +68,12 @@ export const PullRequestItem: Story = {
|
|
|
68
68
|
storyName: 'TDPM Console port',
|
|
69
69
|
storyColorEnum: 'BLUE',
|
|
70
70
|
overlayStatus: { name: 'Awaiting Workspace', color: 'BLUE' },
|
|
71
|
-
state: {
|
|
71
|
+
state: {
|
|
72
|
+
state: 'open',
|
|
73
|
+
merged: false,
|
|
74
|
+
isPullRequest: true,
|
|
75
|
+
title: 'Add serveConsole subcommand under entry-points',
|
|
76
|
+
},
|
|
72
77
|
body: consoleMermaidBodyFixture,
|
|
73
78
|
bodyIsLoading: false,
|
|
74
79
|
bodyError: null,
|
|
@@ -99,7 +104,12 @@ export const PullRequestItemFailingCiWithConflict: Story = {
|
|
|
99
104
|
storyName: 'TDPM Console port',
|
|
100
105
|
storyColorEnum: 'BLUE',
|
|
101
106
|
overlayStatus: { name: 'Awaiting Quality Check', color: 'YELLOW' },
|
|
102
|
-
state: {
|
|
107
|
+
state: {
|
|
108
|
+
state: 'open',
|
|
109
|
+
merged: false,
|
|
110
|
+
isPullRequest: true,
|
|
111
|
+
title: 'Add serveConsole subcommand under entry-points',
|
|
112
|
+
},
|
|
103
113
|
body: consoleMermaidBodyFixture,
|
|
104
114
|
bodyIsLoading: false,
|
|
105
115
|
bodyError: null,
|
|
@@ -130,7 +140,12 @@ export const IssueWithRichMarkdownBody: Story = {
|
|
|
130
140
|
storyName: 'TDPM Console port',
|
|
131
141
|
storyColorEnum: 'BLUE',
|
|
132
142
|
overlayStatus: null,
|
|
133
|
-
state: {
|
|
143
|
+
state: {
|
|
144
|
+
state: 'open',
|
|
145
|
+
merged: false,
|
|
146
|
+
isPullRequest: false,
|
|
147
|
+
title: 'Scaffold React console UI under entry-points with build bundling',
|
|
148
|
+
},
|
|
134
149
|
body: richMarkdownBody,
|
|
135
150
|
bodyIsLoading: false,
|
|
136
151
|
bodyError: null,
|
|
@@ -164,7 +179,12 @@ export const IssueWithLinkedPullRequest: Story = {
|
|
|
164
179
|
storyName: 'TDPM Console port',
|
|
165
180
|
storyColorEnum: 'BLUE',
|
|
166
181
|
overlayStatus: null,
|
|
167
|
-
state: {
|
|
182
|
+
state: {
|
|
183
|
+
state: 'open',
|
|
184
|
+
merged: false,
|
|
185
|
+
isPullRequest: false,
|
|
186
|
+
title: 'Scaffold React console UI under entry-points with build bundling',
|
|
187
|
+
},
|
|
168
188
|
body: '## Issue body\n\nThis issue has a linked pull request.',
|
|
169
189
|
bodyIsLoading: false,
|
|
170
190
|
bodyError: null,
|
|
@@ -19,7 +19,7 @@ const baseProps = {
|
|
|
19
19
|
storyName: 'TDPM Console port',
|
|
20
20
|
storyColorEnum: 'BLUE' as const,
|
|
21
21
|
overlayStatus: null,
|
|
22
|
-
state: { state: 'open', merged: false, isPullRequest: true },
|
|
22
|
+
state: { state: 'open', merged: false, isPullRequest: true, title: '' },
|
|
23
23
|
body: '## Body heading',
|
|
24
24
|
bodyIsLoading: false,
|
|
25
25
|
bodyError: null,
|
|
@@ -79,7 +79,12 @@ describe('ConsoleItemDetail', () => {
|
|
|
79
79
|
<ConsoleItemDetail
|
|
80
80
|
item={issueItem}
|
|
81
81
|
{...baseProps}
|
|
82
|
-
state={{
|
|
82
|
+
state={{
|
|
83
|
+
state: 'open',
|
|
84
|
+
merged: false,
|
|
85
|
+
isPullRequest: false,
|
|
86
|
+
title: '',
|
|
87
|
+
}}
|
|
83
88
|
/>,
|
|
84
89
|
);
|
|
85
90
|
expect(queryByText('Changed files')).toBeNull();
|
|
@@ -164,7 +169,12 @@ describe('ConsoleItemDetail', () => {
|
|
|
164
169
|
<ConsoleItemDetail
|
|
165
170
|
item={issueItem}
|
|
166
171
|
{...baseProps}
|
|
167
|
-
state={{
|
|
172
|
+
state={{
|
|
173
|
+
state: 'open',
|
|
174
|
+
merged: false,
|
|
175
|
+
isPullRequest: false,
|
|
176
|
+
title: '',
|
|
177
|
+
}}
|
|
168
178
|
pullRequestStatus={null}
|
|
169
179
|
/>,
|
|
170
180
|
);
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
ConsolePullRequestStatus,
|
|
17
17
|
ConsoleRelatedPullRequest,
|
|
18
18
|
} from '../../logic/types';
|
|
19
|
+
import type { ConsoleReferenceLinkRenderer } from '../content/ConsoleMarkdownContent';
|
|
19
20
|
import { ConsoleMarkdownContent } from '../content/ConsoleMarkdownContent';
|
|
20
21
|
import { ConsolePanel } from '../layout/ConsolePanel';
|
|
21
22
|
import { ConsoleChangedFileList } from './ConsoleChangedFileList';
|
|
@@ -61,6 +62,7 @@ export type ConsoleItemDetailProps = {
|
|
|
61
62
|
commentComposer: ReactNode;
|
|
62
63
|
operationBar: ReactNode;
|
|
63
64
|
buildImageProxyUrl?: ImageProxyUrlBuilder;
|
|
65
|
+
renderReferenceLink?: ConsoleReferenceLinkRenderer;
|
|
64
66
|
onAddInlineComment?: ConsoleAddInlineComment;
|
|
65
67
|
buildAddInlineComment?: (prUrl: string) => ConsoleAddInlineComment;
|
|
66
68
|
};
|
|
@@ -89,6 +91,7 @@ export const ConsoleItemDetail = ({
|
|
|
89
91
|
commentComposer,
|
|
90
92
|
operationBar,
|
|
91
93
|
buildImageProxyUrl,
|
|
94
|
+
renderReferenceLink,
|
|
92
95
|
onAddInlineComment,
|
|
93
96
|
buildAddInlineComment,
|
|
94
97
|
}: ConsoleItemDetailProps) => {
|
|
@@ -219,6 +222,7 @@ export const ConsoleItemDetail = ({
|
|
|
219
222
|
<ConsoleMarkdownContent
|
|
220
223
|
body={body}
|
|
221
224
|
buildImageProxyUrl={buildImageProxyUrl}
|
|
225
|
+
renderReferenceLink={renderReferenceLink}
|
|
222
226
|
/>
|
|
223
227
|
)}
|
|
224
228
|
</ConsolePanel>
|
|
@@ -245,6 +249,7 @@ export const ConsoleItemDetail = ({
|
|
|
245
249
|
error={commentsError}
|
|
246
250
|
now={now}
|
|
247
251
|
buildImageProxyUrl={buildImageProxyUrl}
|
|
252
|
+
renderReferenceLink={renderReferenceLink}
|
|
248
253
|
/>
|
|
249
254
|
</ConsolePanel>
|
|
250
255
|
|
|
@@ -274,6 +279,7 @@ export const ConsoleItemDetail = ({
|
|
|
274
279
|
commitsError={related.commitsError}
|
|
275
280
|
now={now}
|
|
276
281
|
buildImageProxyUrl={buildImageProxyUrl}
|
|
282
|
+
renderReferenceLink={renderReferenceLink}
|
|
277
283
|
onAddInlineComment={
|
|
278
284
|
buildAddInlineComment
|
|
279
285
|
? buildAddInlineComment(related.pullRequest.url)
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
ConsoleCommit,
|
|
5
5
|
ConsoleRelatedPullRequest,
|
|
6
6
|
} from '../../logic/types';
|
|
7
|
+
import type { ConsoleReferenceLinkRenderer } from '../content/ConsoleMarkdownContent';
|
|
7
8
|
import { ConsoleMarkdownContent } from '../content/ConsoleMarkdownContent';
|
|
8
9
|
import { ConsolePanel } from '../layout/ConsolePanel';
|
|
9
10
|
import { ConsoleChangedFileList } from './ConsoleChangedFileList';
|
|
@@ -24,6 +25,7 @@ export type ConsolePullRequestSectionProps = {
|
|
|
24
25
|
commitsError: string | null;
|
|
25
26
|
now: number;
|
|
26
27
|
buildImageProxyUrl?: ImageProxyUrlBuilder;
|
|
28
|
+
renderReferenceLink?: ConsoleReferenceLinkRenderer;
|
|
27
29
|
onAddInlineComment?: ConsoleAddInlineComment;
|
|
28
30
|
};
|
|
29
31
|
|
|
@@ -39,6 +41,7 @@ export const ConsolePullRequestDetail = ({
|
|
|
39
41
|
commitsError,
|
|
40
42
|
now,
|
|
41
43
|
buildImageProxyUrl,
|
|
44
|
+
renderReferenceLink,
|
|
42
45
|
onAddInlineComment,
|
|
43
46
|
}: ConsolePullRequestSectionProps) => {
|
|
44
47
|
const summary = pullRequest.summary;
|
|
@@ -90,6 +93,7 @@ export const ConsolePullRequestDetail = ({
|
|
|
90
93
|
<ConsoleMarkdownContent
|
|
91
94
|
body={summary?.body ?? body}
|
|
92
95
|
buildImageProxyUrl={buildImageProxyUrl}
|
|
96
|
+
renderReferenceLink={renderReferenceLink}
|
|
93
97
|
/>
|
|
94
98
|
)}
|
|
95
99
|
</ConsolePanel>
|
|
@@ -63,6 +63,7 @@ const buildCaches = (related: ConsoleRelatedPullRequest[]): ConsoleCaches => {
|
|
|
63
63
|
state: 'open',
|
|
64
64
|
merged: false,
|
|
65
65
|
isPullRequest: true,
|
|
66
|
+
title: 'Console item detail fixture title',
|
|
66
67
|
}),
|
|
67
68
|
fetchPullRequestStatus: async (): Promise<ConsolePullRequestStatus> => ({
|
|
68
69
|
found: true,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
2
|
+
import { ResourceCache } from '../lib/resourceCache';
|
|
3
|
+
import type { ConsoleIssueState } from '../logic/types';
|
|
4
|
+
import { useConsoleReferenceLink } from './useConsoleReferenceLink';
|
|
5
|
+
|
|
6
|
+
const buildState = (title: string): ConsoleIssueState => ({
|
|
7
|
+
state: 'open',
|
|
8
|
+
merged: false,
|
|
9
|
+
isPullRequest: true,
|
|
10
|
+
title,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('useConsoleReferenceLink', () => {
|
|
14
|
+
const url = 'https://github.com/octo/repo/pull/7';
|
|
15
|
+
|
|
16
|
+
it('returns null then the resolved state', async () => {
|
|
17
|
+
const cache = new ResourceCache<ConsoleIssueState>(async () =>
|
|
18
|
+
buildState('Decorate links'),
|
|
19
|
+
);
|
|
20
|
+
const { result } = renderHook(() => useConsoleReferenceLink(cache, url));
|
|
21
|
+
expect(result.current).toBeNull();
|
|
22
|
+
await waitFor(() => {
|
|
23
|
+
expect(result.current?.title).toBe('Decorate links');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns null when the fetch fails', async () => {
|
|
28
|
+
const cache = new ResourceCache<ConsoleIssueState>(async () => {
|
|
29
|
+
throw new Error('boom');
|
|
30
|
+
});
|
|
31
|
+
const { result } = renderHook(() => useConsoleReferenceLink(cache, url));
|
|
32
|
+
await waitFor(() => {
|
|
33
|
+
expect(cache.has(url)).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
expect(result.current).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('reuses an already cached value without refetching', () => {
|
|
39
|
+
const fetcher = jest.fn(async () => buildState('cached'));
|
|
40
|
+
const cache = new ResourceCache<ConsoleIssueState>(fetcher);
|
|
41
|
+
cache.load(url, url);
|
|
42
|
+
return cache.load(url, url).then(() => {
|
|
43
|
+
const { result } = renderHook(() => useConsoleReferenceLink(cache, url));
|
|
44
|
+
expect(result.current?.title).toBe('cached');
|
|
45
|
+
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleReferenceLink.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import type { ResourceCache } from '../lib/resourceCache';
|
|
3
|
+
import type { ConsoleIssueState } from '../logic/types';
|
|
4
|
+
|
|
5
|
+
export const useConsoleReferenceLink = (
|
|
6
|
+
cache: ResourceCache<ConsoleIssueState>,
|
|
7
|
+
url: string,
|
|
8
|
+
): ConsoleIssueState | null => {
|
|
9
|
+
const [state, setState] = useState<ConsoleIssueState | null>(
|
|
10
|
+
() => cache.peek(url) ?? null,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const cached = cache.peek(url);
|
|
15
|
+
if (cached !== undefined) {
|
|
16
|
+
setState(cached);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let cancelled = false;
|
|
20
|
+
setState(null);
|
|
21
|
+
cache
|
|
22
|
+
.load(url, url)
|
|
23
|
+
.then((value) => {
|
|
24
|
+
if (!cancelled) {
|
|
25
|
+
setState(value);
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
.catch(() => {
|
|
29
|
+
if (!cancelled) {
|
|
30
|
+
setState(null);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return () => {
|
|
34
|
+
cancelled = true;
|
|
35
|
+
};
|
|
36
|
+
}, [cache, url]);
|
|
37
|
+
|
|
38
|
+
return state;
|
|
39
|
+
};
|
|
@@ -105,11 +105,34 @@ describe('createConsoleApiClient', () => {
|
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
it('parses the issue state', async () => {
|
|
108
|
-
mockFetchOnce({
|
|
108
|
+
mockFetchOnce({
|
|
109
|
+
state: 'closed',
|
|
110
|
+
merged: true,
|
|
111
|
+
isPullRequest: true,
|
|
112
|
+
title: 'Decorate PR links',
|
|
113
|
+
});
|
|
109
114
|
const client = createConsoleApiClient(appendToken);
|
|
110
115
|
expect(
|
|
111
116
|
await client.fetchIssueState('https://github.com/o/r/pull/1'),
|
|
112
|
-
).toEqual({
|
|
117
|
+
).toEqual({
|
|
118
|
+
state: 'closed',
|
|
119
|
+
merged: true,
|
|
120
|
+
isPullRequest: true,
|
|
121
|
+
title: 'Decorate PR links',
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('defaults the title to an empty string when absent', async () => {
|
|
126
|
+
mockFetchOnce({ state: 'open', merged: false, isPullRequest: false });
|
|
127
|
+
const client = createConsoleApiClient(appendToken);
|
|
128
|
+
expect(
|
|
129
|
+
await client.fetchIssueState('https://github.com/o/r/issues/1'),
|
|
130
|
+
).toEqual({
|
|
131
|
+
state: 'open',
|
|
132
|
+
merged: false,
|
|
133
|
+
isPullRequest: false,
|
|
134
|
+
title: '',
|
|
135
|
+
});
|
|
113
136
|
});
|
|
114
137
|
|
|
115
138
|
it('parses pull request commits', async () => {
|
|
@@ -181,12 +181,13 @@ const parsePullRequestStatus = (payload: unknown): ConsolePullRequestStatus => {
|
|
|
181
181
|
|
|
182
182
|
const parseState = (payload: unknown): ConsoleIssueState => {
|
|
183
183
|
if (!isRecord(payload)) {
|
|
184
|
-
return { state: 'open', merged: false, isPullRequest: false };
|
|
184
|
+
return { state: 'open', merged: false, isPullRequest: false, title: '' };
|
|
185
185
|
}
|
|
186
186
|
return {
|
|
187
187
|
state: getString(payload.state) || 'open',
|
|
188
188
|
merged: getBoolean(payload.merged),
|
|
189
189
|
isPullRequest: getBoolean(payload.isPullRequest),
|
|
190
|
+
title: getString(payload.title),
|
|
190
191
|
};
|
|
191
192
|
};
|
|
192
193
|
|