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.

ProjectDumpApp.tsx 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2022 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 { getActivity } from '../../api/ce';
  22. import { getStatus } from '../../api/project-dump';
  23. import withAvailableFeatures, {
  24. WithAvailableFeaturesProps
  25. } from '../../app/components/available-features/withAvailableFeatures';
  26. import withComponentContext from '../../app/components/componentContext/withComponentContext';
  27. import { throwGlobalError } from '../../helpers/error';
  28. import { translate } from '../../helpers/l10n';
  29. import { Feature } from '../../types/features';
  30. import { DumpStatus, DumpTask } from '../../types/project-dump';
  31. import { ActivityRequestParameters, TaskStatuses, TaskTypes } from '../../types/tasks';
  32. import { Component } from '../../types/types';
  33. import Export from './components/Export';
  34. import Import from './components/Import';
  35. import './styles.css';
  36. const POLL_INTERNAL = 5000;
  37. interface Props extends WithAvailableFeaturesProps {
  38. component: Component;
  39. }
  40. interface State {
  41. lastAnalysisTask?: DumpTask;
  42. lastExportTask?: DumpTask;
  43. lastImportTask?: DumpTask;
  44. status?: DumpStatus;
  45. }
  46. export class ProjectDumpApp extends React.Component<Props, State> {
  47. mounted = false;
  48. state: State = {};
  49. componentDidMount() {
  50. this.mounted = true;
  51. this.loadStatus();
  52. }
  53. componentDidUpdate(prevProps: Props) {
  54. if (prevProps.component.key !== this.props.component.key) {
  55. this.loadStatus();
  56. }
  57. }
  58. componentWillUnmount() {
  59. this.mounted = false;
  60. }
  61. getLastTask(component: string, type: TaskTypes) {
  62. const data: ActivityRequestParameters = {
  63. type,
  64. component,
  65. onlyCurrents: true,
  66. status: [
  67. TaskStatuses.Pending,
  68. TaskStatuses.InProgress,
  69. TaskStatuses.Success,
  70. TaskStatuses.Failed,
  71. TaskStatuses.Canceled
  72. ].join(',')
  73. };
  74. return getActivity(data)
  75. .then(({ tasks }) => (tasks.length > 0 ? tasks[0] : undefined), throwGlobalError)
  76. .catch(() => undefined);
  77. }
  78. getLastTaskOfEachType(componentKey: string) {
  79. const projectImportFeatureEnabled = this.props.hasFeature(Feature.ProjectImport);
  80. const all = projectImportFeatureEnabled
  81. ? [
  82. this.getLastTask(componentKey, TaskTypes.ProjectExport),
  83. this.getLastTask(componentKey, TaskTypes.ProjectImport),
  84. this.getLastTask(componentKey, TaskTypes.Report)
  85. ]
  86. : [
  87. this.getLastTask(componentKey, TaskTypes.ProjectExport),
  88. Promise.resolve(),
  89. this.getLastTask(componentKey, TaskTypes.Report)
  90. ];
  91. return Promise.all(all).then(([lastExportTask, lastImportTask, lastAnalysisTask]) => ({
  92. lastExportTask,
  93. lastImportTask,
  94. lastAnalysisTask
  95. }));
  96. }
  97. loadStatus = () => {
  98. const { component } = this.props;
  99. return Promise.all([getStatus(component.key), this.getLastTaskOfEachType(component.key)]).then(
  100. ([status, { lastExportTask, lastImportTask, lastAnalysisTask }]) => {
  101. if (this.mounted) {
  102. this.setState({
  103. status,
  104. lastExportTask,
  105. lastImportTask,
  106. lastAnalysisTask
  107. });
  108. }
  109. return {
  110. status,
  111. lastExportTask,
  112. lastImportTask,
  113. lastAnalysisTask
  114. };
  115. }
  116. );
  117. };
  118. poll = () => {
  119. this.loadStatus().then(
  120. ({ lastExportTask, lastImportTask }) => {
  121. if (this.mounted) {
  122. const progressStatus = [TaskStatuses.Pending, TaskStatuses.InProgress];
  123. const exportNotFinished =
  124. lastExportTask === undefined || progressStatus.includes(lastExportTask.status);
  125. const importNotFinished =
  126. lastImportTask === undefined || progressStatus.includes(lastImportTask.status);
  127. if (exportNotFinished || importNotFinished) {
  128. setTimeout(this.poll, POLL_INTERNAL);
  129. } else {
  130. // Since we fetch status separate from task we could not get an up to date status.
  131. // even if we detect that export / import is finish.
  132. // Doing a last call will make sur we get the latest status.
  133. this.loadStatus();
  134. }
  135. }
  136. },
  137. () => {
  138. /* no catch needed */
  139. }
  140. );
  141. };
  142. render() {
  143. const { component } = this.props;
  144. const projectImportFeatureEnabled = this.props.hasFeature(Feature.ProjectImport);
  145. const { lastAnalysisTask, lastExportTask, lastImportTask, status } = this.state;
  146. return (
  147. <div className="page page-limited" id="project-dump">
  148. <header className="page-header">
  149. <h1 className="page-title">{translate('project_dump.page')}</h1>
  150. <div className="page-description">
  151. {projectImportFeatureEnabled
  152. ? translate('project_dump.page.description')
  153. : translate('project_dump.page.description_without_import')}
  154. </div>
  155. </header>
  156. {status === undefined ? (
  157. <i className="spinner" />
  158. ) : (
  159. <div className="columns">
  160. <div className="column-half">
  161. <Export
  162. componentKey={component.key}
  163. loadStatus={this.poll}
  164. status={status}
  165. task={lastExportTask}
  166. />
  167. </div>
  168. <div className="column-half">
  169. <Import
  170. importEnabled={!!projectImportFeatureEnabled}
  171. analysis={lastAnalysisTask}
  172. componentKey={component.key}
  173. loadStatus={this.poll}
  174. status={status}
  175. task={lastImportTask}
  176. />
  177. </div>
  178. </div>
  179. )}
  180. </div>
  181. );
  182. }
  183. }
  184. export default withComponentContext(withAvailableFeatures(ProjectDumpApp));