You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SourceViewerBase.tsx 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2018 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import * as React from 'react';
  21. import * as classNames from 'classnames';
  22. import { intersection, uniqBy } from 'lodash';
  23. import SourceViewerHeader from './SourceViewerHeader';
  24. import SourceViewerCode from './SourceViewerCode';
  25. import DuplicationPopup from './components/DuplicationPopup';
  26. import defaultLoadIssues from './helpers/loadIssues';
  27. import getCoverageStatus from './helpers/getCoverageStatus';
  28. import {
  29. duplicationsByLine,
  30. issuesByLine,
  31. locationsByLine,
  32. symbolsByLine
  33. } from './helpers/indexing';
  34. import {
  35. getComponentData,
  36. getComponentForSourceViewer,
  37. getDuplications,
  38. getSources
  39. } from '../../api/components';
  40. import { isSameBranchLike, getBranchLikeQuery } from '../../helpers/branches';
  41. import { translate } from '../../helpers/l10n';
  42. import { Alert } from '../ui/Alert';
  43. import { WorkspaceContext } from '../workspace/context';
  44. import './styles.css';
  45. // TODO react-virtualized
  46. export interface Props {
  47. aroundLine?: number;
  48. branchLike: T.BranchLike | undefined;
  49. component: string;
  50. displayAllIssues?: boolean;
  51. displayIssueLocationsCount?: boolean;
  52. displayIssueLocationsLink?: boolean;
  53. displayLocationMarkers?: boolean;
  54. highlightedLine?: number;
  55. // `undefined` elements mean they are located in a different file,
  56. // but kept to maintaint the location indexes
  57. highlightedLocations?: (T.FlowLocation | undefined)[];
  58. highlightedLocationMessage?: { index: number; text: string | undefined };
  59. loadComponent: (
  60. component: string,
  61. branchLike: T.BranchLike | undefined
  62. ) => Promise<T.SourceViewerFile>;
  63. loadIssues: (
  64. component: string,
  65. from: number,
  66. to: number,
  67. branchLike: T.BranchLike | undefined
  68. ) => Promise<T.Issue[]>;
  69. loadSources: (
  70. component: string,
  71. from: number,
  72. to: number,
  73. branchLike: T.BranchLike | undefined
  74. ) => Promise<T.SourceLine[]>;
  75. onLoaded?: (component: T.SourceViewerFile, sources: T.SourceLine[], issues: T.Issue[]) => void;
  76. onLocationSelect?: (index: number) => void;
  77. onIssueChange?: (issue: T.Issue) => void;
  78. onIssueSelect?: (issueKey: string) => void;
  79. onIssueUnselect?: () => void;
  80. scroll?: (element: HTMLElement) => void;
  81. selectedIssue?: string;
  82. }
  83. interface State {
  84. component?: T.SourceViewerFile;
  85. displayDuplications: boolean;
  86. duplicatedFiles?: { [ref: string]: T.DuplicatedFile };
  87. duplications?: T.Duplication[];
  88. duplicationsByLine: { [line: number]: number[] };
  89. hasSourcesAfter: boolean;
  90. highlightedSymbols: string[];
  91. issueLocationsByLine: { [line: number]: T.LinearIssueLocation[] };
  92. issuePopup?: { issue: string; name: string };
  93. issues?: T.Issue[];
  94. issuesByLine: { [line: number]: T.Issue[] };
  95. linePopup?: { index?: number; line: number; name: string };
  96. loading: boolean;
  97. loadingSourcesAfter: boolean;
  98. loadingSourcesBefore: boolean;
  99. notAccessible: boolean;
  100. notExist: boolean;
  101. openIssuesByLine: { [line: number]: boolean };
  102. selectedIssue?: string;
  103. sourceRemoved: boolean;
  104. sources?: T.SourceLine[];
  105. symbolsByLine: { [line: number]: string[] };
  106. }
  107. const LINES = 500;
  108. export default class SourceViewerBase extends React.PureComponent<Props, State> {
  109. node?: HTMLElement | null;
  110. mounted = false;
  111. static defaultProps = {
  112. displayAllIssues: false,
  113. displayIssueLocationsCount: true,
  114. displayIssueLocationsLink: true,
  115. displayLocationMarkers: true,
  116. loadComponent: defaultLoadComponent,
  117. loadIssues: defaultLoadIssues,
  118. loadSources: defaultLoadSources
  119. };
  120. constructor(props: Props) {
  121. super(props);
  122. this.state = {
  123. displayDuplications: false,
  124. duplicationsByLine: {},
  125. hasSourcesAfter: false,
  126. highlightedSymbols: [],
  127. issuesByLine: {},
  128. issueLocationsByLine: {},
  129. loading: true,
  130. loadingSourcesAfter: false,
  131. loadingSourcesBefore: false,
  132. notAccessible: false,
  133. notExist: false,
  134. openIssuesByLine: {},
  135. selectedIssue: props.selectedIssue,
  136. sourceRemoved: false,
  137. symbolsByLine: {}
  138. };
  139. }
  140. componentDidMount() {
  141. this.mounted = true;
  142. this.fetchComponent();
  143. }
  144. componentWillReceiveProps(nextProps: Props) {
  145. // if a component or a branch has changed,
  146. // set `loading: true` immediately to avoid unwanted scrolling in `LineCode`
  147. if (
  148. nextProps.component !== this.props.component ||
  149. !isSameBranchLike(nextProps.branchLike, this.props.branchLike)
  150. ) {
  151. this.setState({ loading: true });
  152. }
  153. if (
  154. nextProps.onIssueSelect !== undefined &&
  155. nextProps.selectedIssue !== this.props.selectedIssue
  156. ) {
  157. this.setState({ selectedIssue: nextProps.selectedIssue });
  158. }
  159. }
  160. componentDidUpdate(prevProps: Props) {
  161. if (
  162. prevProps.component !== this.props.component ||
  163. !isSameBranchLike(prevProps.branchLike, this.props.branchLike)
  164. ) {
  165. this.fetchComponent();
  166. } else if (
  167. this.props.aroundLine !== undefined &&
  168. prevProps.aroundLine !== this.props.aroundLine &&
  169. this.isLineOutsideOfRange(this.props.aroundLine)
  170. ) {
  171. this.fetchSources();
  172. } else {
  173. const { selectedIssue } = this.props;
  174. const { issues } = this.state;
  175. if (
  176. selectedIssue !== undefined &&
  177. issues !== undefined &&
  178. issues.find(issue => issue.key === selectedIssue) === undefined
  179. ) {
  180. this.reloadIssues();
  181. }
  182. }
  183. }
  184. componentWillUnmount() {
  185. this.mounted = false;
  186. }
  187. computeCoverageStatus(lines: T.SourceLine[]) {
  188. return lines.map(line => ({ ...line, coverageStatus: getCoverageStatus(line) }));
  189. }
  190. isLineOutsideOfRange(lineNumber: number) {
  191. const { sources } = this.state;
  192. if (sources && sources.length > 0) {
  193. const firstLine = sources[0];
  194. const lastList = sources[sources.length - 1];
  195. return lineNumber < firstLine.line || lineNumber > lastList.line;
  196. } else {
  197. return true;
  198. }
  199. }
  200. fetchComponent() {
  201. this.setState({ loading: true });
  202. const to = (this.props.aroundLine || 0) + LINES;
  203. const loadIssues = (component: T.SourceViewerFile, sources: T.SourceLine[]) => {
  204. this.props.loadIssues(this.props.component, 1, to, this.props.branchLike).then(
  205. issues => {
  206. if (this.mounted) {
  207. const finalSources = sources.slice(0, LINES);
  208. this.setState(
  209. {
  210. component,
  211. displayDuplications: false,
  212. duplicatedFiles: undefined,
  213. duplications: undefined,
  214. duplicationsByLine: {},
  215. hasSourcesAfter: sources.length > LINES,
  216. highlightedSymbols: [],
  217. issueLocationsByLine: locationsByLine(issues),
  218. issues,
  219. issuesByLine: issuesByLine(issues),
  220. linePopup: undefined,
  221. loading: false,
  222. notAccessible: false,
  223. notExist: false,
  224. openIssuesByLine: {},
  225. issuePopup: undefined,
  226. sourceRemoved: false,
  227. sources: this.computeCoverageStatus(finalSources),
  228. symbolsByLine: symbolsByLine(sources.slice(0, LINES))
  229. },
  230. () => {
  231. if (this.props.onLoaded) {
  232. this.props.onLoaded(component, finalSources, issues);
  233. }
  234. }
  235. );
  236. }
  237. },
  238. () => {
  239. // TODO
  240. }
  241. );
  242. };
  243. const onFailLoadComponent = ({ response }: { response: Response }) => {
  244. // TODO handle other statuses
  245. if (this.mounted) {
  246. if (response.status === 403) {
  247. this.setState({ loading: false, notAccessible: true });
  248. } else if (response.status === 404) {
  249. this.setState({ loading: false, notExist: true });
  250. }
  251. }
  252. };
  253. const onFailLoadSources = (response: Response, component: T.SourceViewerFile) => {
  254. // TODO handle other statuses
  255. if (this.mounted) {
  256. if (response.status === 403) {
  257. this.setState({ component, loading: false, notAccessible: true });
  258. } else if (response.status === 404) {
  259. this.setState({ component, loading: false, sourceRemoved: true });
  260. }
  261. }
  262. };
  263. const onResolve = (component: T.SourceViewerFile) => {
  264. const sourcesRequest =
  265. component.q === 'FIL' || component.q === 'UTS' ? this.loadSources() : Promise.resolve([]);
  266. sourcesRequest.then(
  267. sources => loadIssues(component, sources),
  268. response => onFailLoadSources(response, component)
  269. );
  270. };
  271. this.props
  272. .loadComponent(this.props.component, this.props.branchLike)
  273. .then(onResolve, onFailLoadComponent);
  274. }
  275. fetchSources() {
  276. this.loadSources().then(
  277. sources => {
  278. if (this.mounted) {
  279. const finalSources = sources.slice(0, LINES);
  280. this.setState(
  281. {
  282. sources: sources.slice(0, LINES),
  283. hasSourcesAfter: sources.length > LINES
  284. },
  285. () => {
  286. if (this.props.onLoaded && this.state.component && this.state.issues) {
  287. this.props.onLoaded(this.state.component, finalSources, this.state.issues);
  288. }
  289. }
  290. );
  291. }
  292. },
  293. () => {
  294. // TODO
  295. }
  296. );
  297. }
  298. reloadIssues() {
  299. if (!this.state.sources) {
  300. return;
  301. }
  302. const firstSourceLine = this.state.sources[0];
  303. const lastSourceLine = this.state.sources[this.state.sources.length - 1];
  304. this.props
  305. .loadIssues(
  306. this.props.component,
  307. firstSourceLine && firstSourceLine.line,
  308. lastSourceLine && lastSourceLine.line,
  309. this.props.branchLike
  310. )
  311. .then(
  312. issues => {
  313. if (this.mounted) {
  314. this.setState({
  315. issues,
  316. issuesByLine: issuesByLine(issues),
  317. issueLocationsByLine: locationsByLine(issues)
  318. });
  319. }
  320. },
  321. () => {
  322. // TODO
  323. }
  324. );
  325. }
  326. loadSources = (): Promise<T.SourceLine[]> => {
  327. return new Promise((resolve, reject) => {
  328. const onFailLoadSources = ({ response }: { response: Response }) => {
  329. // TODO handle other statuses
  330. if (this.mounted) {
  331. if ([403, 404].includes(response.status)) {
  332. reject(response);
  333. } else {
  334. resolve([]);
  335. }
  336. }
  337. };
  338. const from = this.props.aroundLine ? Math.max(1, this.props.aroundLine - LINES / 2 + 1) : 1;
  339. let to = this.props.aroundLine ? this.props.aroundLine + LINES / 2 + 1 : LINES + 1;
  340. // make sure we try to download `LINES` lines
  341. if (from === 1 && to < LINES) {
  342. to = LINES;
  343. }
  344. // request one additional line to define `hasSourcesAfter`
  345. to++;
  346. return this.props
  347. .loadSources(this.props.component, from, to, this.props.branchLike)
  348. .then(sources => resolve(sources), onFailLoadSources);
  349. });
  350. };
  351. loadSourcesBefore = () => {
  352. if (!this.state.sources) {
  353. return;
  354. }
  355. const firstSourceLine = this.state.sources[0];
  356. this.setState({ loadingSourcesBefore: true });
  357. const from = Math.max(1, firstSourceLine.line - LINES);
  358. this.props
  359. .loadSources(this.props.component, from, firstSourceLine.line - 1, this.props.branchLike)
  360. .then(
  361. sources => {
  362. this.props
  363. .loadIssues(this.props.component, from, firstSourceLine.line - 1, this.props.branchLike)
  364. .then(
  365. issues => {
  366. if (this.mounted) {
  367. this.setState(prevState => {
  368. const nextIssues = uniqBy(
  369. [...issues, ...(prevState.issues || [])],
  370. issue => issue.key
  371. );
  372. return {
  373. issues: nextIssues,
  374. issuesByLine: issuesByLine(nextIssues),
  375. issueLocationsByLine: locationsByLine(nextIssues),
  376. loadingSourcesBefore: false,
  377. sources: [
  378. ...this.computeCoverageStatus(sources),
  379. ...(prevState.sources || [])
  380. ],
  381. symbolsByLine: { ...prevState.symbolsByLine, ...symbolsByLine(sources) }
  382. };
  383. });
  384. }
  385. },
  386. () => {
  387. // TODO
  388. }
  389. );
  390. },
  391. () => {
  392. // TODO
  393. }
  394. );
  395. };
  396. loadSourcesAfter = () => {
  397. if (!this.state.sources) {
  398. return;
  399. }
  400. const lastSourceLine = this.state.sources[this.state.sources.length - 1];
  401. this.setState({ loadingSourcesAfter: true });
  402. const fromLine = lastSourceLine.line + 1;
  403. // request one additional line to define `hasSourcesAfter`
  404. const toLine = lastSourceLine.line + LINES + 1;
  405. this.props.loadSources(this.props.component, fromLine, toLine, this.props.branchLike).then(
  406. sources => {
  407. this.props.loadIssues(this.props.component, fromLine, toLine, this.props.branchLike).then(
  408. issues => {
  409. if (this.mounted) {
  410. this.setState(prevState => {
  411. const nextIssues = uniqBy(
  412. [...(prevState.issues || []), ...issues],
  413. issue => issue.key
  414. );
  415. return {
  416. issues: nextIssues,
  417. issuesByLine: issuesByLine(nextIssues),
  418. issueLocationsByLine: locationsByLine(nextIssues),
  419. hasSourcesAfter: sources.length > LINES,
  420. loadingSourcesAfter: false,
  421. sources: [
  422. ...(prevState.sources || []),
  423. ...this.computeCoverageStatus(sources.slice(0, LINES))
  424. ],
  425. symbolsByLine: {
  426. ...prevState.symbolsByLine,
  427. ...symbolsByLine(sources.slice(0, LINES))
  428. }
  429. };
  430. });
  431. }
  432. },
  433. () => {
  434. // TODO
  435. }
  436. );
  437. },
  438. () => {
  439. // TODO
  440. }
  441. );
  442. };
  443. loadDuplications = (line: T.SourceLine) => {
  444. getDuplications({
  445. key: this.props.component,
  446. ...getBranchLikeQuery(this.props.branchLike)
  447. }).then(
  448. r => {
  449. if (this.mounted) {
  450. this.setState(state => ({
  451. displayDuplications: true,
  452. duplications: r.duplications,
  453. duplicationsByLine: duplicationsByLine(r.duplications),
  454. duplicatedFiles: r.files,
  455. linePopup:
  456. r.duplications.length === 1
  457. ? { index: 0, line: line.line, name: 'duplications' }
  458. : state.linePopup
  459. }));
  460. }
  461. },
  462. () => {
  463. // TODO
  464. }
  465. );
  466. };
  467. handleLinePopupToggle = ({
  468. index,
  469. line,
  470. name,
  471. open
  472. }: {
  473. index?: number;
  474. line: number;
  475. name: string;
  476. open?: boolean;
  477. }) => {
  478. this.setState((state: State) => {
  479. const samePopup =
  480. state.linePopup !== undefined &&
  481. state.linePopup.name === name &&
  482. state.linePopup.line === line &&
  483. state.linePopup.index === index;
  484. if (open !== false && !samePopup) {
  485. return { linePopup: { index, line, name } };
  486. } else if (open !== true && samePopup) {
  487. return { linePopup: undefined };
  488. }
  489. return null;
  490. });
  491. };
  492. closeLinePopup = () => {
  493. this.setState({ linePopup: undefined });
  494. };
  495. handleIssuePopupToggle = (issue: string, popupName: string, open?: boolean) => {
  496. this.setState((state: State) => {
  497. const samePopup =
  498. state.issuePopup && state.issuePopup.name === popupName && state.issuePopup.issue === issue;
  499. if (open !== false && !samePopup) {
  500. return { issuePopup: { issue, name: popupName } };
  501. } else if (open !== true && samePopup) {
  502. return { issuePopup: undefined };
  503. }
  504. return null;
  505. });
  506. };
  507. handleSymbolClick = (symbols: string[]) => {
  508. this.setState(state => {
  509. const shouldDisable = intersection(state.highlightedSymbols, symbols).length > 0;
  510. const highlightedSymbols = shouldDisable ? [] : symbols;
  511. return { highlightedSymbols };
  512. });
  513. };
  514. handleIssueSelect = (issue: string) => {
  515. if (this.props.onIssueSelect) {
  516. this.props.onIssueSelect(issue);
  517. } else {
  518. this.setState({ selectedIssue: issue });
  519. }
  520. };
  521. handleIssueUnselect = () => {
  522. if (this.props.onIssueUnselect) {
  523. this.props.onIssueUnselect();
  524. } else {
  525. this.setState({ selectedIssue: undefined });
  526. }
  527. };
  528. handleOpenIssues = (line: T.SourceLine) => {
  529. this.setState(state => ({
  530. openIssuesByLine: { ...state.openIssuesByLine, [line.line]: true }
  531. }));
  532. };
  533. handleCloseIssues = (line: T.SourceLine) => {
  534. this.setState(state => ({
  535. openIssuesByLine: { ...state.openIssuesByLine, [line.line]: false }
  536. }));
  537. };
  538. handleIssueChange = (issue: T.Issue) => {
  539. this.setState(({ issues = [] }) => {
  540. const newIssues = issues.map(candidate => (candidate.key === issue.key ? issue : candidate));
  541. return { issues: newIssues, issuesByLine: issuesByLine(newIssues) };
  542. });
  543. if (this.props.onIssueChange) {
  544. this.props.onIssueChange(issue);
  545. }
  546. };
  547. renderDuplicationPopup = (index: number, line: number) => {
  548. const { component, duplicatedFiles, duplications } = this.state;
  549. if (!component || !duplicatedFiles) return <></>;
  550. const duplication = duplications && duplications[index];
  551. let blocks = (duplication && duplication.blocks) || [];
  552. /* eslint-disable no-underscore-dangle */
  553. const inRemovedComponent = blocks.some(b => b._ref === undefined);
  554. let foundOne = false;
  555. blocks = blocks.filter(b => {
  556. const outOfBounds = b.from > line || b.from + b.size < line;
  557. const currentFile = b._ref === '1';
  558. const shouldDisplayForCurrentFile = outOfBounds || foundOne;
  559. const shouldDisplay = !currentFile || shouldDisplayForCurrentFile;
  560. const isOk = b._ref !== undefined && shouldDisplay;
  561. if (b._ref === '1' && !outOfBounds) {
  562. foundOne = true;
  563. }
  564. return isOk;
  565. });
  566. /* eslint-enable no-underscore-dangle */
  567. return (
  568. <WorkspaceContext.Consumer>
  569. {({ openComponent }) => (
  570. <DuplicationPopup
  571. blocks={blocks}
  572. branchLike={this.props.branchLike}
  573. duplicatedFiles={duplicatedFiles}
  574. inRemovedComponent={inRemovedComponent}
  575. onClose={this.closeLinePopup}
  576. openComponent={openComponent}
  577. sourceViewerFile={component}
  578. />
  579. )}
  580. </WorkspaceContext.Consumer>
  581. );
  582. };
  583. renderCode(sources: T.SourceLine[]) {
  584. const hasSourcesBefore = sources.length > 0 && sources[0].line > 1;
  585. return (
  586. <SourceViewerCode
  587. branchLike={this.props.branchLike}
  588. componentKey={this.props.component}
  589. displayAllIssues={this.props.displayAllIssues}
  590. displayIssueLocationsCount={this.props.displayIssueLocationsCount}
  591. displayIssueLocationsLink={this.props.displayIssueLocationsLink}
  592. displayLocationMarkers={this.props.displayLocationMarkers}
  593. duplications={this.state.duplications}
  594. duplicationsByLine={this.state.duplicationsByLine}
  595. hasSourcesAfter={this.state.hasSourcesAfter}
  596. hasSourcesBefore={hasSourcesBefore}
  597. highlightedLine={this.props.highlightedLine}
  598. highlightedLocationMessage={this.props.highlightedLocationMessage}
  599. highlightedLocations={this.props.highlightedLocations}
  600. highlightedSymbols={this.state.highlightedSymbols}
  601. issueLocationsByLine={this.state.issueLocationsByLine}
  602. issuePopup={this.state.issuePopup}
  603. issues={this.state.issues}
  604. issuesByLine={this.state.issuesByLine}
  605. linePopup={this.state.linePopup}
  606. loadDuplications={this.loadDuplications}
  607. loadSourcesAfter={this.loadSourcesAfter}
  608. loadSourcesBefore={this.loadSourcesBefore}
  609. loadingSourcesAfter={this.state.loadingSourcesAfter}
  610. loadingSourcesBefore={this.state.loadingSourcesBefore}
  611. onIssueChange={this.handleIssueChange}
  612. onIssuePopupToggle={this.handleIssuePopupToggle}
  613. onIssueSelect={this.handleIssueSelect}
  614. onIssueUnselect={this.handleIssueUnselect}
  615. onIssuesClose={this.handleCloseIssues}
  616. onIssuesOpen={this.handleOpenIssues}
  617. onLinePopupToggle={this.handleLinePopupToggle}
  618. onLocationSelect={this.props.onLocationSelect}
  619. onSymbolClick={this.handleSymbolClick}
  620. openIssuesByLine={this.state.openIssuesByLine}
  621. renderDuplicationPopup={this.renderDuplicationPopup}
  622. scroll={this.props.scroll}
  623. selectedIssue={this.state.selectedIssue}
  624. sources={sources}
  625. symbolsByLine={this.state.symbolsByLine}
  626. />
  627. );
  628. }
  629. render() {
  630. const { component, loading, sources, notAccessible, sourceRemoved } = this.state;
  631. if (loading) {
  632. return null;
  633. }
  634. if (this.state.notExist) {
  635. return (
  636. <Alert className="spacer-top" variant="warning">
  637. {translate('component_viewer.no_component')}
  638. </Alert>
  639. );
  640. }
  641. if (notAccessible) {
  642. return (
  643. <Alert className="spacer-top" variant="warning">
  644. {translate('code_viewer.no_source_code_displayed_due_to_security')}
  645. </Alert>
  646. );
  647. }
  648. if (!component) {
  649. return null;
  650. }
  651. const className = classNames('source-viewer', {
  652. 'source-duplications-expanded': this.state.displayDuplications
  653. });
  654. return (
  655. <div className={className} ref={node => (this.node = node)}>
  656. <WorkspaceContext.Consumer>
  657. {({ openComponent }) => (
  658. <SourceViewerHeader
  659. branchLike={this.props.branchLike}
  660. openComponent={openComponent}
  661. sourceViewerFile={component}
  662. />
  663. )}
  664. </WorkspaceContext.Consumer>
  665. {sourceRemoved && (
  666. <Alert className="spacer-top" variant="warning">
  667. {translate('code_viewer.no_source_code_displayed_due_to_source_removed')}
  668. </Alert>
  669. )}
  670. {!sourceRemoved && sources !== undefined && this.renderCode(sources)}
  671. </div>
  672. );
  673. }
  674. }
  675. function defaultLoadComponent(component: string, branchLike: T.BranchLike | undefined) {
  676. return Promise.all([
  677. getComponentForSourceViewer({ component, ...getBranchLikeQuery(branchLike) }),
  678. getComponentData({ component, ...getBranchLikeQuery(branchLike) })
  679. ]).then(([component, data]) => ({
  680. ...component,
  681. leakPeriodDate: data.leakPeriodDate
  682. }));
  683. }
  684. function defaultLoadSources(
  685. key: string,
  686. from: number | undefined,
  687. to: number | undefined,
  688. branchLike: T.BranchLike | undefined
  689. ) {
  690. return getSources({ key, from, to, ...getBranchLikeQuery(branchLike) });
  691. }