Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

Workspace.tsx 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  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 { omit, uniqBy } from 'lodash';
  21. import * as React from 'react';
  22. import { getRulesApp } from '../../api/rules';
  23. import { get, save } from '../../helpers/storage';
  24. import { Dict } from '../../types/types';
  25. import { ComponentDescriptor, RuleDescriptor, WorkspaceContext } from './context';
  26. import './styles.css';
  27. import WorkspaceComponentViewer from './WorkspaceComponentViewer';
  28. import WorkspaceNav from './WorkspaceNav';
  29. import WorkspacePortal from './WorkspacePortal';
  30. import WorkspaceRuleViewer from './WorkspaceRuleViewer';
  31. const WORKSPACE = 'sonarqube-workspace';
  32. interface State {
  33. components: ComponentDescriptor[];
  34. externalRulesRepoNames: Dict<string>;
  35. height: number;
  36. maximized?: boolean;
  37. open: { component?: string; rule?: string };
  38. rules: RuleDescriptor[];
  39. }
  40. export const MIN_HEIGHT = 0.05;
  41. export const MAX_HEIGHT = 0.85;
  42. export const INITIAL_HEIGHT = 300;
  43. export const TYPE_KEY = '__type__';
  44. export enum WorkspaceTypes {
  45. Rule = 'rule',
  46. Component = 'component'
  47. }
  48. export default class Workspace extends React.PureComponent<{}, State> {
  49. mounted = false;
  50. constructor(props: {}) {
  51. super(props);
  52. this.state = {
  53. externalRulesRepoNames: {},
  54. height: INITIAL_HEIGHT,
  55. open: {},
  56. ...this.loadWorkspace()
  57. };
  58. }
  59. componentDidMount() {
  60. this.mounted = true;
  61. this.fetchRuleNames();
  62. }
  63. componentDidUpdate(_: {}, prevState: State) {
  64. if (prevState.components !== this.state.components || prevState.rules !== this.state.rules) {
  65. this.saveWorkspace();
  66. }
  67. }
  68. componentWillUnmount() {
  69. this.mounted = false;
  70. }
  71. fetchRuleNames = async () => {
  72. const { repositories } = await getRulesApp();
  73. const externalRulesRepoNames: Dict<string> = {};
  74. repositories
  75. .filter(({ key }) => key.startsWith('external_'))
  76. .forEach(({ key, name }) => {
  77. externalRulesRepoNames[key.replace('external_', '')] = name;
  78. });
  79. this.setState({ externalRulesRepoNames });
  80. };
  81. loadWorkspace = () => {
  82. try {
  83. const data: any[] = JSON.parse(get(WORKSPACE) || '');
  84. const components: ComponentDescriptor[] = data.filter(
  85. x => x[TYPE_KEY] === WorkspaceTypes.Component
  86. );
  87. const rules: RuleDescriptor[] = data.filter(x => x[TYPE_KEY] === WorkspaceTypes.Rule);
  88. return { components, rules };
  89. } catch {
  90. // Fail silently.
  91. return { components: [], rules: [] };
  92. }
  93. };
  94. saveWorkspace = () => {
  95. const data = [
  96. // Do not save line number, next time the file is open, it should be open
  97. // on the first line.
  98. ...this.state.components.map(x =>
  99. omit({ ...x, [TYPE_KEY]: WorkspaceTypes.Component }, 'line')
  100. ),
  101. ...this.state.rules.map(x => ({ ...x, [TYPE_KEY]: WorkspaceTypes.Rule }))
  102. ];
  103. save(WORKSPACE, JSON.stringify(data));
  104. };
  105. handleOpenComponent = (component: ComponentDescriptor) => {
  106. this.setState((state: State) => ({
  107. components: uniqBy([...state.components, component], c => c.key),
  108. open: { component: component.key }
  109. }));
  110. };
  111. handleComponentReopen = (componentKey: string) => {
  112. this.setState({ open: { component: componentKey } });
  113. };
  114. handleOpenRule = (rule: RuleDescriptor) => {
  115. this.setState((state: State) => ({
  116. open: { rule: rule.key },
  117. rules: uniqBy([...state.rules, rule], r => r.key)
  118. }));
  119. };
  120. handleRuleReopen = (ruleKey: string) => {
  121. this.setState({ open: { rule: ruleKey } });
  122. };
  123. handleComponentClose = (componentKey: string) => {
  124. this.setState((state: State) => ({
  125. components: state.components.filter(x => x.key !== componentKey),
  126. open: {
  127. ...state.open,
  128. component: state.open.component === componentKey ? undefined : state.open.component
  129. }
  130. }));
  131. };
  132. handleRuleClose = (ruleKey: string) => {
  133. this.setState((state: State) => ({
  134. rules: state.rules.filter(x => x.key !== ruleKey),
  135. open: {
  136. ...state.open,
  137. rule: state.open.rule === ruleKey ? undefined : state.open.rule
  138. }
  139. }));
  140. };
  141. handleComponentLoad = (details: { key: string; name: string; qualifier: string }) => {
  142. if (this.mounted) {
  143. const { key, name, qualifier } = details;
  144. this.setState((state: State) => ({
  145. components: state.components.map(component =>
  146. component.key === key ? { ...component, name, qualifier } : component
  147. )
  148. }));
  149. }
  150. };
  151. handleRuleLoad = (details: { key: string; name: string }) => {
  152. if (this.mounted) {
  153. const { key, name } = details;
  154. this.setState((state: State) => ({
  155. rules: state.rules.map(rule => (rule.key === key ? { ...rule, name } : rule))
  156. }));
  157. }
  158. };
  159. handleCollapse = () => {
  160. this.setState({ open: {} });
  161. };
  162. handleMaximize = () => {
  163. this.setState({ maximized: true });
  164. };
  165. handleMinimize = () => {
  166. this.setState({ maximized: false });
  167. };
  168. handleResize = (deltaY: number) => {
  169. const minHeight = window.innerHeight * MIN_HEIGHT;
  170. const maxHeight = window.innerHeight * MAX_HEIGHT;
  171. this.setState((state: State) => ({
  172. height: Math.min(maxHeight, Math.max(minHeight, state.height - deltaY))
  173. }));
  174. };
  175. render() {
  176. const { components, externalRulesRepoNames, height, maximized, open, rules } = this.state;
  177. const openComponent = open.component && components.find(x => x.key === open.component);
  178. const openRule = open.rule && rules.find(x => x.key === open.rule);
  179. const actualHeight = maximized ? window.innerHeight * MAX_HEIGHT : height;
  180. return (
  181. <WorkspaceContext.Provider
  182. value={{
  183. externalRulesRepoNames,
  184. openComponent: this.handleOpenComponent,
  185. openRule: this.handleOpenRule
  186. }}>
  187. {this.props.children}
  188. <WorkspacePortal>
  189. {(components.length > 0 || rules.length > 0) && (
  190. <WorkspaceNav
  191. components={components}
  192. onComponentClose={this.handleComponentClose}
  193. onComponentOpen={this.handleComponentReopen}
  194. onRuleClose={this.handleRuleClose}
  195. onRuleOpen={this.handleRuleReopen}
  196. open={open}
  197. rules={rules}
  198. />
  199. )}
  200. {openComponent && (
  201. <WorkspaceComponentViewer
  202. component={openComponent}
  203. height={actualHeight}
  204. maximized={maximized}
  205. onClose={this.handleComponentClose}
  206. onCollapse={this.handleCollapse}
  207. onLoad={this.handleComponentLoad}
  208. onMaximize={this.handleMaximize}
  209. onMinimize={this.handleMinimize}
  210. onResize={this.handleResize}
  211. />
  212. )}
  213. {openRule && (
  214. <WorkspaceRuleViewer
  215. height={actualHeight}
  216. maximized={maximized}
  217. onClose={this.handleRuleClose}
  218. onCollapse={this.handleCollapse}
  219. onLoad={this.handleRuleLoad}
  220. onMaximize={this.handleMaximize}
  221. onMinimize={this.handleMinimize}
  222. onResize={this.handleResize}
  223. rule={openRule}
  224. />
  225. )}
  226. </WorkspacePortal>
  227. </WorkspaceContext.Provider>
  228. );
  229. }
  230. }