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/ 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__/annotateLines.spec.ts b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/annotateLines.spec.ts new file mode 100644 index 00000000..e7baf144 --- /dev/null +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/annotateLines.spec.ts @@ -0,0 +1,703 @@ +import { annotateLines, annotatePlaceholderLines } from '../annotateLines'; + +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({ + snippetLines: [], + allLines: [], + }); + }); + }); + + describe('if callSitesMap is not empty', () => { + it('returns a snippet of annotated code lines', () => { + const { snippetLines, allLines } = annotatePlaceholderLines( + new Map([ + [12, { line: 12, flat: 0, cum: 40000000 }], + [15, { line: 15, flat: 0, cum: 710000000 }], + [11, { line: 11, flat: 0, cum: 30000000 }], + ]) + ); + + expect(snippetLines).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 { snippetLines, allLines } = annotatePlaceholderLines( + new Map([ + [2, { line: 2, flat: 0, cum: 40000000 }], + [4, { line: 4, flat: 0, cum: 710000000 }], + [1, { line: 1, flat: 0, cum: 30000000 }], + ]) + ); + + expect(snippetLines).toEqual([ + { + 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([]); + }); + }); + }); +}); + +describe('buildLineProfiles(fileContent, callSitesMap', () => { + describe('if callSitesMap is empty', () => { + it('returns an empty snippet array', () => { + const { snippetLines, allLines } = annotateLines('// this file is empty', new Map()); + expect(snippetLines).toEqual([]); + expect(allLines).toEqual([ + { + cum: 0, + flat: 0, + line: '// this file is empty', + number: 1, + }, + ]); + }); + }); + + describe('if callSitesMap is not empty', () => { + const fileContent = `import { buildLineProfiles, buildPlaceholderLineProfiles } from '../buildLineProfiles'; + +describe('buildPlaceholderLineProfiles(callSitesMap)', () => { + describe('if callSitesMap is empty', () => { + it('returns an empty array', () => { + const result = buildPlaceholderLineProfiles(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( + new Map([ + [12, { line: 12, flat: 0, cum: 40000000 }], + [15, { line: 15, flat: 0, cum: 710000000 }], + [11, { line: 11, flat: 0, cum: 30000000 }], + ]) + ); + + expect(result).toMatchInlineSnapshot(); + }); + }); +});`; + + it('returns an array containing samples/line info, sorted by line number and padded with extra lines', () => { + const { snippetLines, allLines } = annotateLines( + fileContent, + new Map([ + [12, { line: 12, flat: 0, cum: 40000000 }], + [15, { line: 15, flat: 0, cum: 710000000 }], + [11, { line: 11, flat: 0, cum: 30000000 }], + ]) + ); + + expect(snippetLines).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 { snippetLines, 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(snippetLines).toEqual([ + { + 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, + }, + ]); + + expect(allLines).toEqual([ + { + 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: '', + 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/__tests__/buildLineProfiles.spec.ts b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/buildLineProfiles.spec.ts deleted file mode 100644 index 6f14a465..00000000 --- a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/__tests__/buildLineProfiles.spec.ts +++ /dev/null @@ -1,402 +0,0 @@ -import { buildLineProfiles, buildPlaceholderLineProfiles } from '../buildLineProfiles'; - -describe('buildPlaceholderLineProfiles(callSitesMap)', () => { - describe('if callSitesMap is empty', () => { - it('returns an empty array', () => { - const result = buildPlaceholderLineProfiles(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( - new Map([ - [12, { line: 12, flat: 0, cum: 40000000 }], - [15, { line: 15, flat: 0, cum: 710000000 }], - [11, { line: 11, flat: 0, cum: 30000000 }], - ]) - ); - - 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, - }, - { - "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, - }, - ] - `); - }); - - 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, - }, - ] - `); - }); - }); - }); -}); - -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([]); - }); - }); - - describe('if callSitesMap is not empty', () => { - const fileContent = `import { buildLineProfiles, buildPlaceholderLineProfiles } from '../buildLineProfiles'; - -describe('buildPlaceholderLineProfiles(callSitesMap)', () => { - describe('if callSitesMap is empty', () => { - it('returns an empty array', () => { - const result = buildPlaceholderLineProfiles(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( - new Map([ - [12, { line: 12, flat: 0, cum: 40000000 }], - [15, { line: 15, flat: 0, cum: 710000000 }], - [11, { line: 11, flat: 0, cum: 30000000 }], - ]) - ); - - expect(result).toMatchInlineSnapshot(); - }); - }); -});`; - - it('returns an array containing samples/line info, sorted by line number and padded with extra lines', () => { - const result = buildLineProfiles( - fileContent, - new Map([ - [12, { line: 12, flat: 0, cum: 40000000 }], - [15, { line: 15, flat: 0, cum: 710000000 }], - [11, { line: 11, flat: 0, cum: 30000000 }], - ]) - ); - - expect(result).toMatchInlineSnapshot(` - [ - { - "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, - }, - ] - `); - }); - - 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, - }, - ] - `); - }); - }); - }); -}); diff --git a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/annotateLines.ts b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/annotateLines.ts new file mode 100644 index 00000000..f3d4217f --- /dev/null +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/annotateLines.ts @@ -0,0 +1,77 @@ +import { CallSiteProps, LineProfile } from '../../../domain/types/FunctionDetails'; + +const VERTICAL_LINES_PADDING = 5; + +type CallSitesMap = Map; + +interface AnnotatedLines { + snippetLines: LineProfile[]; + allLines: LineProfile[]; +} + +export function annotatePlaceholderLines(callSitesMap: CallSitesMap): AnnotatedLines { + if (!callSitesMap.size) { + return { + snippetLines: [], + allLines: [], + }; + } + + const callSites = Array.from(callSitesMap.values()).sort((a, b) => a.line - b.line); + + const firstLineIndex = Math.max(0, callSites[0].line - VERTICAL_LINES_PADDING - 1); + const lastLineIndex = callSites[callSites.length - 1].line + VERTICAL_LINES_PADDING + 1; + + const annotatedSnippet = []; + for (let lineNumber = firstLineIndex + 1; lineNumber < lastLineIndex; lineNumber++) { + const callSite = callSitesMap.get(lineNumber); + + annotatedSnippet.push({ + line: undefined, + number: lineNumber, + cum: callSite?.cum ?? 0, + flat: callSite?.flat ?? 0, + }); + } + + // With no file contents, we return only a dummy annotated snippet which shows + // the appropriate line numbers, but no content. + return { + snippetLines: annotatedSnippet, + allLines: [], + }; +} + +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'); + + const annotatedLines = lines.map((line, index) => { + const lineNumber = index + 1; + const callSite = callSitesMap.get(lineNumber); + + return { + line, + number: lineNumber, + cum: callSite?.cum ?? 0, + flat: callSite?.flat ?? 0, + }; + }); + + if (callSitesMap.size === 0) { + // If the call site map is empty, there's no snippet to render. + 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 { + snippetLines: annotatedSnippet, + allLines: annotatedLines, + }; +} 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 deleted file mode 100644 index d9ef03bf..00000000 --- a/src/pages/ProfilesExplorerView/components/SceneExploreServiceFlameGraph/components/SceneFunctionDetailsPanel/components/CodeContainer/domain/buildLineProfiles.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { CallSiteProps, LineProfile } from '../../../domain/types/FunctionDetails'; - -const VERTICAL_LINES_PADDING = 5; - -type CallSitesMap = Map; - -export function buildPlaceholderLineProfiles(callSitesMap: CallSitesMap) { - if (!callSitesMap.size) { - return []; - } - - const callSites = Array.from(callSitesMap.values()).sort((a, b) => a.line - b.line); - - 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 = []; - - for (let lineNumber = firstLineIndex + 1; lineNumber < lastLineIndex; lineNumber++) { - const callSite = callSitesMap.get(lineNumber); - - lines.push({ - line: undefined, - number: lineNumber, - cum: callSite?.cum ?? 0, - flat: callSite?.flat ?? 0, - }); - } - - return lines; -} - -export function buildLineProfiles(fileContent: string, callSitesMap: CallSitesMap): LineProfile[] { - if (!callSitesMap.size) { - return []; - } - - 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); - - return allLines.slice(firstLineIndex, lastLineIndex).map((line, index) => { - const lineNumber = index + firstLineIndex + 1; - const callSite = callSitesMap.get(lineNumber); - - return { - line, - number: lineNumber, - cum: callSite?.cum ?? 0, - flat: callSite?.flat ?? 0, - }; - }); -} 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..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,16 +4,17 @@ 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 { buildLineProfiles, buildPlaceholderLineProfiles } 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,11 +35,11 @@ export function useCodeContainer(dataSourceUid: string, functionDetails: Functio }); // might be a bit costly so we memoize it - const lines = useMemo( + const { snippetLines, allLines } = useMemo( () => fileInfo?.content - ? buildLineProfiles(fileInfo.content, functionDetails.callSites) - : buildPlaceholderLineProfiles(functionDetails.callSites), + ? annotateLines(fileInfo.content, functionDetails.callSites) + : annotatePlaceholderLines(functionDetails.callSites), [fileInfo?.content, functionDetails.callSites] ); @@ -49,8 +50,9 @@ 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 ?? '???' })), - noCodeAvailable: Boolean(fetchError) || !lines.some((line) => line.line), + snippetLines: snippetLines.map((annotatedLine) => ({ ...annotatedLine, line: annotatedLine.line ?? '???' })), + allLines: allLines.map((annotateLine) => ({ ...annotateLine, line: annotateLine.line ?? '???' })), + noCodeAvailable: Boolean(fetchError) || !allLines.some((line) => line.line), }, actions: { setOpenAiSuggestions,