@atlaspack/inspector-frontend 0.1.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/.atlaspackrc +4 -0
- package/.eslintrc.json +19 -0
- package/CHANGELOG.md +12 -0
- package/dist/atlassian-dark-brand-refresh.91b786da.js +2 -0
- package/dist/atlassian-dark-brand-refresh.91b786da.js.map +1 -0
- package/dist/atlassian-dark-future.59ebadca.js +2 -0
- package/dist/atlassian-dark-future.59ebadca.js.map +1 -0
- package/dist/atlassian-dark-increased-contrast.ff6775f2.js +2 -0
- package/dist/atlassian-dark-increased-contrast.ff6775f2.js.map +1 -0
- package/dist/atlassian-dark.ad679134.js +2 -0
- package/dist/atlassian-dark.ad679134.js.map +1 -0
- package/dist/atlassian-legacy-dark.8aa27f7f.js +2 -0
- package/dist/atlassian-legacy-dark.8aa27f7f.js.map +1 -0
- package/dist/atlassian-legacy-light.2eb372ce.js +2 -0
- package/dist/atlassian-legacy-light.2eb372ce.js.map +1 -0
- package/dist/atlassian-light-brand-refresh.fadcab0a.js +2 -0
- package/dist/atlassian-light-brand-refresh.fadcab0a.js.map +1 -0
- package/dist/atlassian-light-future.612afe8a.js +2 -0
- package/dist/atlassian-light-future.612afe8a.js.map +1 -0
- package/dist/atlassian-light-increased-contrast.7161cd79.js +2 -0
- package/dist/atlassian-light-increased-contrast.7161cd79.js.map +1 -0
- package/dist/atlassian-light.bc343d4c.js +2 -0
- package/dist/atlassian-light.bc343d4c.js.map +1 -0
- package/dist/atlassian-shape.b92d69c0.js +2 -0
- package/dist/atlassian-shape.b92d69c0.js.map +1 -0
- package/dist/atlassian-spacing.60ddd8e7.js +2 -0
- package/dist/atlassian-spacing.60ddd8e7.js.map +1 -0
- package/dist/atlassian-typography-adg3.f88947f6.js +2 -0
- package/dist/atlassian-typography-adg3.f88947f6.js.map +1 -0
- package/dist/atlassian-typography-modernized.42016c51.js +2 -0
- package/dist/atlassian-typography-modernized.42016c51.js.map +1 -0
- package/dist/atlassian-typography-refreshed.ec0d111b.js +2 -0
- package/dist/atlassian-typography-refreshed.ec0d111b.js.map +1 -0
- package/dist/atlassian-typography.66d7e8f4.js +2 -0
- package/dist/atlassian-typography.66d7e8f4.js.map +1 -0
- package/dist/badge-light.7e55986a.png +0 -0
- package/dist/custom-theme.4680282a.js +2 -0
- package/dist/custom-theme.4680282a.js.map +1 -0
- package/dist/drag-handle.136830d3.js +2 -0
- package/dist/drag-handle.136830d3.js.map +1 -0
- package/dist/drag-handle.63bdb345.css +2 -0
- package/dist/drag-handle.63bdb345.css.map +1 -0
- package/dist/index.13289f53.js +28 -0
- package/dist/index.13289f53.js.map +1 -0
- package/dist/index.a41fafce.css +2 -0
- package/dist/index.a41fafce.css.map +1 -0
- package/dist/index.html +1 -0
- package/dist/index.runtime.3c39d71d.js +2 -0
- package/dist/index.runtime.3c39d71d.js.map +1 -0
- package/dist/refractor.2c1fd9a1.js +2 -0
- package/dist/refractor.2c1fd9a1.js.map +1 -0
- package/index.html +11 -0
- package/jest.config.js +16 -0
- package/package.json +64 -0
- package/src/APIError.test.ts +72 -0
- package/src/APIError.tsx +29 -0
- package/src/AppRoutes.tsx +56 -0
- package/src/hack-feature-flags.ts +6 -0
- package/src/main.tsx +50 -0
- package/src/test/stubCssModule.js +1 -0
- package/src/ui/App.module.css +122 -0
- package/src/ui/App.module.css.d.ts +8 -0
- package/src/ui/AppLayout/AppLayout.tsx +26 -0
- package/src/ui/AppLayout/SidebarNavigation/LinkItem.tsx +26 -0
- package/src/ui/AppLayout/SidebarNavigation/SidebarNavigation.tsx +45 -0
- package/src/ui/AppLayout/TopNavigation/Logo.module.css +12 -0
- package/src/ui/AppLayout/TopNavigation/Logo.module.css.d.ts +4 -0
- package/src/ui/AppLayout/TopNavigation/Logo.tsx +11 -0
- package/src/ui/AppLayout/TopNavigation/TopNavigation.module.css +14 -0
- package/src/ui/AppLayout/TopNavigation/TopNavigation.module.css.d.ts +3 -0
- package/src/ui/AppLayout/TopNavigation/TopNavigation.tsx +45 -0
- package/src/ui/AppLayout/TopNavigation/badge-light.png +0 -0
- package/src/ui/AppLayout/TopNavigation/logo-light.png +0 -0
- package/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.module.css +9 -0
- package/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.module.css.d.ts +3 -0
- package/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.test.tsx +15 -0
- package/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.tsx +14 -0
- package/src/ui/app/StatsPage.tsx +77 -0
- package/src/ui/app/cache/CacheKeysIndexPage.tsx +13 -0
- package/src/ui/app/cache/CacheKeysPage.module.css +11 -0
- package/src/ui/app/cache/CacheKeysPage.module.css.d.ts +4 -0
- package/src/ui/app/cache/CacheKeysPage.tsx +23 -0
- package/src/ui/app/cache/[key]/CacheValuePage.tsx +40 -0
- package/src/ui/app/cache/ui/CacheKeyList.module.css +40 -0
- package/src/ui/app/cache/ui/CacheKeyList.module.css.d.ts +7 -0
- package/src/ui/app/cache/ui/CacheKeyList.tsx +187 -0
- package/src/ui/app/cache-invalidation/CacheInvalidationPage.tsx +22 -0
- package/src/ui/app/cache-invalidation/[fileId]/CacheInvalidationFilePage.tsx +22 -0
- package/src/ui/app/cache-invalidation/ui/CacheFileList.module.css +40 -0
- package/src/ui/app/cache-invalidation/ui/CacheFileList.module.css.d.ts +7 -0
- package/src/ui/app/cache-invalidation/ui/CacheFileList.tsx +185 -0
- package/src/ui/app/treemap/BottomPanelResizeState.test.ts +25 -0
- package/src/ui/app/treemap/BottomPanelResizeState.tsx +48 -0
- package/src/ui/app/treemap/FoamTreemapPage.module.css +24 -0
- package/src/ui/app/treemap/FoamTreemapPage.module.css.d.ts +6 -0
- package/src/ui/app/treemap/FoamTreemapPage.tsx +47 -0
- package/src/ui/app/treemap/controllers/RelatedBundlesController.tsx +41 -0
- package/src/ui/app/treemap/controllers/UrlFocusController.tsx +33 -0
- package/src/ui/app/treemap/ui/BottomPanel/BottomPanel.module.css +24 -0
- package/src/ui/app/treemap/ui/BottomPanel/BottomPanel.module.css.d.ts +5 -0
- package/src/ui/app/treemap/ui/BottomPanel/BottomPanel.tsx +24 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.module.css +13 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.module.css.d.ts +5 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.tsx +53 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/AssetTable.tsx +135 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.module.css +7 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.module.css.d.ts +3 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.tsx +123 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableModel.tsx +18 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.module.css +20 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.module.css.d.ts +6 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.tsx +79 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/getFileURL.test.ts +19 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/getFileURL.ts +24 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.module.css +20 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.module.css.d.ts +5 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.tsx +42 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.module.css +29 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.module.css.d.ts +6 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.tsx +107 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.module.css +7 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.module.css.d.ts +3 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.tsx +20 -0
- package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/SourceCodeURL.tsx +5 -0
- package/src/ui/app/treemap/ui/BundleGraphRenderer.module.css +13 -0
- package/src/ui/app/treemap/ui/BundleGraphRenderer.module.css.d.ts +4 -0
- package/src/ui/app/treemap/ui/BundleGraphRenderer.tsx +95 -0
- package/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.module.css +6 -0
- package/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.module.css.d.ts +3 -0
- package/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.tsx +49 -0
- package/src/ui/app/treemap/ui/SigmaGraph.module.css +5 -0
- package/src/ui/app/treemap/ui/SigmaGraph.module.css.d.ts +3 -0
- package/src/ui/app/treemap/ui/SigmaGraph.tsx +80 -0
- package/src/ui/app/treemap/ui/Treemap.tsx +14 -0
- package/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.module.css +32 -0
- package/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.module.css.d.ts +5 -0
- package/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.tsx +24 -0
- package/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.module.css +14 -0
- package/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.module.css.d.ts +4 -0
- package/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.tsx +271 -0
- package/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.module.css +15 -0
- package/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.module.css.d.ts +4 -0
- package/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.tsx +111 -0
- package/src/ui/app/treemap/ui/TreemapRenderer/controllers/useStableCallback.test.ts +27 -0
- package/src/ui/app/treemap/ui/TreemapRenderer/controllers/useStableCallback.ts +21 -0
- package/src/ui/app/treemap/ui/TreemapRenderer/useMouseMoveController.ts +20 -0
- package/src/ui/globals.css +26 -0
- package/src/ui/globals.css.d.ts +1 -0
- package/src/ui/globals.d.ts +9 -0
- package/src/ui/model/ViewModel.test.ts +31 -0
- package/src/ui/model/ViewModel.ts +62 -0
- package/src/ui/not-found/NotFoundPage.module.css +7 -0
- package/src/ui/not-found/NotFoundPage.module.css.d.ts +3 -0
- package/src/ui/not-found/NotFoundPage.tsx +9 -0
- package/src/ui/types/Graph.tsx +12 -0
- package/src/ui/util/ErrorBoundary.module.css +3 -0
- package/src/ui/util/ErrorBoundary.module.css.d.ts +3 -0
- package/src/ui/util/ErrorBoundary.test.tsx +65 -0
- package/src/ui/util/ErrorBoundary.tsx +75 -0
- package/src/ui/util/colorPalette.tsx +122 -0
- package/src/ui/util/formatBytes.test.ts +13 -0
- package/src/ui/util/formatBytes.tsx +9 -0
- package/src/ui/util/getRandomDarkerColor.tsx +31 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {observer} from 'mobx-react-lite';
|
|
2
|
+
import {CollapsibleTableModel} from './CollapsibleTableModel';
|
|
3
|
+
import {runInAction} from 'mobx';
|
|
4
|
+
import {CollapsibleTableRow} from './CollapsibleTableRow';
|
|
5
|
+
|
|
6
|
+
import * as styles from './CollapsibleTable.module.css';
|
|
7
|
+
|
|
8
|
+
function limit(value: number, len: number) {
|
|
9
|
+
if (value < 0) {
|
|
10
|
+
return len - (Math.abs(value) % len);
|
|
11
|
+
}
|
|
12
|
+
return value % len;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface CollapsibleTableProps {
|
|
16
|
+
model: CollapsibleTableModel;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const CollapsibleTable = observer(({model}: CollapsibleTableProps) => {
|
|
20
|
+
function focusOnNode(nodeId: string) {
|
|
21
|
+
runInAction(() => {
|
|
22
|
+
model.focusedNodeId = nodeId;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const node = document.querySelector(
|
|
26
|
+
`[data-nodeid="${model.focusedNodeId}"]`,
|
|
27
|
+
);
|
|
28
|
+
if (node) {
|
|
29
|
+
node.scrollIntoView({behavior: 'smooth', block: 'center'});
|
|
30
|
+
(node as HTMLElement).focus();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Handles keyboard navigation in 4 directions.
|
|
36
|
+
*/
|
|
37
|
+
function onKeyDown(e: React.KeyboardEvent<HTMLTableElement>) {
|
|
38
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
e.stopPropagation();
|
|
41
|
+
|
|
42
|
+
runInAction(() => {
|
|
43
|
+
const current = model.flatNodeList.findIndex(
|
|
44
|
+
(node: any) => node.id === model.focusedNodeId,
|
|
45
|
+
);
|
|
46
|
+
if (current === -1) {
|
|
47
|
+
focusOnNode(model.flatNodeList[0].id);
|
|
48
|
+
} else {
|
|
49
|
+
const increment = e.key === 'ArrowDown' ? 1 : -1;
|
|
50
|
+
focusOnNode(
|
|
51
|
+
model.flatNodeList[
|
|
52
|
+
limit(current + increment, model.flatNodeList.length)
|
|
53
|
+
].id,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
} else if (e.key === 'ArrowRight') {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
e.stopPropagation();
|
|
60
|
+
|
|
61
|
+
const current = model.flatNodeList.findIndex(
|
|
62
|
+
(node: any) => node.id === model.focusedNodeId,
|
|
63
|
+
);
|
|
64
|
+
runInAction(() => {
|
|
65
|
+
if (current !== -1) {
|
|
66
|
+
model.flatNodeList[current].isExpanded = true;
|
|
67
|
+
|
|
68
|
+
const newNode =
|
|
69
|
+
model.flatNodeList[limit(current + 1, model.flatNodeList.length)];
|
|
70
|
+
if (newNode.level > model.flatNodeList[current].level) {
|
|
71
|
+
focusOnNode(newNode.id);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
} else if (e.key === 'ArrowLeft') {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
e.stopPropagation();
|
|
78
|
+
|
|
79
|
+
const current = model.flatNodeList.find(
|
|
80
|
+
(node: any) => node.id === model.focusedNodeId,
|
|
81
|
+
);
|
|
82
|
+
if (current && current.isExpanded) {
|
|
83
|
+
runInAction(() => {
|
|
84
|
+
current.isExpanded = false;
|
|
85
|
+
});
|
|
86
|
+
} else if (current && current.parent) {
|
|
87
|
+
focusOnNode(current.parent);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* When a node is focused, walk up until we find the `data-nodeid` of the focused row,
|
|
94
|
+
* then update view model to focus on that node.
|
|
95
|
+
*/
|
|
96
|
+
function onFocus(e: React.FocusEvent<HTMLTableElement>) {
|
|
97
|
+
let current = e.target as HTMLElement;
|
|
98
|
+
while (current && !current.getAttribute('data-nodeid')) {
|
|
99
|
+
current = current.parentElement as HTMLElement;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
runInAction(() => {
|
|
103
|
+
model.focusedNodeId = current.getAttribute('data-nodeid');
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div className={styles.collapsibleTable}>
|
|
109
|
+
<table onKeyDown={onKeyDown} onFocus={onFocus}>
|
|
110
|
+
<tbody>
|
|
111
|
+
{model.flatNodeList.map((node, i) => (
|
|
112
|
+
<CollapsibleTableRow
|
|
113
|
+
key={i}
|
|
114
|
+
node={node}
|
|
115
|
+
level={node.level}
|
|
116
|
+
model={model}
|
|
117
|
+
/>
|
|
118
|
+
))}
|
|
119
|
+
</tbody>
|
|
120
|
+
</table>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface CollapsibleTableModel {
|
|
2
|
+
nodes: CollapsibleTableNode[];
|
|
3
|
+
focusedNodeId: string | null;
|
|
4
|
+
flatNodeList: CollapsibleTableNode[];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CollapsibleTableNode {
|
|
8
|
+
id: string;
|
|
9
|
+
path: string;
|
|
10
|
+
sourceCodeUrl: {
|
|
11
|
+
url: string;
|
|
12
|
+
type: 'github' | 'bitbucket';
|
|
13
|
+
} | null;
|
|
14
|
+
isExpanded: boolean;
|
|
15
|
+
children: CollapsibleTableNode[];
|
|
16
|
+
parent: string | null;
|
|
17
|
+
level: number;
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.collapsibleTableRow {
|
|
2
|
+
height: 20px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.collapsibleTableRowPath {
|
|
6
|
+
vertical-align: baseline;
|
|
7
|
+
padding-left: 16px;
|
|
8
|
+
display: flex;
|
|
9
|
+
gap: 8px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.collapsibleTableRowPathButton {
|
|
13
|
+
border: none;
|
|
14
|
+
background: none;
|
|
15
|
+
width: 16px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.collapsibleTableRowPathPlaceholder {
|
|
19
|
+
width: 16px;
|
|
20
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {Fragment, useEffect} from 'react';
|
|
2
|
+
import {observer} from 'mobx-react-lite';
|
|
3
|
+
import {runInAction} from 'mobx';
|
|
4
|
+
import {BitbucketIcon} from '@atlaskit/logo';
|
|
5
|
+
|
|
6
|
+
import * as styles from './CollapsibleTableRow.module.css';
|
|
7
|
+
import {
|
|
8
|
+
CollapsibleTableModel,
|
|
9
|
+
CollapsibleTableNode,
|
|
10
|
+
} from './CollapsibleTableModel';
|
|
11
|
+
|
|
12
|
+
interface CollapsibleTableRowProps {
|
|
13
|
+
model: CollapsibleTableModel;
|
|
14
|
+
node: CollapsibleTableNode;
|
|
15
|
+
level: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const CollapsibleTableRow = observer(
|
|
19
|
+
({node, level, model}: CollapsibleTableRowProps) => {
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (model.focusedNodeId === node.id) {
|
|
22
|
+
const row = document.querySelector(
|
|
23
|
+
`[data-nodeid="${model.focusedNodeId}"]`,
|
|
24
|
+
);
|
|
25
|
+
if (row) {
|
|
26
|
+
(row as HTMLElement).focus();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}, [model, node]);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Fragment>
|
|
33
|
+
<tr
|
|
34
|
+
className={styles.collapsibleTableRow}
|
|
35
|
+
data-nodeid={node.id}
|
|
36
|
+
tabIndex={0}
|
|
37
|
+
autoFocus={model.focusedNodeId === node.id}
|
|
38
|
+
>
|
|
39
|
+
<td
|
|
40
|
+
className={styles.collapsibleTableRowPath}
|
|
41
|
+
style={{paddingLeft: level * 16}}
|
|
42
|
+
>
|
|
43
|
+
{node.children.length > 0 ? (
|
|
44
|
+
<button
|
|
45
|
+
onClick={() => {
|
|
46
|
+
runInAction(() => {
|
|
47
|
+
node.isExpanded = !node.isExpanded;
|
|
48
|
+
});
|
|
49
|
+
}}
|
|
50
|
+
className={styles.collapsibleTableRowPathButton}
|
|
51
|
+
>
|
|
52
|
+
{node.isExpanded ? '▼' : '▶'}
|
|
53
|
+
</button>
|
|
54
|
+
) : (
|
|
55
|
+
<span className={styles.collapsibleTableRowPathPlaceholder} />
|
|
56
|
+
)}
|
|
57
|
+
|
|
58
|
+
{node.path}
|
|
59
|
+
</td>
|
|
60
|
+
{node.sourceCodeUrl && (
|
|
61
|
+
<td>
|
|
62
|
+
<a
|
|
63
|
+
href={node.sourceCodeUrl.url}
|
|
64
|
+
rel="noopener noreferrer"
|
|
65
|
+
target="_blank"
|
|
66
|
+
>
|
|
67
|
+
{node.sourceCodeUrl.type === 'github' ? (
|
|
68
|
+
<>GitHub link</>
|
|
69
|
+
) : (
|
|
70
|
+
<BitbucketIcon size="small" />
|
|
71
|
+
)}
|
|
72
|
+
</a>
|
|
73
|
+
</td>
|
|
74
|
+
)}
|
|
75
|
+
</tr>
|
|
76
|
+
</Fragment>
|
|
77
|
+
);
|
|
78
|
+
},
|
|
79
|
+
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {getFileURL} from './getFileURL';
|
|
2
|
+
|
|
3
|
+
describe('getFileURL', () => {
|
|
4
|
+
it('should return the correct URL for a file in the project', () => {
|
|
5
|
+
const fileURL = getFileURL(
|
|
6
|
+
'jira/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/AssetTable.tsx',
|
|
7
|
+
{
|
|
8
|
+
owner: 'atlassian',
|
|
9
|
+
repo: 'atlassian-frontend',
|
|
10
|
+
type: 'bitbucket',
|
|
11
|
+
},
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
expect(fileURL).toEqual({
|
|
15
|
+
type: 'bitbucket',
|
|
16
|
+
url: 'https://bitbucket.org/atlassian/atlassian-frontend/src/master/jira/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/AssetTable.tsx',
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {SourceCodeURL} from '../SourceCodeURL';
|
|
2
|
+
|
|
3
|
+
export function getFileURL(
|
|
4
|
+
filePath: string,
|
|
5
|
+
sourceCodeURL: SourceCodeURL | null,
|
|
6
|
+
): {url: string; type: 'github' | 'bitbucket'} | null {
|
|
7
|
+
if (!sourceCodeURL) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (sourceCodeURL.type === 'github') {
|
|
12
|
+
return {
|
|
13
|
+
url: `https://github.com/${sourceCodeURL.owner}/${sourceCodeURL.repo}/blob/master/${filePath}`,
|
|
14
|
+
type: 'github',
|
|
15
|
+
};
|
|
16
|
+
} else if (sourceCodeURL.type === 'bitbucket') {
|
|
17
|
+
return {
|
|
18
|
+
url: `https://bitbucket.org/${sourceCodeURL.owner}/${sourceCodeURL.repo}/src/master/${filePath}`,
|
|
19
|
+
type: 'bitbucket',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.focusedGroupInfoBundleGraph {
|
|
2
|
+
height: 100%;
|
|
3
|
+
width: 100%;
|
|
4
|
+
padding-top: 8px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.focusedGroupInfoAdvancedSettings {
|
|
8
|
+
overflow-x: hidden;
|
|
9
|
+
overflow-y: auto;
|
|
10
|
+
height: calc(100% - 8px);
|
|
11
|
+
width: 100%;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.focusedGroupInfoAssetTable {
|
|
15
|
+
overflow-x: hidden;
|
|
16
|
+
overflow-y: auto;
|
|
17
|
+
height: 100%;
|
|
18
|
+
width: 100%;
|
|
19
|
+
margin-top: 8px;
|
|
20
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {useSearchParams} from 'react-router';
|
|
2
|
+
import {observer} from 'mobx-react-lite';
|
|
3
|
+
import Tabs, {Tab, TabList, TabPanel} from '@atlaskit/tabs';
|
|
4
|
+
import {viewModel} from '../../../../../model/ViewModel';
|
|
5
|
+
import {GraphContainer} from './GraphContainer';
|
|
6
|
+
import {BundleGraphRenderer} from '../../BundleGraphRenderer';
|
|
7
|
+
import {AdvancedSettings} from './AdvancedSettings';
|
|
8
|
+
import * as styles from './FocusedGroupInfo.module.css';
|
|
9
|
+
import {FocusedGroupInfoInner} from './FocusedGroupInfoInner';
|
|
10
|
+
|
|
11
|
+
export const FocusedGroupInfo = observer(() => {
|
|
12
|
+
const [searchParams] = useSearchParams();
|
|
13
|
+
const bundle = searchParams.get('bundle');
|
|
14
|
+
if (!viewModel.focusedGroup || !bundle) {
|
|
15
|
+
return (
|
|
16
|
+
<Tabs id="focused-group-info-tabs">
|
|
17
|
+
<TabList>
|
|
18
|
+
<Tab>Bundle graph</Tab>
|
|
19
|
+
<Tab>Advanced settings</Tab>
|
|
20
|
+
</TabList>
|
|
21
|
+
|
|
22
|
+
<TabPanel>
|
|
23
|
+
<div className={styles.focusedGroupInfoBundleGraph}>
|
|
24
|
+
<GraphContainer fullWidth>
|
|
25
|
+
<BundleGraphRenderer />
|
|
26
|
+
</GraphContainer>
|
|
27
|
+
</div>
|
|
28
|
+
</TabPanel>
|
|
29
|
+
|
|
30
|
+
<TabPanel>
|
|
31
|
+
<div className={styles.focusedGroupInfoAdvancedSettings}>
|
|
32
|
+
<AdvancedSettings />
|
|
33
|
+
</div>
|
|
34
|
+
</TabPanel>
|
|
35
|
+
</Tabs>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<FocusedGroupInfoInner group={viewModel.focusedGroup} bundle={bundle} />
|
|
41
|
+
);
|
|
42
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.focusedGroupInfoInner {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: row;
|
|
4
|
+
height: 100%;
|
|
5
|
+
width: 100%;
|
|
6
|
+
gap: 8px;
|
|
7
|
+
padding-bottom: 8px;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.focusedGroupInfoInnerAdvancedSettings {
|
|
11
|
+
overflow-x: hidden;
|
|
12
|
+
overflow-y: auto;
|
|
13
|
+
height: calc(100% - 8px);
|
|
14
|
+
width: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.focusedGroupInfoInnerAssetTable {
|
|
18
|
+
overflow-x: hidden;
|
|
19
|
+
overflow-y: auto;
|
|
20
|
+
height: 100%;
|
|
21
|
+
width: 100%;
|
|
22
|
+
margin-top: 8px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.focusedGroupInfoInnerGraphContainer {
|
|
26
|
+
height: 100%;
|
|
27
|
+
width: 400px;
|
|
28
|
+
display: flex;
|
|
29
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {observer} from 'mobx-react-lite';
|
|
2
|
+
import Tabs, {Tab, TabList, TabPanel} from '@atlaskit/tabs';
|
|
3
|
+
import {Group, viewModel} from '../../../../../model/ViewModel';
|
|
4
|
+
import {GraphContainer} from './GraphContainer';
|
|
5
|
+
import {AdvancedSettings} from './AdvancedSettings';
|
|
6
|
+
import {useSuspenseQuery} from '@tanstack/react-query';
|
|
7
|
+
import {useMemo} from 'react';
|
|
8
|
+
import {Graph} from '../../../../../types/Graph';
|
|
9
|
+
import {formatBytes} from '../../../../../util/formatBytes';
|
|
10
|
+
import {SigmaGraph} from '../../SigmaGraph';
|
|
11
|
+
import {AssetTable} from './AssetTable/AssetTable';
|
|
12
|
+
import qs from 'qs';
|
|
13
|
+
import {Stack} from '@atlaskit/primitives';
|
|
14
|
+
|
|
15
|
+
import * as styles from './FocusedGroupInfoInner.module.css';
|
|
16
|
+
import {SourceCodeURL} from './SourceCodeURL';
|
|
17
|
+
|
|
18
|
+
export const FocusedGroupInfoInner = observer(
|
|
19
|
+
({group, bundle}: {group: Group; bundle: string}) => {
|
|
20
|
+
const {data} = useSuspenseQuery<{
|
|
21
|
+
relevantPaths: string[][];
|
|
22
|
+
sourceCodeURL: SourceCodeURL | null;
|
|
23
|
+
projectRoot: string;
|
|
24
|
+
repositoryRoot: string;
|
|
25
|
+
}>({
|
|
26
|
+
queryKey: [
|
|
27
|
+
'/api/treemap/reasons?' +
|
|
28
|
+
qs.stringify({
|
|
29
|
+
path:
|
|
30
|
+
group.type === 'asset' ? group.id.split('::')[1].slice(1) : '',
|
|
31
|
+
bundle,
|
|
32
|
+
}),
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const graph = useMemo(() => {
|
|
37
|
+
const graph: Graph<any> = {
|
|
38
|
+
nodes: [],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
for (const path of data.relevantPaths) {
|
|
42
|
+
for (let i = 0; i < path.length; i++) {
|
|
43
|
+
const node = {
|
|
44
|
+
id: path[i],
|
|
45
|
+
nodeId: path[i],
|
|
46
|
+
displayName: path[i],
|
|
47
|
+
path: path,
|
|
48
|
+
level: i,
|
|
49
|
+
edges: i < path.length - 1 ? [path[i + 1]] : [],
|
|
50
|
+
extra: null,
|
|
51
|
+
};
|
|
52
|
+
graph.nodes.push(node);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return graph;
|
|
57
|
+
}, [data]);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className={styles.focusedGroupInfoInner}>
|
|
61
|
+
<Tabs id="focused-group-info-tabs">
|
|
62
|
+
<TabList>
|
|
63
|
+
<Tab>Bottom-up</Tab>
|
|
64
|
+
<Tab>Top-down</Tab>
|
|
65
|
+
<Tab>Advanced settings</Tab>
|
|
66
|
+
</TabList>
|
|
67
|
+
|
|
68
|
+
<TabPanel>
|
|
69
|
+
<div className={styles.focusedGroupInfoInnerAssetTable}>
|
|
70
|
+
<AssetTable data={data} isBottomUp />
|
|
71
|
+
</div>
|
|
72
|
+
</TabPanel>
|
|
73
|
+
|
|
74
|
+
<TabPanel>
|
|
75
|
+
<div className={styles.focusedGroupInfoInnerAssetTable}>
|
|
76
|
+
<AssetTable data={data} isBottomUp={false} />
|
|
77
|
+
</div>
|
|
78
|
+
</TabPanel>
|
|
79
|
+
|
|
80
|
+
<TabPanel>
|
|
81
|
+
<div className={styles.focusedGroupInfoInnerAdvancedSettings}>
|
|
82
|
+
<AdvancedSettings />
|
|
83
|
+
</div>
|
|
84
|
+
</TabPanel>
|
|
85
|
+
</Tabs>
|
|
86
|
+
|
|
87
|
+
<div className={styles.focusedGroupInfoInnerGraphContainer}>
|
|
88
|
+
<Stack space="space.100" grow="fill">
|
|
89
|
+
<Stack space="space.050">
|
|
90
|
+
<strong>{viewModel.focusedGroup!.label}</strong>
|
|
91
|
+
{formatBytes(
|
|
92
|
+
viewModel.focusedGroup?.assetTreeSize ??
|
|
93
|
+
viewModel.focusedGroup!.weight,
|
|
94
|
+
)}
|
|
95
|
+
</Stack>
|
|
96
|
+
|
|
97
|
+
<Stack grow="fill">
|
|
98
|
+
<GraphContainer>
|
|
99
|
+
<SigmaGraph graph={graph} />
|
|
100
|
+
</GraphContainer>
|
|
101
|
+
</Stack>
|
|
102
|
+
</Stack>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
},
|
|
107
|
+
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as styles from './GraphContainer.module.css';
|
|
2
|
+
|
|
3
|
+
export function GraphContainer({
|
|
4
|
+
children,
|
|
5
|
+
fullWidth = false,
|
|
6
|
+
}: {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
fullWidth?: boolean;
|
|
9
|
+
}) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
className={styles.graphContainer}
|
|
13
|
+
style={{
|
|
14
|
+
width: fullWidth ? '100%' : 300,
|
|
15
|
+
}}
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import Graphology from 'graphology';
|
|
2
|
+
import {useRef} from 'react';
|
|
3
|
+
import forceAtlas2 from 'graphology-layout-forceatlas2';
|
|
4
|
+
import FA2Layout from 'graphology-layout-forceatlas2/worker';
|
|
5
|
+
import {useEffect} from 'react';
|
|
6
|
+
import Sigma from 'sigma';
|
|
7
|
+
import {useSearchParams} from 'react-router';
|
|
8
|
+
import {useQuery} from '@tanstack/react-query';
|
|
9
|
+
import qs from 'qs';
|
|
10
|
+
import Spinner from '@atlaskit/spinner';
|
|
11
|
+
|
|
12
|
+
import {Graph} from '../../../types/Graph';
|
|
13
|
+
import * as styles from './BundleGraphRenderer.module.css';
|
|
14
|
+
|
|
15
|
+
function setup(container: HTMLDivElement, graph: Graphology) {
|
|
16
|
+
const sensibleSettings = forceAtlas2.inferSettings(graph);
|
|
17
|
+
const fa2Layout = new FA2Layout(graph, {
|
|
18
|
+
settings: sensibleSettings,
|
|
19
|
+
});
|
|
20
|
+
fa2Layout.start();
|
|
21
|
+
|
|
22
|
+
const renderer: any = new Sigma(graph, container, {
|
|
23
|
+
allowInvalidContainer: true,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
renderer.kill();
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type BundleGraph = Graph<{size: number}>;
|
|
32
|
+
|
|
33
|
+
export function BundleGraphRenderer() {
|
|
34
|
+
const [searchParams] = useSearchParams();
|
|
35
|
+
const rootNodeId = searchParams.get('rootNodeId');
|
|
36
|
+
const visualizationRef = useRef<HTMLDivElement>(null);
|
|
37
|
+
const {
|
|
38
|
+
data: bundleGraph,
|
|
39
|
+
isLoading: isLoadingBundleGraph,
|
|
40
|
+
error: errorBundleGraph,
|
|
41
|
+
} = useQuery<BundleGraph>({
|
|
42
|
+
queryKey: [`/api/bundle-graph?${qs.stringify({rootNodeId})}`],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (visualizationRef.current && bundleGraph) {
|
|
47
|
+
const graph = new Graphology();
|
|
48
|
+
const nodes = new Set<string>();
|
|
49
|
+
for (let node of bundleGraph.nodes) {
|
|
50
|
+
nodes.add(node.id);
|
|
51
|
+
graph.addNode(node.id, {
|
|
52
|
+
label: node.displayName,
|
|
53
|
+
// color: getRandomDarkerColor(node.displayName).family[2],
|
|
54
|
+
x: Math.random() * 10000,
|
|
55
|
+
y: Math.random() * 10000,
|
|
56
|
+
size:
|
|
57
|
+
node.id === '@@root'
|
|
58
|
+
? 4
|
|
59
|
+
: node.extra?.size
|
|
60
|
+
? node.extra.size / 500000
|
|
61
|
+
: 2,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
for (let node of bundleGraph.nodes) {
|
|
65
|
+
for (let edge of node.edges) {
|
|
66
|
+
if (nodes.has(node.id) && nodes.has(edge)) {
|
|
67
|
+
graph.addEdge(node.id, edge, {
|
|
68
|
+
size: 0.1,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return setup(visualizationRef.current, graph);
|
|
75
|
+
}
|
|
76
|
+
}, [bundleGraph]);
|
|
77
|
+
|
|
78
|
+
if (isLoadingBundleGraph) {
|
|
79
|
+
return (
|
|
80
|
+
<div className={styles.loadingIndicator}>
|
|
81
|
+
<Spinner size="large" />
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (errorBundleGraph) {
|
|
87
|
+
return <div>Error: {errorBundleGraph.message}</div>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!bundleGraph) {
|
|
91
|
+
throw new Error('No bundle graph');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return <div className={styles.expander} ref={visualizationRef} />;
|
|
95
|
+
}
|