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.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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 withAppStateContext from '../../app/components/app-state/withAppStateContext';
  24. import throwGlobalError from '../../app/utils/throwGlobalError';
  25. import { translate } from '../../helpers/l10n';
  26. import { AppState } from '../../types/appstate';
  27. import { DumpStatus, DumpTask } from '../../types/project-dump';
  28. import { TaskStatuses, TaskTypes } from '../../types/tasks';
  29. import { Component } from '../../types/types';
  30. import Export from './components/Export';
  31. import Import from './components/Import';
  32. import './styles.css';
  33. const POLL_INTERNAL = 5000;
  34. interface Props {
  35. appState: AppState;
  36. component: Component;
  37. }
  38. interface State {
  39. lastAnalysisTask?: DumpTask;
  40. lastExportTask?: DumpTask;
  41. lastImportTask?: DumpTask;
  42. status?: DumpStatus;
  43. }
  44. export class ProjectDumpApp extends React.Component<Props, State> {
  45. mounted = false;
  46. state: State = {};
  47. componentDidMount() {
  48. this.mounted = true;
  49. this.loadStatus();
  50. }
  51. componentDidUpdate(prevProps: Props) {
  52. if (prevProps.component.key !== this.props.component.key) {
  53. this.loadStatus();
  54. }
  55. }
  56. componentWillUnmount() {
  57. this.mounted = false;
  58. }
  59. getLastTask(component: string, type: TaskTypes) {
  60. const data = {
  61. type,
  62. component,
  63. onlyCurrents: true,
  64. status: [
  65. TaskStatuses.Pending,
  66. TaskStatuses.InProgress,
  67. TaskStatuses.Success,
  68. TaskStatuses.Failed,
  69. TaskStatuses.Canceled
  70. ].join(',')
  71. };
  72. return getActivity(data)
  73. .then(({ tasks }) => (tasks.length > 0 ? tasks[0] : undefined), throwGlobalError)
  74. .catch(() => undefined);
  75. }
  76. getLastTaskOfEachType(componentKey: string) {
  77. const {
  78. appState: { projectImportFeatureEnabled }
  79. } = this.props;
  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 {
  144. component,
  145. appState: { projectImportFeatureEnabled }
  146. } = this.props;
  147. const { lastAnalysisTask, lastExportTask, lastImportTask, status } = this.state;
  148. return (
  149. <div className="page page-limited" id="project-dump">
  150. <header className="page-header">
  151. <h1 className="page-title">{translate('project_dump.page')}</h1>
  152. <div className="page-description">
  153. {projectImportFeatureEnabled
  154. ? translate('project_dump.page.description')
  155. : translate('project_dump.page.description_without_import')}
  156. </div>
  157. </header>
  158. {status === undefined ? (
  159. <i className="spinner" />
  160. ) : (
  161. <div className="columns">
  162. <div className="column-half">
  163. <Export
  164. componentKey={component.key}
  165. loadStatus={this.poll}
  166. status={status}
  167. task={lastExportTask}
  168. />
  169. </div>
  170. <div className="column-half">
  171. <Import
  172. importEnabled={!!projectImportFeatureEnabled}
  173. analysis={lastAnalysisTask}
  174. componentKey={component.key}
  175. loadStatus={this.poll}
  176. status={status}
  177. task={lastImportTask}
  178. />
  179. </div>
  180. </div>
  181. )}
  182. </div>
  183. );
  184. }
  185. }
  186. export default withAppStateContext(ProjectDumpApp);