From 88c8d7b626bf12114c47aa4373c03de4cf8bac31 Mon Sep 17 00:00:00 2001 From: bryanhuhta Date: Tue, 11 Mar 2025 17:08:19 -0500 Subject: [PATCH 1/4] yarn format:fix --- buf.gen.yaml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/buf.gen.yaml b/buf.gen.yaml index 15e71d5a..74863384 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -9,16 +9,15 @@ plugins: out: src/shared/pyroscope-api opt: target=ts inputs: - # Build a subset of the Pyroscope protobuf API + # Build a subset of the Pyroscope protobuf API - git_repo: https://github.com/grafana/pyroscope.git # Update this to any commit, which is merged in Pyroscope main ref: weekly-f105-886b418af subdir: api paths: - - adhocprofiles/ - - querier/ - - settings/ - - types/ - - vcs/ - - google/ - + - adhocprofiles/ + - querier/ + - settings/ + - types/ + - vcs/ + - google/ From 84528f2a9f0d8893a05b164e053c55c4a2c0dc02 Mon Sep 17 00:00:00 2001 From: bryanhuhta Date: Tue, 11 Mar 2025 17:10:47 -0500 Subject: [PATCH 2/4] Pass entire annotated file to Optimize Code prompt --- .../CodeContainer/CodeContainer.tsx | 4 +- .../__tests__/buildLineProfiles.spec.ts | 878 ++++++++++++------ .../CodeContainer/domain/buildLineProfiles.ts | 39 +- .../CodeContainer/domain/useCodeContainer.ts | 25 +- 4 files changed, 627 insertions(+), 319 deletions(-) diff --git a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/CodeContainer.tsx b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/CodeContainer.tsx index f3539772..07ab5880 100644 --- a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/CodeContainer.tsx +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/CodeContainer.tsx @@ -22,7 +22,7 @@ export function CodeContainer({ dataSourceUid, functionDetails }: CodeContainerP return ( <> ) : null} diff --git a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/buildLineProfiles.spec.ts b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/buildLineProfiles.spec.ts index 6f14a465..1d9b917d 100644 --- a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/buildLineProfiles.spec.ts +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/buildLineProfiles.spec.ts @@ -1,16 +1,16 @@ -import { buildLineProfiles, buildPlaceholderLineProfiles } from '../buildLineProfiles'; +import { annotateLines, annotatePlaceholderLineProfiles } from '../buildLineProfiles'; describe('buildPlaceholderLineProfiles(callSitesMap)', () => { describe('if callSitesMap is empty', () => { - it('returns an empty array', () => { - const result = buildPlaceholderLineProfiles(new Map()); - expect(result).toEqual([]); + it('returns an empty snippet and empty allLines array', () => { + const result = annotatePlaceholderLineProfiles(new Map()); + expect(result).toEqual([[], []]); }); }); describe('if callSitesMap is not empty', () => { - it('returns an array containing samples/line info, sorted by line number and padded with extra lines', () => { - const result = buildPlaceholderLineProfiles( + it('returns a snippet of annotated code lines', () => { + const [snippet, allLines] = annotatePlaceholderLineProfiles( new Map([ [12, { line: 12, flat: 0, cum: 40000000 }], [15, { line: 15, flat: 0, cum: 710000000 }], @@ -18,170 +18,170 @@ describe('buildPlaceholderLineProfiles(callSitesMap)', () => { ]) ); - expect(result).toMatchInlineSnapshot(` - [ - { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 6, - }, - { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 7, - }, - { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 8, - }, - { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 9, - }, - { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 10, - }, - { - "cum": 30000000, - "flat": 0, - "line": undefined, - "number": 11, - }, + expect(snippet).toEqual([ + { + cum: 0, + flat: 0, + line: undefined, + number: 6, + }, + { + cum: 0, + flat: 0, + line: undefined, + number: 7, + }, + { + cum: 0, + flat: 0, + line: undefined, + number: 8, + }, + { + cum: 0, + flat: 0, + line: undefined, + number: 9, + }, + { + cum: 0, + flat: 0, + line: undefined, + number: 10, + }, + { + cum: 30000000, + flat: 0, + line: undefined, + number: 11, + }, + { + cum: 40000000, + flat: 0, + line: undefined, + number: 12, + }, + { + cum: 0, + flat: 0, + line: undefined, + number: 13, + }, + { + cum: 0, + flat: 0, + line: undefined, + number: 14, + }, + { + cum: 710000000, + flat: 0, + line: undefined, + number: 15, + }, + { + cum: 0, + flat: 0, + line: undefined, + number: 16, + }, + { + cum: 0, + flat: 0, + line: undefined, + number: 17, + }, + { + cum: 0, + flat: 0, + line: undefined, + number: 18, + }, + { + cum: 0, + flat: 0, + line: undefined, + number: 19, + }, + { + cum: 0, + flat: 0, + line: undefined, + number: 20, + }, + ]); + + expect(allLines).toEqual([]); + }); + + describe('when the lines contained in callSitesMap are smaller than the number of padded lines', () => { + it('returns an array cropped properly', () => { + const [snippet, allLines] = annotatePlaceholderLineProfiles( + new Map([ + [2, { line: 2, flat: 0, cum: 40000000 }], + [4, { line: 4, flat: 0, cum: 710000000 }], + [1, { line: 1, flat: 0, cum: 30000000 }], + ]) + ); + + expect(snippet).toEqual([ { - "cum": 40000000, - "flat": 0, - "line": undefined, - "number": 12, + cum: 30000000, + flat: 0, + line: undefined, + number: 1, }, { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 13, + cum: 40000000, + flat: 0, + line: undefined, + number: 2, }, { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 14, + cum: 0, + flat: 0, + line: undefined, + number: 3, }, { - "cum": 710000000, - "flat": 0, - "line": undefined, - "number": 15, + cum: 710000000, + flat: 0, + line: undefined, + number: 4, }, { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 16, + cum: 0, + flat: 0, + line: undefined, + number: 5, }, { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 17, + cum: 0, + flat: 0, + line: undefined, + number: 6, }, { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 18, + cum: 0, + flat: 0, + line: undefined, + number: 7, }, { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 19, + cum: 0, + flat: 0, + line: undefined, + number: 8, }, { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 20, + cum: 0, + flat: 0, + line: undefined, + number: 9, }, - ] - `); - }); - - describe('when the lines contained in callSitesMap are smaller than the number of padded lines', () => { - it('returns an array cropped properly', () => { - const result = buildPlaceholderLineProfiles( - new Map([ - [2, { line: 2, flat: 0, cum: 40000000 }], - [4, { line: 4, flat: 0, cum: 710000000 }], - [1, { line: 1, flat: 0, cum: 30000000 }], - ]) - ); + ]); - expect(result).toMatchInlineSnapshot(` - [ - { - "cum": 30000000, - "flat": 0, - "line": undefined, - "number": 1, - }, - { - "cum": 40000000, - "flat": 0, - "line": undefined, - "number": 2, - }, - { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 3, - }, - { - "cum": 710000000, - "flat": 0, - "line": undefined, - "number": 4, - }, - { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 5, - }, - { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 6, - }, - { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 7, - }, - { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 8, - }, - { - "cum": 0, - "flat": 0, - "line": undefined, - "number": 9, - }, - ] - `); + expect(allLines).toEqual([]); }); }); }); @@ -189,9 +189,17 @@ describe('buildPlaceholderLineProfiles(callSitesMap)', () => { describe('buildLineProfiles(fileContent, callSitesMap', () => { describe('if callSitesMap is empty', () => { - it('returns an empty array', () => { - const result = buildLineProfiles('// this file is empty', new Map()); - expect(result).toEqual([]); + it('returns an empty snippet array', () => { + const [snippet, allLines] = annotateLines('// this file is empty', new Map()); + expect(snippet).toEqual([]); + expect(allLines).toEqual([ + { + cum: 0, + flat: 0, + line: '// this file is empty', + number: 1, + }, + ]); }); }); @@ -222,7 +230,7 @@ describe('buildPlaceholderLineProfiles(callSitesMap)', () => { });`; it('returns an array containing samples/line info, sorted by line number and padded with extra lines', () => { - const result = buildLineProfiles( + const [snippet, allLines] = annotateLines( fileContent, new Map([ [12, { line: 12, flat: 0, cum: 40000000 }], @@ -231,171 +239,461 @@ describe('buildPlaceholderLineProfiles(callSitesMap)', () => { ]) ); - expect(result).toMatchInlineSnapshot(` - [ + expect(snippet).toEqual([ + { + cum: 0, + flat: 0, + line: ' const result = buildPlaceholderLineProfiles(new Map());', + number: 6, + }, + { + cum: 0, + flat: 0, + line: ' expect(result).toEqual([]);', + number: 7, + }, + { + cum: 0, + flat: 0, + line: ' });', + number: 8, + }, + { + cum: 0, + flat: 0, + line: ' });', + number: 9, + }, + { + cum: 0, + flat: 0, + line: '', + number: 10, + }, + { + cum: 30000000, + flat: 0, + line: " describe('if callSitesMap is not empty', () => {", + number: 11, + }, + { + cum: 40000000, + flat: 0, + line: " it('returns an array containing samples/line info, sorted by line number and padded with extra lines', () => {", + number: 12, + }, + { + cum: 0, + flat: 0, + line: ' const result = buildPlaceholderLineProfiles(', + number: 13, + }, + { + cum: 0, + flat: 0, + line: ' new Map([', + number: 14, + }, + { + cum: 710000000, + flat: 0, + line: ' [12, { line: 12, flat: 0, cum: 40000000 }],', + number: 15, + }, + { + cum: 0, + flat: 0, + line: ' [15, { line: 15, flat: 0, cum: 710000000 }],', + number: 16, + }, + { + cum: 0, + flat: 0, + line: ' [11, { line: 11, flat: 0, cum: 30000000 }],', + number: 17, + }, + { + cum: 0, + flat: 0, + line: ' ])', + number: 18, + }, + { + cum: 0, + flat: 0, + line: ' );', + number: 19, + }, + { + cum: 0, + flat: 0, + line: '', + number: 20, + }, + ]); + + expect(allLines).toEqual([ + { + cum: 0, + flat: 0, + line: "import { buildLineProfiles, buildPlaceholderLineProfiles } from '../buildLineProfiles';", + number: 1, + }, + { + cum: 0, + flat: 0, + line: '', + number: 2, + }, + { + cum: 0, + flat: 0, + line: "describe('buildPlaceholderLineProfiles(callSitesMap)', () => {", + number: 3, + }, + { + cum: 0, + flat: 0, + line: " describe('if callSitesMap is empty', () => {", + number: 4, + }, + { + cum: 0, + flat: 0, + line: " it('returns an empty array', () => {", + number: 5, + }, + { + cum: 0, + flat: 0, + line: ' const result = buildPlaceholderLineProfiles(new Map());', + number: 6, + }, + { + cum: 0, + flat: 0, + line: ' expect(result).toEqual([]);', + number: 7, + }, + { + cum: 0, + flat: 0, + line: ' });', + number: 8, + }, + { + cum: 0, + flat: 0, + line: ' });', + number: 9, + }, + { + cum: 0, + flat: 0, + line: '', + number: 10, + }, + { + cum: 30000000, + flat: 0, + line: " describe('if callSitesMap is not empty', () => {", + number: 11, + }, + { + cum: 40000000, + flat: 0, + line: " it('returns an array containing samples/line info, sorted by line number and padded with extra lines', () => {", + number: 12, + }, + { + cum: 0, + flat: 0, + line: ' const result = buildPlaceholderLineProfiles(', + number: 13, + }, + { + cum: 0, + flat: 0, + line: ' new Map([', + number: 14, + }, + { + cum: 710000000, + flat: 0, + line: ' [12, { line: 12, flat: 0, cum: 40000000 }],', + number: 15, + }, + { + cum: 0, + flat: 0, + line: ' [15, { line: 15, flat: 0, cum: 710000000 }],', + number: 16, + }, + { + cum: 0, + flat: 0, + line: ' [11, { line: 11, flat: 0, cum: 30000000 }],', + number: 17, + }, + { + cum: 0, + flat: 0, + line: ' ])', + number: 18, + }, + { + cum: 0, + flat: 0, + line: ' );', + number: 19, + }, + { + cum: 0, + flat: 0, + line: '', + number: 20, + }, + { + cum: 0, + flat: 0, + line: ' expect(result).toMatchInlineSnapshot();', + number: 21, + }, + { + cum: 0, + flat: 0, + line: ' });', + number: 22, + }, + { + cum: 0, + flat: 0, + line: ' });', + number: 23, + }, + { + cum: 0, + flat: 0, + line: '});', + number: 24, + }, + ]); + }); + + describe('when the lines contained in callSitesMap are smaller than the number of padded lines', () => { + it('returns an array cropped properly', () => { + const [snippet, allLines] = annotateLines( + fileContent, + new Map([ + [2, { line: 2, flat: 0, cum: 40000000 }], + [4, { line: 4, flat: 0, cum: 710000000 }], + [1, { line: 1, flat: 0, cum: 30000000 }], + ]) + ); + + expect(snippet).toEqual([ { - "cum": 0, - "flat": 0, - "line": " const result = buildPlaceholderLineProfiles(new Map());", - "number": 6, + cum: 30000000, + flat: 0, + line: "import { buildLineProfiles, buildPlaceholderLineProfiles } from '../buildLineProfiles';", + number: 1, }, { - "cum": 0, - "flat": 0, - "line": " expect(result).toEqual([]);", - "number": 7, + cum: 40000000, + flat: 0, + line: '', + number: 2, }, { - "cum": 0, - "flat": 0, - "line": " });", - "number": 8, + cum: 0, + flat: 0, + line: "describe('buildPlaceholderLineProfiles(callSitesMap)', () => {", + number: 3, }, { - "cum": 0, - "flat": 0, - "line": " });", - "number": 9, + cum: 710000000, + flat: 0, + line: " describe('if callSitesMap is empty', () => {", + number: 4, }, { - "cum": 0, - "flat": 0, - "line": "", - "number": 10, + cum: 0, + flat: 0, + line: " it('returns an empty array', () => {", + number: 5, }, { - "cum": 30000000, - "flat": 0, - "line": " describe('if callSitesMap is not empty', () => {", - "number": 11, + cum: 0, + flat: 0, + line: ' const result = buildPlaceholderLineProfiles(new Map());', + number: 6, }, { - "cum": 40000000, - "flat": 0, - "line": " it('returns an array containing samples/line info, sorted by line number and padded with extra lines', () => {", - "number": 12, + cum: 0, + flat: 0, + line: ' expect(result).toEqual([]);', + number: 7, }, { - "cum": 0, - "flat": 0, - "line": " const result = buildPlaceholderLineProfiles(", - "number": 13, + cum: 0, + flat: 0, + line: ' });', + number: 8, }, { - "cum": 0, - "flat": 0, - "line": " new Map([", - "number": 14, + cum: 0, + flat: 0, + line: ' });', + number: 9, }, + ]); + + expect(allLines).toEqual([ { - "cum": 710000000, - "flat": 0, - "line": " [12, { line: 12, flat: 0, cum: 40000000 }],", - "number": 15, + cum: 30000000, + flat: 0, + line: "import { buildLineProfiles, buildPlaceholderLineProfiles } from '../buildLineProfiles';", + number: 1, }, { - "cum": 0, - "flat": 0, - "line": " [15, { line: 15, flat: 0, cum: 710000000 }],", - "number": 16, + cum: 40000000, + flat: 0, + line: '', + number: 2, }, { - "cum": 0, - "flat": 0, - "line": " [11, { line: 11, flat: 0, cum: 30000000 }],", - "number": 17, + cum: 0, + flat: 0, + line: "describe('buildPlaceholderLineProfiles(callSitesMap)', () => {", + number: 3, }, { - "cum": 0, - "flat": 0, - "line": " ])", - "number": 18, + cum: 710000000, + flat: 0, + line: " describe('if callSitesMap is empty', () => {", + number: 4, }, { - "cum": 0, - "flat": 0, - "line": " );", - "number": 19, + cum: 0, + flat: 0, + line: " it('returns an empty array', () => {", + number: 5, }, { - "cum": 0, - "flat": 0, - "line": "", - "number": 20, + cum: 0, + flat: 0, + line: ' const result = buildPlaceholderLineProfiles(new Map());', + number: 6, }, - ] - `); - }); - - describe('when the lines contained in callSitesMap are smaller than the number of padded lines', () => { - it('returns an array cropped properly', () => { - const result = buildLineProfiles( - fileContent, - new Map([ - [2, { line: 2, flat: 0, cum: 40000000 }], - [4, { line: 4, flat: 0, cum: 710000000 }], - [1, { line: 1, flat: 0, cum: 30000000 }], - ]) - ); - - expect(result).toMatchInlineSnapshot(` - [ - { - "cum": 30000000, - "flat": 0, - "line": "import { buildLineProfiles, buildPlaceholderLineProfiles } from '../buildLineProfiles';", - "number": 1, - }, - { - "cum": 40000000, - "flat": 0, - "line": "", - "number": 2, - }, - { - "cum": 0, - "flat": 0, - "line": "describe('buildPlaceholderLineProfiles(callSitesMap)', () => {", - "number": 3, - }, - { - "cum": 710000000, - "flat": 0, - "line": " describe('if callSitesMap is empty', () => {", - "number": 4, - }, - { - "cum": 0, - "flat": 0, - "line": " it('returns an empty array', () => {", - "number": 5, - }, - { - "cum": 0, - "flat": 0, - "line": " const result = buildPlaceholderLineProfiles(new Map());", - "number": 6, - }, - { - "cum": 0, - "flat": 0, - "line": " expect(result).toEqual([]);", - "number": 7, - }, - { - "cum": 0, - "flat": 0, - "line": " });", - "number": 8, - }, - { - "cum": 0, - "flat": 0, - "line": " });", - "number": 9, - }, - ] - `); + { + cum: 0, + flat: 0, + line: ' expect(result).toEqual([]);', + number: 7, + }, + { + cum: 0, + flat: 0, + line: ' });', + number: 8, + }, + { + cum: 0, + flat: 0, + line: ' });', + number: 9, + }, + { + cum: 0, + flat: 0, + line: '', + number: 10, + }, + { + cum: 0, + flat: 0, + line: " describe('if callSitesMap is not empty', () => {", + number: 11, + }, + { + cum: 0, + flat: 0, + line: " it('returns an array containing samples/line info, sorted by line number and padded with extra lines', () => {", + number: 12, + }, + { + cum: 0, + flat: 0, + line: ' const result = buildPlaceholderLineProfiles(', + number: 13, + }, + { + cum: 0, + flat: 0, + line: ' new Map([', + number: 14, + }, + { + cum: 0, + flat: 0, + line: ' [12, { line: 12, flat: 0, cum: 40000000 }],', + number: 15, + }, + { + cum: 0, + flat: 0, + line: ' [15, { line: 15, flat: 0, cum: 710000000 }],', + number: 16, + }, + { + cum: 0, + flat: 0, + line: ' [11, { line: 11, flat: 0, cum: 30000000 }],', + number: 17, + }, + { + cum: 0, + flat: 0, + line: ' ])', + number: 18, + }, + { + cum: 0, + flat: 0, + line: ' );', + number: 19, + }, + { + cum: 0, + flat: 0, + line: '', + number: 20, + }, + { + cum: 0, + flat: 0, + line: ' expect(result).toMatchInlineSnapshot();', + number: 21, + }, + { + cum: 0, + flat: 0, + line: ' });', + number: 22, + }, + { + cum: 0, + flat: 0, + line: ' });', + number: 23, + }, + { + cum: 0, + flat: 0, + line: '});', + number: 24, + }, + ]); }); }); }); diff --git a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/buildLineProfiles.ts b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/buildLineProfiles.ts index d9ef03bf..3f041a88 100644 --- a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/buildLineProfiles.ts +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/buildLineProfiles.ts @@ -4,9 +4,9 @@ const VERTICAL_LINES_PADDING = 5; type CallSitesMap = Map; -export function buildPlaceholderLineProfiles(callSitesMap: CallSitesMap) { +export function annotatePlaceholderLineProfiles(callSitesMap: CallSitesMap): LineProfile[][] { if (!callSitesMap.size) { - return []; + return [[], []]; } const callSites = Array.from(callSitesMap.values()).sort((a, b) => a.line - b.line); @@ -14,12 +14,11 @@ export function buildPlaceholderLineProfiles(callSitesMap: CallSitesMap) { const firstLineIndex = Math.max(0, callSites[0].line - VERTICAL_LINES_PADDING - 1); const lastLineIndex = callSites[callSites.length - 1].line + VERTICAL_LINES_PADDING + 1; - const lines = []; - + const annotatedSnippet = []; for (let lineNumber = firstLineIndex + 1; lineNumber < lastLineIndex; lineNumber++) { const callSite = callSitesMap.get(lineNumber); - lines.push({ + annotatedSnippet.push({ line: undefined, number: lineNumber, cum: callSite?.cum ?? 0, @@ -27,22 +26,17 @@ export function buildPlaceholderLineProfiles(callSitesMap: CallSitesMap) { }); } - return lines; + // With no file contents, we return only a dummy annotated snippet which shows + // the appropriate line numbers, but no content. + return [annotatedSnippet, []]; } -export function buildLineProfiles(fileContent: string, callSitesMap: CallSitesMap): LineProfile[] { - if (!callSitesMap.size) { - return []; - } - +export function annotateLines(fileContent: string, callSitesMap: CallSitesMap): LineProfile[][] { const callSites = Array.from(callSitesMap.values()).sort((a, b) => a.line - b.line); - const allLines = fileContent.split('\n'); - - const firstLineIndex = Math.max(0, callSites[0].line - VERTICAL_LINES_PADDING - 1); - const lastLineIndex = Math.min(allLines.length, callSites[callSites.length - 1].line + VERTICAL_LINES_PADDING); + const lines = fileContent.split('\n'); - return allLines.slice(firstLineIndex, lastLineIndex).map((line, index) => { - const lineNumber = index + firstLineIndex + 1; + const annotatedLines = lines.map((line, index) => { + const lineNumber = index + 1; const callSite = callSitesMap.get(lineNumber); return { @@ -52,4 +46,15 @@ export function buildLineProfiles(fileContent: string, callSitesMap: CallSitesMa flat: callSite?.flat ?? 0, }; }); + + if (callSitesMap.size === 0) { + // If the call site map is empty, there's no snippet to render. + return [[], annotatedLines]; + } + + const firstLineIndex = Math.max(0, callSites[0].line - VERTICAL_LINES_PADDING - 1); + const lastLineIndex = Math.min(lines.length, callSites[callSites.length - 1].line + VERTICAL_LINES_PADDING); + const annotatedSnippet = annotatedLines.slice(firstLineIndex, lastLineIndex); + + return [annotatedSnippet, annotatedLines]; } diff --git a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/useCodeContainer.ts b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/useCodeContainer.ts index ff1bb401..d4f8cef4 100644 --- a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/useCodeContainer.ts +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/useCodeContainer.ts @@ -5,15 +5,16 @@ import { FunctionDetails, LineProfile } from '../../../domain/types/FunctionDeta import { useGitHubContext } from '../../GitHubContextProvider/useGitHubContext'; import { useFetchVCSFile } from '../infrastructure/useFetchVCSFile'; import { buildGithubUrlForFunction } from './buildGithubUrlForFunction'; -import { buildLineProfiles, buildPlaceholderLineProfiles } from './buildLineProfiles'; +import { annotateLines, annotatePlaceholderLineProfiles } from './buildLineProfiles'; /** * View model for Code component */ export type CodeLine = LineProfile & { line: string }; -type CodeContainerDomainValue = DomainHookReturnValue & { data: { lines: CodeLine[] } }; +type CodeContainerDomainValue = DomainHookReturnValue & { data: { snippetLines: CodeLine[]; allLines: CodeLine[] } }; +// eslint-disable-next-line sonarjs/cognitive-complexity export function useCodeContainer(dataSourceUid: string, functionDetails: FunctionDetails): CodeContainerDomainValue { const { isLoggedIn } = useGitHubContext(); const { version } = functionDetails; @@ -34,13 +35,16 @@ export function useCodeContainer(dataSourceUid: string, functionDetails: Functio }); // might be a bit costly so we memoize it - const lines = useMemo( - () => - fileInfo?.content - ? buildLineProfiles(fileInfo.content, functionDetails.callSites) - : buildPlaceholderLineProfiles(functionDetails.callSites), - [fileInfo?.content, functionDetails.callSites] - ); + const { snippetLines, lines } = useMemo(() => { + const [snippetLines, lines] = fileInfo?.content + ? annotateLines(fileInfo.content, functionDetails.callSites) + : annotatePlaceholderLineProfiles(functionDetails.callSites); + + return { + snippetLines, + lines, + }; + }, [fileInfo?.content, functionDetails.callSites]); return { data: { @@ -49,7 +53,8 @@ export function useCodeContainer(dataSourceUid: string, functionDetails: Functio isLoadingCode: isFetching, unit: functionDetails.unit, githubUrl: fileInfo?.URL ? buildGithubUrlForFunction(fileInfo.URL, functionDetails.startLine) : undefined, - lines: lines.map((line) => ({ ...line, line: line.line ?? '???' })), + snippetLines: snippetLines.map((annotatedLine) => ({ ...annotatedLine, line: annotatedLine.line ?? '???' })), + allLines: lines.map((annotateLine) => ({ ...annotateLine, line: annotateLine.line ?? '???' })), noCodeAvailable: Boolean(fetchError) || !lines.some((line) => line.line), }, actions: { From 071a438d6955ee5c20092150789d20c5bb8a26f9 Mon Sep 17 00:00:00 2001 From: bryanhuhta Date: Wed, 12 Mar 2025 10:11:29 -0500 Subject: [PATCH 3/4] Reorganize annotation functions Rename buildLineProfiles.ts to annotateLines.ts and define a type for the return value of annotate* functions. --- ...Profiles.spec.ts => annotateLines.spec.ts} | 24 +++++++-------- ...{buildLineProfiles.ts => annotateLines.ts} | 29 +++++++++++++++---- .../CodeContainer/domain/useCodeContainer.ts | 23 +++++++-------- 3 files changed, 45 insertions(+), 31 deletions(-) rename src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/{buildLineProfiles.spec.ts => annotateLines.spec.ts} (96%) rename src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/{buildLineProfiles.ts => annotateLines.ts} (78%) diff --git a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/buildLineProfiles.spec.ts b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/annotateLines.spec.ts similarity index 96% rename from src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/buildLineProfiles.spec.ts rename to src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/annotateLines.spec.ts index 1d9b917d..2f241b8d 100644 --- a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/buildLineProfiles.spec.ts +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/annotateLines.spec.ts @@ -1,16 +1,16 @@ -import { annotateLines, annotatePlaceholderLineProfiles } from '../buildLineProfiles'; +import { annotateLines, annotatePlaceholderLines } from '../annotateLines'; describe('buildPlaceholderLineProfiles(callSitesMap)', () => { describe('if callSitesMap is empty', () => { it('returns an empty snippet and empty allLines array', () => { - const result = annotatePlaceholderLineProfiles(new Map()); + const result = annotatePlaceholderLines(new Map()); expect(result).toEqual([[], []]); }); }); describe('if callSitesMap is not empty', () => { it('returns a snippet of annotated code lines', () => { - const [snippet, allLines] = annotatePlaceholderLineProfiles( + const { snippetLines, allLines } = annotatePlaceholderLines( new Map([ [12, { line: 12, flat: 0, cum: 40000000 }], [15, { line: 15, flat: 0, cum: 710000000 }], @@ -18,7 +18,7 @@ describe('buildPlaceholderLineProfiles(callSitesMap)', () => { ]) ); - expect(snippet).toEqual([ + expect(snippetLines).toEqual([ { cum: 0, flat: 0, @@ -116,7 +116,7 @@ describe('buildPlaceholderLineProfiles(callSitesMap)', () => { describe('when the lines contained in callSitesMap are smaller than the number of padded lines', () => { it('returns an array cropped properly', () => { - const [snippet, allLines] = annotatePlaceholderLineProfiles( + const { snippetLines, allLines } = annotatePlaceholderLines( new Map([ [2, { line: 2, flat: 0, cum: 40000000 }], [4, { line: 4, flat: 0, cum: 710000000 }], @@ -124,7 +124,7 @@ describe('buildPlaceholderLineProfiles(callSitesMap)', () => { ]) ); - expect(snippet).toEqual([ + expect(snippetLines).toEqual([ { cum: 30000000, flat: 0, @@ -190,8 +190,8 @@ describe('buildPlaceholderLineProfiles(callSitesMap)', () => { describe('buildLineProfiles(fileContent, callSitesMap', () => { describe('if callSitesMap is empty', () => { it('returns an empty snippet array', () => { - const [snippet, allLines] = annotateLines('// this file is empty', new Map()); - expect(snippet).toEqual([]); + const { snippetLines, allLines } = annotateLines('// this file is empty', new Map()); + expect(snippetLines).toEqual([]); expect(allLines).toEqual([ { cum: 0, @@ -230,7 +230,7 @@ describe('buildPlaceholderLineProfiles(callSitesMap)', () => { });`; it('returns an array containing samples/line info, sorted by line number and padded with extra lines', () => { - const [snippet, allLines] = annotateLines( + const { snippetLines, allLines } = annotateLines( fileContent, new Map([ [12, { line: 12, flat: 0, cum: 40000000 }], @@ -239,7 +239,7 @@ describe('buildPlaceholderLineProfiles(callSitesMap)', () => { ]) ); - expect(snippet).toEqual([ + expect(snippetLines).toEqual([ { cum: 0, flat: 0, @@ -482,7 +482,7 @@ describe('buildPlaceholderLineProfiles(callSitesMap)', () => { describe('when the lines contained in callSitesMap are smaller than the number of padded lines', () => { it('returns an array cropped properly', () => { - const [snippet, allLines] = annotateLines( + const { snippetLines, allLines } = annotateLines( fileContent, new Map([ [2, { line: 2, flat: 0, cum: 40000000 }], @@ -491,7 +491,7 @@ describe('buildPlaceholderLineProfiles(callSitesMap)', () => { ]) ); - expect(snippet).toEqual([ + expect(snippetLines).toEqual([ { cum: 30000000, flat: 0, diff --git a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/buildLineProfiles.ts b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/annotateLines.ts similarity index 78% rename from src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/buildLineProfiles.ts rename to src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/annotateLines.ts index 3f041a88..f3d4217f 100644 --- a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/buildLineProfiles.ts +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/annotateLines.ts @@ -4,9 +4,17 @@ const VERTICAL_LINES_PADDING = 5; type CallSitesMap = Map; -export function annotatePlaceholderLineProfiles(callSitesMap: CallSitesMap): LineProfile[][] { +interface AnnotatedLines { + snippetLines: LineProfile[]; + allLines: LineProfile[]; +} + +export function annotatePlaceholderLines(callSitesMap: CallSitesMap): AnnotatedLines { if (!callSitesMap.size) { - return [[], []]; + return { + snippetLines: [], + allLines: [], + }; } const callSites = Array.from(callSitesMap.values()).sort((a, b) => a.line - b.line); @@ -28,10 +36,13 @@ export function annotatePlaceholderLineProfiles(callSitesMap: CallSitesMap): Lin // With no file contents, we return only a dummy annotated snippet which shows // the appropriate line numbers, but no content. - return [annotatedSnippet, []]; + return { + snippetLines: annotatedSnippet, + allLines: [], + }; } -export function annotateLines(fileContent: string, callSitesMap: CallSitesMap): LineProfile[][] { +export function annotateLines(fileContent: string, callSitesMap: CallSitesMap): AnnotatedLines { const callSites = Array.from(callSitesMap.values()).sort((a, b) => a.line - b.line); const lines = fileContent.split('\n'); @@ -49,12 +60,18 @@ export function annotateLines(fileContent: string, callSitesMap: CallSitesMap): if (callSitesMap.size === 0) { // If the call site map is empty, there's no snippet to render. - return [[], annotatedLines]; + return { + snippetLines: [], + allLines: annotatedLines, + }; } const firstLineIndex = Math.max(0, callSites[0].line - VERTICAL_LINES_PADDING - 1); const lastLineIndex = Math.min(lines.length, callSites[callSites.length - 1].line + VERTICAL_LINES_PADDING); const annotatedSnippet = annotatedLines.slice(firstLineIndex, lastLineIndex); - return [annotatedSnippet, annotatedLines]; + return { + snippetLines: annotatedSnippet, + allLines: annotatedLines, + }; } diff --git a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/useCodeContainer.ts b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/useCodeContainer.ts index d4f8cef4..9becf12a 100644 --- a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/useCodeContainer.ts +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/useCodeContainer.ts @@ -4,8 +4,8 @@ import { useMemo, useState } from 'react'; import { FunctionDetails, LineProfile } from '../../../domain/types/FunctionDetails'; import { useGitHubContext } from '../../GitHubContextProvider/useGitHubContext'; import { useFetchVCSFile } from '../infrastructure/useFetchVCSFile'; +import { annotateLines, annotatePlaceholderLines } from './annotateLines'; import { buildGithubUrlForFunction } from './buildGithubUrlForFunction'; -import { annotateLines, annotatePlaceholderLineProfiles } from './buildLineProfiles'; /** * View model for Code component @@ -35,16 +35,13 @@ export function useCodeContainer(dataSourceUid: string, functionDetails: Functio }); // might be a bit costly so we memoize it - const { snippetLines, lines } = useMemo(() => { - const [snippetLines, lines] = fileInfo?.content - ? annotateLines(fileInfo.content, functionDetails.callSites) - : annotatePlaceholderLineProfiles(functionDetails.callSites); - - return { - snippetLines, - lines, - }; - }, [fileInfo?.content, functionDetails.callSites]); + const { snippetLines, allLines } = useMemo( + () => + fileInfo?.content + ? annotateLines(fileInfo.content, functionDetails.callSites) + : annotatePlaceholderLines(functionDetails.callSites), + [fileInfo?.content, functionDetails.callSites] + ); return { data: { @@ -54,8 +51,8 @@ export function useCodeContainer(dataSourceUid: string, functionDetails: Functio unit: functionDetails.unit, githubUrl: fileInfo?.URL ? buildGithubUrlForFunction(fileInfo.URL, functionDetails.startLine) : undefined, snippetLines: snippetLines.map((annotatedLine) => ({ ...annotatedLine, line: annotatedLine.line ?? '???' })), - allLines: lines.map((annotateLine) => ({ ...annotateLine, line: annotateLine.line ?? '???' })), - noCodeAvailable: Boolean(fetchError) || !lines.some((line) => line.line), + allLines: allLines.map((annotateLine) => ({ ...annotateLine, line: annotateLine.line ?? '???' })), + noCodeAvailable: Boolean(fetchError) || !allLines.some((line) => line.line), }, actions: { setOpenAiSuggestions, From f603463670a095d977c0a3aa56c319ce57dfb0f2 Mon Sep 17 00:00:00 2001 From: bryanhuhta Date: Wed, 12 Mar 2025 10:21:40 -0500 Subject: [PATCH 4/4] Fix test after refactor --- .../CodeContainer/domain/__tests__/annotateLines.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/annotateLines.spec.ts b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/annotateLines.spec.ts index 2f241b8d..e7baf144 100644 --- a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/annotateLines.spec.ts +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/annotateLines.spec.ts @@ -4,7 +4,10 @@ describe('buildPlaceholderLineProfiles(callSitesMap)', () => { describe('if callSitesMap is empty', () => { it('returns an empty snippet and empty allLines array', () => { const result = annotatePlaceholderLines(new Map()); - expect(result).toEqual([[], []]); + expect(result).toEqual({ + snippetLines: [], + allLines: [], + }); }); });