@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.
Files changed (164) hide show
  1. package/.atlaspackrc +4 -0
  2. package/.eslintrc.json +19 -0
  3. package/CHANGELOG.md +12 -0
  4. package/dist/atlassian-dark-brand-refresh.91b786da.js +2 -0
  5. package/dist/atlassian-dark-brand-refresh.91b786da.js.map +1 -0
  6. package/dist/atlassian-dark-future.59ebadca.js +2 -0
  7. package/dist/atlassian-dark-future.59ebadca.js.map +1 -0
  8. package/dist/atlassian-dark-increased-contrast.ff6775f2.js +2 -0
  9. package/dist/atlassian-dark-increased-contrast.ff6775f2.js.map +1 -0
  10. package/dist/atlassian-dark.ad679134.js +2 -0
  11. package/dist/atlassian-dark.ad679134.js.map +1 -0
  12. package/dist/atlassian-legacy-dark.8aa27f7f.js +2 -0
  13. package/dist/atlassian-legacy-dark.8aa27f7f.js.map +1 -0
  14. package/dist/atlassian-legacy-light.2eb372ce.js +2 -0
  15. package/dist/atlassian-legacy-light.2eb372ce.js.map +1 -0
  16. package/dist/atlassian-light-brand-refresh.fadcab0a.js +2 -0
  17. package/dist/atlassian-light-brand-refresh.fadcab0a.js.map +1 -0
  18. package/dist/atlassian-light-future.612afe8a.js +2 -0
  19. package/dist/atlassian-light-future.612afe8a.js.map +1 -0
  20. package/dist/atlassian-light-increased-contrast.7161cd79.js +2 -0
  21. package/dist/atlassian-light-increased-contrast.7161cd79.js.map +1 -0
  22. package/dist/atlassian-light.bc343d4c.js +2 -0
  23. package/dist/atlassian-light.bc343d4c.js.map +1 -0
  24. package/dist/atlassian-shape.b92d69c0.js +2 -0
  25. package/dist/atlassian-shape.b92d69c0.js.map +1 -0
  26. package/dist/atlassian-spacing.60ddd8e7.js +2 -0
  27. package/dist/atlassian-spacing.60ddd8e7.js.map +1 -0
  28. package/dist/atlassian-typography-adg3.f88947f6.js +2 -0
  29. package/dist/atlassian-typography-adg3.f88947f6.js.map +1 -0
  30. package/dist/atlassian-typography-modernized.42016c51.js +2 -0
  31. package/dist/atlassian-typography-modernized.42016c51.js.map +1 -0
  32. package/dist/atlassian-typography-refreshed.ec0d111b.js +2 -0
  33. package/dist/atlassian-typography-refreshed.ec0d111b.js.map +1 -0
  34. package/dist/atlassian-typography.66d7e8f4.js +2 -0
  35. package/dist/atlassian-typography.66d7e8f4.js.map +1 -0
  36. package/dist/badge-light.7e55986a.png +0 -0
  37. package/dist/custom-theme.4680282a.js +2 -0
  38. package/dist/custom-theme.4680282a.js.map +1 -0
  39. package/dist/drag-handle.136830d3.js +2 -0
  40. package/dist/drag-handle.136830d3.js.map +1 -0
  41. package/dist/drag-handle.63bdb345.css +2 -0
  42. package/dist/drag-handle.63bdb345.css.map +1 -0
  43. package/dist/index.13289f53.js +28 -0
  44. package/dist/index.13289f53.js.map +1 -0
  45. package/dist/index.a41fafce.css +2 -0
  46. package/dist/index.a41fafce.css.map +1 -0
  47. package/dist/index.html +1 -0
  48. package/dist/index.runtime.3c39d71d.js +2 -0
  49. package/dist/index.runtime.3c39d71d.js.map +1 -0
  50. package/dist/refractor.2c1fd9a1.js +2 -0
  51. package/dist/refractor.2c1fd9a1.js.map +1 -0
  52. package/index.html +11 -0
  53. package/jest.config.js +16 -0
  54. package/package.json +64 -0
  55. package/src/APIError.test.ts +72 -0
  56. package/src/APIError.tsx +29 -0
  57. package/src/AppRoutes.tsx +56 -0
  58. package/src/hack-feature-flags.ts +6 -0
  59. package/src/main.tsx +50 -0
  60. package/src/test/stubCssModule.js +1 -0
  61. package/src/ui/App.module.css +122 -0
  62. package/src/ui/App.module.css.d.ts +8 -0
  63. package/src/ui/AppLayout/AppLayout.tsx +26 -0
  64. package/src/ui/AppLayout/SidebarNavigation/LinkItem.tsx +26 -0
  65. package/src/ui/AppLayout/SidebarNavigation/SidebarNavigation.tsx +45 -0
  66. package/src/ui/AppLayout/TopNavigation/Logo.module.css +12 -0
  67. package/src/ui/AppLayout/TopNavigation/Logo.module.css.d.ts +4 -0
  68. package/src/ui/AppLayout/TopNavigation/Logo.tsx +11 -0
  69. package/src/ui/AppLayout/TopNavigation/TopNavigation.module.css +14 -0
  70. package/src/ui/AppLayout/TopNavigation/TopNavigation.module.css.d.ts +3 -0
  71. package/src/ui/AppLayout/TopNavigation/TopNavigation.tsx +45 -0
  72. package/src/ui/AppLayout/TopNavigation/badge-light.png +0 -0
  73. package/src/ui/AppLayout/TopNavigation/logo-light.png +0 -0
  74. package/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.module.css +9 -0
  75. package/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.module.css.d.ts +3 -0
  76. package/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.test.tsx +15 -0
  77. package/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.tsx +14 -0
  78. package/src/ui/app/StatsPage.tsx +77 -0
  79. package/src/ui/app/cache/CacheKeysIndexPage.tsx +13 -0
  80. package/src/ui/app/cache/CacheKeysPage.module.css +11 -0
  81. package/src/ui/app/cache/CacheKeysPage.module.css.d.ts +4 -0
  82. package/src/ui/app/cache/CacheKeysPage.tsx +23 -0
  83. package/src/ui/app/cache/[key]/CacheValuePage.tsx +40 -0
  84. package/src/ui/app/cache/ui/CacheKeyList.module.css +40 -0
  85. package/src/ui/app/cache/ui/CacheKeyList.module.css.d.ts +7 -0
  86. package/src/ui/app/cache/ui/CacheKeyList.tsx +187 -0
  87. package/src/ui/app/cache-invalidation/CacheInvalidationPage.tsx +22 -0
  88. package/src/ui/app/cache-invalidation/[fileId]/CacheInvalidationFilePage.tsx +22 -0
  89. package/src/ui/app/cache-invalidation/ui/CacheFileList.module.css +40 -0
  90. package/src/ui/app/cache-invalidation/ui/CacheFileList.module.css.d.ts +7 -0
  91. package/src/ui/app/cache-invalidation/ui/CacheFileList.tsx +185 -0
  92. package/src/ui/app/treemap/BottomPanelResizeState.test.ts +25 -0
  93. package/src/ui/app/treemap/BottomPanelResizeState.tsx +48 -0
  94. package/src/ui/app/treemap/FoamTreemapPage.module.css +24 -0
  95. package/src/ui/app/treemap/FoamTreemapPage.module.css.d.ts +6 -0
  96. package/src/ui/app/treemap/FoamTreemapPage.tsx +47 -0
  97. package/src/ui/app/treemap/controllers/RelatedBundlesController.tsx +41 -0
  98. package/src/ui/app/treemap/controllers/UrlFocusController.tsx +33 -0
  99. package/src/ui/app/treemap/ui/BottomPanel/BottomPanel.module.css +24 -0
  100. package/src/ui/app/treemap/ui/BottomPanel/BottomPanel.module.css.d.ts +5 -0
  101. package/src/ui/app/treemap/ui/BottomPanel/BottomPanel.tsx +24 -0
  102. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.module.css +13 -0
  103. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.module.css.d.ts +5 -0
  104. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.tsx +53 -0
  105. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/AssetTable.tsx +135 -0
  106. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.module.css +7 -0
  107. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.module.css.d.ts +3 -0
  108. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.tsx +123 -0
  109. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableModel.tsx +18 -0
  110. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.module.css +20 -0
  111. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.module.css.d.ts +6 -0
  112. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.tsx +79 -0
  113. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/getFileURL.test.ts +19 -0
  114. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/getFileURL.ts +24 -0
  115. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.module.css +20 -0
  116. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.module.css.d.ts +5 -0
  117. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.tsx +42 -0
  118. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.module.css +29 -0
  119. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.module.css.d.ts +6 -0
  120. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.tsx +107 -0
  121. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.module.css +7 -0
  122. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.module.css.d.ts +3 -0
  123. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.tsx +20 -0
  124. package/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/SourceCodeURL.tsx +5 -0
  125. package/src/ui/app/treemap/ui/BundleGraphRenderer.module.css +13 -0
  126. package/src/ui/app/treemap/ui/BundleGraphRenderer.module.css.d.ts +4 -0
  127. package/src/ui/app/treemap/ui/BundleGraphRenderer.tsx +95 -0
  128. package/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.module.css +6 -0
  129. package/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.module.css.d.ts +3 -0
  130. package/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.tsx +49 -0
  131. package/src/ui/app/treemap/ui/SigmaGraph.module.css +5 -0
  132. package/src/ui/app/treemap/ui/SigmaGraph.module.css.d.ts +3 -0
  133. package/src/ui/app/treemap/ui/SigmaGraph.tsx +80 -0
  134. package/src/ui/app/treemap/ui/Treemap.tsx +14 -0
  135. package/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.module.css +32 -0
  136. package/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.module.css.d.ts +5 -0
  137. package/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.tsx +24 -0
  138. package/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.module.css +14 -0
  139. package/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.module.css.d.ts +4 -0
  140. package/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.tsx +271 -0
  141. package/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.module.css +15 -0
  142. package/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.module.css.d.ts +4 -0
  143. package/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.tsx +111 -0
  144. package/src/ui/app/treemap/ui/TreemapRenderer/controllers/useStableCallback.test.ts +27 -0
  145. package/src/ui/app/treemap/ui/TreemapRenderer/controllers/useStableCallback.ts +21 -0
  146. package/src/ui/app/treemap/ui/TreemapRenderer/useMouseMoveController.ts +20 -0
  147. package/src/ui/globals.css +26 -0
  148. package/src/ui/globals.css.d.ts +1 -0
  149. package/src/ui/globals.d.ts +9 -0
  150. package/src/ui/model/ViewModel.test.ts +31 -0
  151. package/src/ui/model/ViewModel.ts +62 -0
  152. package/src/ui/not-found/NotFoundPage.module.css +7 -0
  153. package/src/ui/not-found/NotFoundPage.module.css.d.ts +3 -0
  154. package/src/ui/not-found/NotFoundPage.tsx +9 -0
  155. package/src/ui/types/Graph.tsx +12 -0
  156. package/src/ui/util/ErrorBoundary.module.css +3 -0
  157. package/src/ui/util/ErrorBoundary.module.css.d.ts +3 -0
  158. package/src/ui/util/ErrorBoundary.test.tsx +65 -0
  159. package/src/ui/util/ErrorBoundary.tsx +75 -0
  160. package/src/ui/util/colorPalette.tsx +122 -0
  161. package/src/ui/util/formatBytes.test.ts +13 -0
  162. package/src/ui/util/formatBytes.tsx +9 -0
  163. package/src/ui/util/getRandomDarkerColor.tsx +31 -0
  164. 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,6 @@
1
+ export const __esModule: true;
2
+ export const collapsibleTableRow: string;
3
+ export const collapsibleTableRowPath: string;
4
+ export const collapsibleTableRowPathButton: string;
5
+ export const collapsibleTableRowPathPlaceholder: string;
6
+
@@ -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,5 @@
1
+ export const __esModule: true;
2
+ export const focusedGroupInfoAdvancedSettings: string;
3
+ export const focusedGroupInfoAssetTable: string;
4
+ export const focusedGroupInfoBundleGraph: string;
5
+
@@ -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,6 @@
1
+ export const __esModule: true;
2
+ export const focusedGroupInfoInner: string;
3
+ export const focusedGroupInfoInnerAdvancedSettings: string;
4
+ export const focusedGroupInfoInnerAssetTable: string;
5
+ export const focusedGroupInfoInnerGraphContainer: string;
6
+
@@ -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,7 @@
1
+ .graphContainer {
2
+ height: 100%;
3
+ width: 100%;
4
+ border: 1px solid var(--ds-border);
5
+ border-radius: 8px;
6
+ background-color: var(--ds-surface-sunken);
7
+ }
@@ -0,0 +1,3 @@
1
+ export const __esModule: true;
2
+ export const graphContainer: string;
3
+
@@ -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,5 @@
1
+ export interface SourceCodeURL {
2
+ owner: string;
3
+ repo: string;
4
+ type: 'github' | 'bitbucket';
5
+ }
@@ -0,0 +1,13 @@
1
+ .expander {
2
+ height: 100%;
3
+ width: 100%;
4
+ flex: 1;
5
+ }
6
+
7
+ .loadingIndicator {
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ height: 100%;
12
+ width: 100%;
13
+ }
@@ -0,0 +1,4 @@
1
+ export const __esModule: true;
2
+ export const expander: string;
3
+ export const loadingIndicator: string;
4
+
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ .focusBreadcrumbs {
2
+ padding: 4px;
3
+ display: flex;
4
+ flex-direction: row;
5
+ gap: 4px;
6
+ }
@@ -0,0 +1,3 @@
1
+ export const __esModule: true;
2
+ export const focusBreadcrumbs: string;
3
+