@atlaspack/inspector 0.0.19-canary.3998

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 (235) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +126 -0
  3. package/bin/atlaspack-inspector.js +2 -0
  4. package/lib/backend/cli.js +4 -0
  5. package/lib/backend/config/logger.js +16 -0
  6. package/lib/backend/config/middleware/cacheDataMiddleware.js +60 -0
  7. package/lib/backend/config/middleware/errorHandlingMiddleware.js +17 -0
  8. package/lib/backend/config/middleware/errorHandlingMiddleware.test.js +44 -0
  9. package/lib/backend/config/middleware/loggingMiddleware.js +22 -0
  10. package/lib/backend/controllers/BundleGraphController.js +42 -0
  11. package/lib/backend/controllers/CacheDataController.js +143 -0
  12. package/lib/backend/controllers/FrontendAssetsController.js +61 -0
  13. package/lib/backend/controllers/TreeMapController.js +108 -0
  14. package/lib/backend/controllers/mcp/InspectorMCP.js +231 -0
  15. package/lib/backend/controllers/mcp/InspectorMCPController.js +16 -0
  16. package/lib/backend/errors/HTTPError.js +16 -0
  17. package/lib/backend/errors/HTTPError.test.js +21 -0
  18. package/lib/backend/index.js +177 -0
  19. package/lib/backend/services/AnalyticsService.js +86 -0
  20. package/lib/backend/services/LazyValue.js +33 -0
  21. package/lib/backend/services/LazyValue.test.js +12 -0
  22. package/lib/backend/services/buildJsonGraph.js +86 -0
  23. package/lib/backend/services/buildTreemap.js +140 -0
  24. package/lib/backend/services/buildTreemapBundle.test.js +298 -0
  25. package/lib/backend/services/findSourceCodeUrl.js +107 -0
  26. package/lib/backend/services/findSourceCodeUrl.test.js +216 -0
  27. package/lib/backend/services/getCacheStats.js +36 -0
  28. package/lib/backend/services/getCacheStats.test.js +162 -0
  29. package/lib/backend/services/getDisplayName.js +18 -0
  30. package/lib/backend/services/getDisplayName.test.js +71 -0
  31. package/lib/backend/services/loadCacheData.js +209 -0
  32. package/lib/backend/services/loadCacheData.test.js +79 -0
  33. package/lib/backend/testing/TemporaryDirectory.js +46 -0
  34. package/lib/frontend/node_modules/@atlaspack/packager-js/lib/dev-prelude.js +145 -0
  35. package/lib/frontend/node_modules/@atlaspack/packager-js/src/dev-prelude.js +145 -0
  36. package/package.json +75 -0
  37. package/screenshots/bottom-up.png +0 -0
  38. package/screenshots/cache-inspector.png +0 -0
  39. package/screenshots/treemap.png +0 -0
  40. package/src/backend/README.md +14 -0
  41. package/src/backend/cli.ts +3 -0
  42. package/src/backend/config/logger.ts +14 -0
  43. package/src/backend/config/middleware/cacheDataMiddleware.ts +94 -0
  44. package/src/backend/config/middleware/errorHandlingMiddleware.test.ts +52 -0
  45. package/src/backend/config/middleware/errorHandlingMiddleware.ts +20 -0
  46. package/src/backend/config/middleware/loggingMiddleware.ts +24 -0
  47. package/src/backend/controllers/BundleGraphController.ts +73 -0
  48. package/src/backend/controllers/CacheDataController.ts +187 -0
  49. package/src/backend/controllers/FrontendAssetsController.ts +28 -0
  50. package/src/backend/controllers/TreeMapController.ts +143 -0
  51. package/src/backend/controllers/mcp/InspectorMCP.ts +311 -0
  52. package/src/backend/controllers/mcp/InspectorMCPController.ts +17 -0
  53. package/src/backend/errors/HTTPError.test.ts +19 -0
  54. package/src/backend/errors/HTTPError.ts +14 -0
  55. package/src/backend/globals.d.ts +9 -0
  56. package/src/backend/index.ts +271 -0
  57. package/src/backend/services/AnalyticsService.ts +118 -0
  58. package/src/backend/services/LazyValue.test.ts +13 -0
  59. package/src/backend/services/LazyValue.ts +29 -0
  60. package/src/backend/services/buildJsonGraph.ts +124 -0
  61. package/src/backend/services/buildTreemap.ts +273 -0
  62. package/src/backend/services/buildTreemapBundle.test.ts +348 -0
  63. package/src/backend/services/findSourceCodeUrl.test.ts +228 -0
  64. package/src/backend/services/findSourceCodeUrl.ts +146 -0
  65. package/src/backend/services/getCacheStats.test.ts +169 -0
  66. package/src/backend/services/getCacheStats.ts +46 -0
  67. package/src/backend/services/getDisplayName.test.ts +84 -0
  68. package/src/backend/services/getDisplayName.ts +20 -0
  69. package/src/backend/services/loadCacheData.test.ts +101 -0
  70. package/src/backend/services/loadCacheData.ts +294 -0
  71. package/src/backend/testing/TemporaryDirectory.ts +50 -0
  72. package/src/frontend/.atlaspackrc +4 -0
  73. package/src/frontend/.eslintrc.json +19 -0
  74. package/src/frontend/dist/atlassian-dark-brand-refresh.91b786da.js +2 -0
  75. package/src/frontend/dist/atlassian-dark-brand-refresh.91b786da.js.map +1 -0
  76. package/src/frontend/dist/atlassian-dark-future.59ebadca.js +2 -0
  77. package/src/frontend/dist/atlassian-dark-future.59ebadca.js.map +1 -0
  78. package/src/frontend/dist/atlassian-dark-increased-contrast.ff6775f2.js +2 -0
  79. package/src/frontend/dist/atlassian-dark-increased-contrast.ff6775f2.js.map +1 -0
  80. package/src/frontend/dist/atlassian-dark.ad679134.js +2 -0
  81. package/src/frontend/dist/atlassian-dark.ad679134.js.map +1 -0
  82. package/src/frontend/dist/atlassian-legacy-dark.8aa27f7f.js +2 -0
  83. package/src/frontend/dist/atlassian-legacy-dark.8aa27f7f.js.map +1 -0
  84. package/src/frontend/dist/atlassian-legacy-light.2eb372ce.js +2 -0
  85. package/src/frontend/dist/atlassian-legacy-light.2eb372ce.js.map +1 -0
  86. package/src/frontend/dist/atlassian-light-brand-refresh.fadcab0a.js +2 -0
  87. package/src/frontend/dist/atlassian-light-brand-refresh.fadcab0a.js.map +1 -0
  88. package/src/frontend/dist/atlassian-light-future.612afe8a.js +2 -0
  89. package/src/frontend/dist/atlassian-light-future.612afe8a.js.map +1 -0
  90. package/src/frontend/dist/atlassian-light-increased-contrast.7161cd79.js +2 -0
  91. package/src/frontend/dist/atlassian-light-increased-contrast.7161cd79.js.map +1 -0
  92. package/src/frontend/dist/atlassian-light.bc343d4c.js +2 -0
  93. package/src/frontend/dist/atlassian-light.bc343d4c.js.map +1 -0
  94. package/src/frontend/dist/atlassian-shape.b92d69c0.js +2 -0
  95. package/src/frontend/dist/atlassian-shape.b92d69c0.js.map +1 -0
  96. package/src/frontend/dist/atlassian-spacing.60ddd8e7.js +2 -0
  97. package/src/frontend/dist/atlassian-spacing.60ddd8e7.js.map +1 -0
  98. package/src/frontend/dist/atlassian-typography-adg3.f88947f6.js +2 -0
  99. package/src/frontend/dist/atlassian-typography-adg3.f88947f6.js.map +1 -0
  100. package/src/frontend/dist/atlassian-typography-modernized.42016c51.js +2 -0
  101. package/src/frontend/dist/atlassian-typography-modernized.42016c51.js.map +1 -0
  102. package/src/frontend/dist/atlassian-typography-refreshed.ec0d111b.js +2 -0
  103. package/src/frontend/dist/atlassian-typography-refreshed.ec0d111b.js.map +1 -0
  104. package/src/frontend/dist/atlassian-typography.66d7e8f4.js +2 -0
  105. package/src/frontend/dist/atlassian-typography.66d7e8f4.js.map +1 -0
  106. package/src/frontend/dist/badge-light.7e55986a.png +0 -0
  107. package/src/frontend/dist/custom-theme.4680282a.js +2 -0
  108. package/src/frontend/dist/custom-theme.4680282a.js.map +1 -0
  109. package/src/frontend/dist/drag-handle.136830d3.js +2 -0
  110. package/src/frontend/dist/drag-handle.136830d3.js.map +1 -0
  111. package/src/frontend/dist/drag-handle.63bdb345.css +2 -0
  112. package/src/frontend/dist/drag-handle.63bdb345.css.map +1 -0
  113. package/src/frontend/dist/index.a41fafce.css +2 -0
  114. package/src/frontend/dist/index.a41fafce.css.map +1 -0
  115. package/src/frontend/dist/index.a4ce2b12.js +28 -0
  116. package/src/frontend/dist/index.a4ce2b12.js.map +1 -0
  117. package/src/frontend/dist/index.html +1 -0
  118. package/src/frontend/dist/index.runtime.a729d997.js +2 -0
  119. package/src/frontend/dist/index.runtime.a729d997.js.map +1 -0
  120. package/src/frontend/dist/refractor.2c1fd9a1.js +2 -0
  121. package/src/frontend/dist/refractor.2c1fd9a1.js.map +1 -0
  122. package/src/frontend/index.html +11 -0
  123. package/src/frontend/jest.config.js +16 -0
  124. package/src/frontend/package.json +64 -0
  125. package/src/frontend/src/APIError.test.ts +72 -0
  126. package/src/frontend/src/APIError.tsx +29 -0
  127. package/src/frontend/src/AppRoutes.tsx +56 -0
  128. package/src/frontend/src/hack-feature-flags.ts +6 -0
  129. package/src/frontend/src/main.tsx +50 -0
  130. package/src/frontend/src/test/stubCssModule.js +1 -0
  131. package/src/frontend/src/ui/App.module.css +122 -0
  132. package/src/frontend/src/ui/App.module.css.d.ts +8 -0
  133. package/src/frontend/src/ui/AppLayout/AppLayout.tsx +26 -0
  134. package/src/frontend/src/ui/AppLayout/SidebarNavigation/LinkItem.tsx +26 -0
  135. package/src/frontend/src/ui/AppLayout/SidebarNavigation/SidebarNavigation.tsx +45 -0
  136. package/src/frontend/src/ui/AppLayout/TopNavigation/Logo.module.css +12 -0
  137. package/src/frontend/src/ui/AppLayout/TopNavigation/Logo.module.css.d.ts +4 -0
  138. package/src/frontend/src/ui/AppLayout/TopNavigation/Logo.tsx +11 -0
  139. package/src/frontend/src/ui/AppLayout/TopNavigation/TopNavigation.module.css +14 -0
  140. package/src/frontend/src/ui/AppLayout/TopNavigation/TopNavigation.module.css.d.ts +3 -0
  141. package/src/frontend/src/ui/AppLayout/TopNavigation/TopNavigation.tsx +45 -0
  142. package/src/frontend/src/ui/AppLayout/TopNavigation/badge-light.png +0 -0
  143. package/src/frontend/src/ui/AppLayout/TopNavigation/logo-light.png +0 -0
  144. package/src/frontend/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.module.css +9 -0
  145. package/src/frontend/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.module.css.d.ts +3 -0
  146. package/src/frontend/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.test.tsx +15 -0
  147. package/src/frontend/src/ui/DefaultLoadingIndicator/DefaultLoadingIndicator.tsx +14 -0
  148. package/src/frontend/src/ui/app/StatsPage.tsx +77 -0
  149. package/src/frontend/src/ui/app/cache/CacheKeysIndexPage.tsx +13 -0
  150. package/src/frontend/src/ui/app/cache/CacheKeysPage.module.css +11 -0
  151. package/src/frontend/src/ui/app/cache/CacheKeysPage.module.css.d.ts +4 -0
  152. package/src/frontend/src/ui/app/cache/CacheKeysPage.tsx +23 -0
  153. package/src/frontend/src/ui/app/cache/[key]/CacheValuePage.tsx +40 -0
  154. package/src/frontend/src/ui/app/cache/ui/CacheKeyList.module.css +40 -0
  155. package/src/frontend/src/ui/app/cache/ui/CacheKeyList.module.css.d.ts +7 -0
  156. package/src/frontend/src/ui/app/cache/ui/CacheKeyList.tsx +187 -0
  157. package/src/frontend/src/ui/app/cache-invalidation/CacheInvalidationPage.tsx +22 -0
  158. package/src/frontend/src/ui/app/cache-invalidation/[fileId]/CacheInvalidationFilePage.tsx +22 -0
  159. package/src/frontend/src/ui/app/cache-invalidation/ui/CacheFileList.module.css +40 -0
  160. package/src/frontend/src/ui/app/cache-invalidation/ui/CacheFileList.module.css.d.ts +7 -0
  161. package/src/frontend/src/ui/app/cache-invalidation/ui/CacheFileList.tsx +185 -0
  162. package/src/frontend/src/ui/app/treemap/BottomPanelResizeState.test.ts +25 -0
  163. package/src/frontend/src/ui/app/treemap/BottomPanelResizeState.tsx +48 -0
  164. package/src/frontend/src/ui/app/treemap/FoamTreemapPage.module.css +24 -0
  165. package/src/frontend/src/ui/app/treemap/FoamTreemapPage.module.css.d.ts +6 -0
  166. package/src/frontend/src/ui/app/treemap/FoamTreemapPage.tsx +47 -0
  167. package/src/frontend/src/ui/app/treemap/controllers/RelatedBundlesController.tsx +41 -0
  168. package/src/frontend/src/ui/app/treemap/controllers/UrlFocusController.tsx +33 -0
  169. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/BottomPanel.module.css +24 -0
  170. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/BottomPanel.module.css.d.ts +5 -0
  171. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/BottomPanel.tsx +24 -0
  172. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.module.css +13 -0
  173. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.module.css.d.ts +5 -0
  174. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AdvancedSettings.tsx +53 -0
  175. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/AssetTable.tsx +135 -0
  176. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.module.css +7 -0
  177. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.module.css.d.ts +3 -0
  178. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTable.tsx +123 -0
  179. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableModel.tsx +18 -0
  180. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.module.css +20 -0
  181. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.module.css.d.ts +6 -0
  182. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/CollapsibleTable/CollapsibleTableRow.tsx +79 -0
  183. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/getFileURL.test.ts +19 -0
  184. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/AssetTable/getFileURL.ts +24 -0
  185. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.module.css +20 -0
  186. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.module.css.d.ts +5 -0
  187. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfo.tsx +42 -0
  188. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.module.css +29 -0
  189. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.module.css.d.ts +6 -0
  190. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/FocusedGroupInfoInner.tsx +107 -0
  191. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.module.css +7 -0
  192. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.module.css.d.ts +3 -0
  193. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/GraphContainer.tsx +20 -0
  194. package/src/frontend/src/ui/app/treemap/ui/BottomPanel/FocusedGroupInfo/SourceCodeURL.tsx +5 -0
  195. package/src/frontend/src/ui/app/treemap/ui/BundleGraphRenderer.module.css +13 -0
  196. package/src/frontend/src/ui/app/treemap/ui/BundleGraphRenderer.module.css.d.ts +4 -0
  197. package/src/frontend/src/ui/app/treemap/ui/BundleGraphRenderer.tsx +95 -0
  198. package/src/frontend/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.module.css +6 -0
  199. package/src/frontend/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.module.css.d.ts +3 -0
  200. package/src/frontend/src/ui/app/treemap/ui/FocusBreadcrumbs/FocusBreadcrumbs.tsx +49 -0
  201. package/src/frontend/src/ui/app/treemap/ui/SigmaGraph.module.css +5 -0
  202. package/src/frontend/src/ui/app/treemap/ui/SigmaGraph.module.css.d.ts +3 -0
  203. package/src/frontend/src/ui/app/treemap/ui/SigmaGraph.tsx +80 -0
  204. package/src/frontend/src/ui/app/treemap/ui/Treemap.tsx +14 -0
  205. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.module.css +32 -0
  206. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.module.css.d.ts +5 -0
  207. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/ImpactScore.tsx +24 -0
  208. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.module.css +14 -0
  209. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.module.css.d.ts +4 -0
  210. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapRenderer.tsx +271 -0
  211. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.module.css +15 -0
  212. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.module.css.d.ts +4 -0
  213. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/TreemapTooltip.tsx +111 -0
  214. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/controllers/useStableCallback.test.ts +27 -0
  215. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/controllers/useStableCallback.ts +21 -0
  216. package/src/frontend/src/ui/app/treemap/ui/TreemapRenderer/useMouseMoveController.ts +20 -0
  217. package/src/frontend/src/ui/globals.css +26 -0
  218. package/src/frontend/src/ui/globals.css.d.ts +1 -0
  219. package/src/frontend/src/ui/globals.d.ts +9 -0
  220. package/src/frontend/src/ui/model/ViewModel.test.ts +31 -0
  221. package/src/frontend/src/ui/model/ViewModel.ts +62 -0
  222. package/src/frontend/src/ui/not-found/NotFoundPage.module.css +7 -0
  223. package/src/frontend/src/ui/not-found/NotFoundPage.module.css.d.ts +3 -0
  224. package/src/frontend/src/ui/not-found/NotFoundPage.tsx +9 -0
  225. package/src/frontend/src/ui/types/Graph.tsx +12 -0
  226. package/src/frontend/src/ui/util/ErrorBoundary.module.css +3 -0
  227. package/src/frontend/src/ui/util/ErrorBoundary.module.css.d.ts +3 -0
  228. package/src/frontend/src/ui/util/ErrorBoundary.test.tsx +65 -0
  229. package/src/frontend/src/ui/util/ErrorBoundary.tsx +75 -0
  230. package/src/frontend/src/ui/util/colorPalette.tsx +122 -0
  231. package/src/frontend/src/ui/util/formatBytes.test.ts +13 -0
  232. package/src/frontend/src/ui/util/formatBytes.tsx +9 -0
  233. package/src/frontend/src/ui/util/getRandomDarkerColor.tsx +31 -0
  234. package/src/frontend/tsconfig.json +12 -0
  235. package/src/frontend/yarn.lock +0 -0
@@ -0,0 +1,3 @@
1
+ export const __esModule: true;
2
+ export const collapsibleTable: string;
3
+
@@ -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
+ }