]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10375 Project's homepage sidebar improvements (#3032)
authorStas Vilchik <stas.vilchik@sonarsource.com>
Thu, 8 Feb 2018 11:05:28 +0000 (12:05 +0100)
committerGitHub <noreply@github.com>
Thu, 8 Feb 2018 11:05:28 +0000 (12:05 +0100)
20 files changed:
server/sonar-web/src/main/js/app/theme.js
server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx
server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx
server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx
server/sonar-web/src/main/js/apps/overview/meta/MetaKey.js [deleted file]
server/sonar-web/src/main/js/apps/overview/meta/MetaKey.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.tsx
server/sonar-web/src/main/js/apps/overview/meta/MetaOrganizationKey.js [deleted file]
server/sonar-web/src/main/js/apps/overview/meta/MetaOrganizationKey.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js [deleted file]
server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js [deleted file]
server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx
server/sonar-web/src/main/js/apps/overview/meta/MetaTags.tsx
server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap
server/sonar-web/src/main/js/apps/overview/styles.css
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index b5ebfb7b796e7d9a632576959fc2f2104c349bac..04f518b8060b7caf35727d0b0183c2ade7092cb7 100644 (file)
@@ -70,6 +70,8 @@ module.exports = {
 
   contextNavHeightRaw: 9 * grid,
 
+  pagePadding: '20px',
+
   // different
   defaultShadow: '0 6px 12px rgba(0, 0, 0, 0.175)',
 
index b86c74027f4a0870adf00600d806664bd97db0b9..4a4e572f7c39120a2b58b79d523c8fdfdc75f897 100644 (file)
@@ -65,7 +65,7 @@ export default class BadgesModal extends React.PureComponent<Props, State> {
     const header = translate('overview.badges.title');
     const fullBadgeOptions = { branch, project, ...badgeOptions };
     return (
-      <>
+      <div className="overview-meta-card">
         <button className="js-project-badges" onClick={this.handleOpen}>
           {translate('overview.badges.get_badge')}
         </button>
@@ -106,7 +106,7 @@ export default class BadgesModal extends React.PureComponent<Props, State> {
             </footer>
           </Modal>
         )}
-      </>
+      </div>
     );
   }
 }
index f43e19a571555d57eab8913fb6551be2735dda2e..e813fd772bda5de07bdc073a36f0fea32c450450 100644 (file)
@@ -1,14 +1,16 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`should display the modal after click 1`] = `
-<React.Fragment>
+<div
+  className="overview-meta-card"
+>
   <button
     className="js-project-badges"
     onClick={[Function]}
   >
     overview.badges.get_badge
   </button>
-</React.Fragment>
+</div>
 `;
 
 exports[`should display the modal after click 2`] = `
index 44285fe03da1722a836ef781ec1e4335f4106727..4e7dd8c57b5741552edd87ae7b7919bc83ca46c2 100644 (file)
@@ -191,7 +191,7 @@ export class OverviewApp extends React.PureComponent<Props, State> {
             </div>
           </div>
 
-          <div className="page-sidebar-fixed">
+          <div className="overview-sidebar page-sidebar-fixed">
             <Meta
               branch={branchName}
               component={component}
index ac3f80bb2a835f95d39654f2f8c3dedc9a5ea753..6984424fa8feb9d4f57652de22da63eef0521ef9 100644 (file)
@@ -116,7 +116,9 @@ export default class AnalysesList extends React.PureComponent<Props, State> {
 
     return (
       <div className="overview-meta-card">
-        <h4 className="overview-meta-header">{translate('project_activity.page')}</h4>
+        <h4 className="overview-meta-header">
+          {translate('overview.project_activity', this.props.component.qualifier)}
+        </h4>
 
         <PreviewGraph
           branch={this.props.branch}
index 82521e8c55c19560651b868a4bf47b381faf06db..4a55b5e29a44a91be5dd99a91283e8fa7839264f 100644 (file)
@@ -30,6 +30,7 @@ import BadgesModal from '../badges/BadgesModal';
 import AnalysesList from '../events/AnalysesList';
 import { Visibility, Component, Metric } from '../../../app/types';
 import { History } from '../../../api/time-machine';
+import { translate } from '../../../helpers/l10n';
 import { MeasureEnhanced } from '../../../helpers/measures';
 
 interface Props {
@@ -55,25 +56,18 @@ export default class Meta extends React.PureComponent<Props> {
     const isProject = qualifier === 'TRK';
     const isPrivate = visibility === Visibility.Private;
 
-    const hasDescription = !!description;
-    const hasQualityProfiles = Array.isArray(qualityProfiles) && qualityProfiles.length > 0;
-    const hasQualityGate = !!qualityGate;
-
-    const shouldShowQualityProfiles = isProject && hasQualityProfiles;
-    const shouldShowQualityGate = isProject && hasQualityGate;
-    const hasOrganization = component.organization != null && organizationsEnabled;
-
     return (
       <div className="overview-meta">
-        {hasDescription && (
-          <div className="overview-meta-card overview-meta-description">{description}</div>
-        )}
-
-        <MetaSize branch={branch} component={component} measures={this.props.measures} />
-
-        {isProject && (
-          <MetaTags component={component} onComponentChange={this.props.onComponentChange} />
-        )}
+        <div className="overview-meta-card">
+          <h4 className="overview-meta-header">
+            {translate('overview.about_this_project', qualifier)}
+          </h4>
+          {description !== undefined && <p className="overview-meta-description">{description}</p>}
+          {isProject && (
+            <MetaTags component={component} onComponentChange={this.props.onComponentChange} />
+          )}
+          <MetaSize branch={branch} component={component} measures={this.props.measures} />
+        </div>
 
         <AnalysesList
           branch={branch}
@@ -83,27 +77,32 @@ export default class Meta extends React.PureComponent<Props> {
           qualifier={component.qualifier}
         />
 
-        {shouldShowQualityGate && (
-          <MetaQualityGate
-            gate={qualityGate}
-            organization={hasOrganization && component.organization}
-          />
-        )}
-
-        {shouldShowQualityProfiles && (
-          <MetaQualityProfiles
-            component={component}
-            customOrganizations={organizationsEnabled}
-            organization={component.organization}
-            profiles={qualityProfiles}
-          />
+        {isProject && (
+          <div className="overview-meta-card">
+            {qualityGate && (
+              <MetaQualityGate
+                organization={organizationsEnabled ? component.organization : undefined}
+                qualityGate={qualityGate}
+              />
+            )}
+
+            {qualityProfiles &&
+              qualityProfiles.length > 0 && (
+                <MetaQualityProfiles
+                  headerClassName={qualityGate ? 'big-spacer-top' : undefined}
+                  organization={organizationsEnabled ? component.organization : undefined}
+                  profiles={qualityProfiles}
+                />
+              )}
+          </div>
         )}
 
         {isProject && <MetaLinks component={component} />}
 
-        <MetaKey component={component} />
-
-        {hasOrganization && <MetaOrganizationKey component={component} />}
+        <div className="overview-meta-card">
+          <MetaKey componentKey={component.key} qualifier={component.qualifier} />
+          {organizationsEnabled && <MetaOrganizationKey organization={component.organization} />}
+        </div>
 
         {onSonarCloud &&
           isProject &&
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaKey.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaKey.js
deleted file mode 100644 (file)
index cfb43f2..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import React from 'react';
-import { translate } from '../../../helpers/l10n';
-
-const MetaKey = ({ component }) => {
-  return (
-    <div className="overview-meta-card">
-      <h4 className="overview-meta-header">{translate('key')}</h4>
-      <input
-        className="overview-key"
-        type="text"
-        value={component.key}
-        readOnly={true}
-        onClick={e => e.target.select()}
-      />
-    </div>
-  );
-};
-
-export default MetaKey;
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaKey.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaKey.tsx
new file mode 100644 (file)
index 0000000..4d6c574
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import ClipboardButton from '../../../components/controls/ClipboardButton';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  componentKey: string;
+  qualifier: string;
+}
+
+export default function MetaKey({ componentKey, qualifier }: Props) {
+  return (
+    <>
+      <h4 className="overview-meta-header">{translate('overview.project_key', qualifier)}</h4>
+      <div className="display-flex-center">
+        <input className="overview-key" type="text" value={componentKey} readOnly={true} />
+        <ClipboardButton className="little-spacer-left" copyValue={componentKey} />
+      </div>
+    </>
+  );
+}
index 66980dda92637b22322e8b4aeea017c216ab6b43..907d76a3877957eff061c8de9b622e859985b13e 100644 (file)
@@ -22,6 +22,7 @@ import MetaLink from './MetaLink';
 import { getProjectLinks, ProjectLink } from '../../../api/projectLinks';
 import { orderLinks } from '../../project-admin/links/utils';
 import { LightComponent } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
 
 interface Props {
   component: LightComponent;
@@ -71,6 +72,7 @@ export default class MetaLinks extends React.PureComponent<Props, State> {
 
     return (
       <div className="overview-meta-card">
+        <h4 className="overview-meta-header">{translate('overview.external_links')}</h4>
         <ul className="overview-meta-list">
           {orderedLinks.map(link => <MetaLink key={link.id} link={link} />)}
         </ul>
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaOrganizationKey.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaOrganizationKey.js
deleted file mode 100644 (file)
index 30bd9aa..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import React from 'react';
-import { translate } from '../../../helpers/l10n';
-
-const MetaOrganizationKey = ({ component }) => {
-  return (
-    <div className="overview-meta-card">
-      <h4 className="overview-meta-header">{translate('organization_key')}</h4>
-      <input
-        className="overview-key"
-        type="text"
-        value={component.organization}
-        readOnly={true}
-        onClick={e => e.target.select()}
-      />
-    </div>
-  );
-};
-
-export default MetaOrganizationKey;
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaOrganizationKey.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaOrganizationKey.tsx
new file mode 100644 (file)
index 0000000..9d8ca5a
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { translate } from '../../../helpers/l10n';
+import ClipboardButton from '../../../components/controls/ClipboardButton';
+
+interface Props {
+  organization: string;
+}
+
+export default function MetaOrganizationKey({ organization }: Props) {
+  return (
+    <>
+      <h4 className="overview-meta-header big-spacer-top">{translate('organization_key')}</h4>
+      <div className="display-flex-center">
+        <input className="overview-key" type="text" value={organization} readOnly={true} />
+        <ClipboardButton className="little-spacer-left" copyValue={organization} />
+      </div>
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js
deleted file mode 100644 (file)
index 2181137..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import React from 'react';
-import { Link } from 'react-router';
-import { translate } from '../../../helpers/l10n';
-import { getQualityGateUrl } from '../../../helpers/urls';
-
-const MetaQualityGate = ({ gate, organization }) => {
-  return (
-    <div className="overview-meta-card">
-      <h4 className="overview-meta-header">{translate('overview.quality_gate')}</h4>
-
-      <ul className="overview-meta-list">
-        <li>
-          {gate.isDefault && (
-            <span className="note spacer-right">{'(' + translate('default') + ')'}</span>
-          )}
-          <Link to={getQualityGateUrl(gate.key, organization)}>{gate.name}</Link>
-        </li>
-      </ul>
-    </div>
-  );
-};
-
-export default MetaQualityGate;
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.tsx
new file mode 100644 (file)
index 0000000..bd71304
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { Link } from 'react-router';
+import { translate } from '../../../helpers/l10n';
+import { getQualityGateUrl } from '../../../helpers/urls';
+
+interface Props {
+  organization?: string;
+  qualityGate: { isDefault?: boolean; key: string; name: string };
+}
+
+export default function MetaQualityGate({ qualityGate, organization }: Props) {
+  return (
+    <>
+      <h4 className="overview-meta-header">{translate('overview.quality_gate')}</h4>
+
+      <ul className="overview-meta-list">
+        <li>
+          {qualityGate.isDefault && (
+            <span className="note spacer-right">{'(' + translate('default') + ')'}</span>
+          )}
+          <Link to={getQualityGateUrl(qualityGate.key, organization)}>{qualityGate.name}</Link>
+        </li>
+      </ul>
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js
deleted file mode 100644 (file)
index c39bcf3..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { connect } from 'react-redux';
-import { Link } from 'react-router';
-import Tooltip from '../../../components/controls/Tooltip';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { getQualityProfileUrl } from '../../../helpers/urls';
-import { searchRules } from '../../../api/rules';
-import { getLanguages } from '../../../store/rootReducer';
-
-class MetaQualityProfiles extends React.PureComponent {
-  /*:: mounted: boolean; */
-
-  /*:: props: {
-    component: { organization: string },
-    customOrganizations: boolean,
-    languages: { [string]: { name: string } },
-    organization: string | void;
-    profiles: Array<{ key: string, language: string, name: string }>
-  };
-*/
-
-  state = {
-    deprecatedByKey: {}
-  };
-
-  componentDidMount() {
-    this.mounted = true;
-    this.loadDeprecatedRules();
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  loadDeprecatedRules() {
-    const requests = this.props.profiles.map(profile =>
-      this.loadDeprecatedRulesForProfile(profile.key)
-    );
-    Promise.all(requests).then(
-      responses => {
-        if (this.mounted) {
-          const deprecatedByKey = {};
-          responses.forEach((count, i) => {
-            const profileKey = this.props.profiles[i].key;
-            deprecatedByKey[profileKey] = count;
-          });
-          this.setState({ deprecatedByKey });
-        }
-      },
-      () => {}
-    );
-  }
-
-  loadDeprecatedRulesForProfile(profileKey) {
-    const data = {
-      activation: 'true',
-      organization: this.props.organization,
-      ps: 1,
-      qprofile: profileKey,
-      statuses: 'DEPRECATED'
-    };
-    return searchRules(data).then(r => r.total);
-  }
-
-  getDeprecatedRulesCount(profile) {
-    const count = this.state.deprecatedByKey[profile.key];
-    return count || 0;
-  }
-
-  renderProfile(profile) {
-    const languageFromStore = this.props.languages[profile.language];
-    const languageName = languageFromStore ? languageFromStore.name : profile.language;
-
-    const path = this.props.customOrganizations
-      ? getQualityProfileUrl(profile.name, profile.language, this.props.component.organization)
-      : getQualityProfileUrl(profile.name, profile.language);
-
-    const inner = (
-      <div className="text-ellipsis">
-        <span className="note spacer-right">{'(' + languageName + ')'}</span>
-        <Link to={path}>{profile.name}</Link>
-      </div>
-    );
-
-    const count = this.getDeprecatedRulesCount(profile);
-
-    if (count > 0) {
-      const tooltip = translateWithParameters('overview.deprecated_profile', count);
-      return (
-        <Tooltip key={profile.key} overlay={tooltip}>
-          <li className="overview-deprecated-rules">{inner}</li>
-        </Tooltip>
-      );
-    }
-
-    return <li key={profile.key}>{inner}</li>;
-  }
-
-  render() {
-    const { profiles } = this.props;
-
-    return (
-      <div className="overview-meta-card">
-        <h4 className="overview-meta-header">{translate('overview.quality_profiles')}</h4>
-
-        <ul className="overview-meta-list">
-          {profiles.map(profile => this.renderProfile(profile))}
-        </ul>
-      </div>
-    );
-  }
-}
-
-const mapStateToProps = state => ({
-  languages: getLanguages(state)
-});
-
-export default connect(mapStateToProps)(MetaQualityProfiles);
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.tsx
new file mode 100644 (file)
index 0000000..c964881
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { Link } from 'react-router';
+import * as classNames from 'classnames';
+import Tooltip from '../../../components/controls/Tooltip';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { getQualityProfileUrl } from '../../../helpers/urls';
+import { searchRules } from '../../../api/rules';
+import { getLanguages } from '../../../store/rootReducer';
+
+interface StateProps {
+  languages: { [key: string]: { name: string } };
+}
+
+interface OwnProps {
+  headerClassName?: string;
+  organization?: string;
+  profiles: { key: string; language: string; name: string }[];
+}
+
+interface State {
+  deprecatedByKey: { [key: string]: number };
+}
+
+class MetaQualityProfiles extends React.PureComponent<StateProps & OwnProps, State> {
+  mounted: boolean;
+  state: State = { deprecatedByKey: {} };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.loadDeprecatedRules();
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  loadDeprecatedRules() {
+    const requests = this.props.profiles.map(profile =>
+      this.loadDeprecatedRulesForProfile(profile.key)
+    );
+    Promise.all(requests).then(
+      responses => {
+        if (this.mounted) {
+          const deprecatedByKey: { [key: string]: number } = {};
+          responses.forEach((count, i) => {
+            const profileKey = this.props.profiles[i].key;
+            deprecatedByKey[profileKey] = count;
+          });
+          this.setState({ deprecatedByKey });
+        }
+      },
+      () => {}
+    );
+  }
+
+  loadDeprecatedRulesForProfile(profileKey: string) {
+    const data = {
+      activation: 'true',
+      organization: this.props.organization,
+      ps: 1,
+      qprofile: profileKey,
+      statuses: 'DEPRECATED'
+    };
+    return searchRules(data).then(r => r.total);
+  }
+
+  getDeprecatedRulesCount(profile: { key: string }) {
+    const count = this.state.deprecatedByKey[profile.key];
+    return count || 0;
+  }
+
+  renderProfile(profile: { key: string; language: string; name: string }) {
+    const languageFromStore = this.props.languages[profile.language];
+    const languageName = languageFromStore ? languageFromStore.name : profile.language;
+
+    const path = getQualityProfileUrl(profile.name, profile.language, this.props.organization);
+
+    const inner = (
+      <div className="text-ellipsis">
+        <span className="note spacer-right">{'(' + languageName + ')'}</span>
+        <Link to={path}>{profile.name}</Link>
+      </div>
+    );
+
+    const count = this.getDeprecatedRulesCount(profile);
+
+    if (count > 0) {
+      const tooltip = translateWithParameters('overview.deprecated_profile', count);
+      return (
+        <Tooltip key={profile.key} overlay={tooltip}>
+          <li className="overview-deprecated-rules">{inner}</li>
+        </Tooltip>
+      );
+    }
+
+    return <li key={profile.key}>{inner}</li>;
+  }
+
+  render() {
+    const { headerClassName, profiles } = this.props;
+
+    return (
+      <>
+        <h4 className={classNames('overview-meta-header', headerClassName)}>
+          {translate('overview.quality_profiles')}
+        </h4>
+
+        <ul className="overview-meta-list">
+          {profiles.map(profile => this.renderProfile(profile))}
+        </ul>
+      </>
+    );
+  }
+}
+
+const mapStateToProps = (state: any) => ({
+  languages: getLanguages(state)
+});
+
+export default connect<StateProps, {}, OwnProps>(mapStateToProps)(MetaQualityProfiles);
index 6465c861817a7f5ac0f7e0ddeb184f2ed997d703..335d0b487956bbcab44897c87f7d8f83072bd38a 100644 (file)
@@ -89,7 +89,7 @@ export default class MetaSize extends React.PureComponent<Props> {
     }
 
     return (
-      <div id="overview-size" className="overview-meta-card">
+      <div className="big-spacer-top" id="overview-size">
         {this.props.component.qualifier === 'APP' && this.renderProjects()}
         {this.renderLoC(ncloc)}
         {this.renderLoCDistribution()}
index e0caea4f6fbcf591bbb5e2a1a4e204e4a722e64a..209b8a978805b0d1853d52fc923f80d9ac1aac71 100644 (file)
@@ -99,7 +99,7 @@ export default class MetaTags extends React.PureComponent<Props, State> {
 
     if (this.canUpdateTags()) {
       return (
-        <div className="overview-meta-card overview-meta-tags" ref={card => (this.card = card)}>
+        <div className="big-spacer-top overview-meta-tags" ref={card => (this.card = card)}>
           <button
             className="button-link"
             onClick={this.handleClick}
@@ -120,7 +120,7 @@ export default class MetaTags extends React.PureComponent<Props, State> {
       );
     } else {
       return (
-        <div className="overview-meta-card overview-meta-tags">
+        <div className="big-spacer-top overview-meta-tags">
           <TagsList
             allowUpdate={false}
             className="note"
index f7c3da2dfca276c19353afb8756bea858706fc5d..1eea19831ab1704e9b27129d6f2a14ce935cf73b 100644 (file)
@@ -2,7 +2,7 @@
 
 exports[`should open the tag selector on click 1`] = `
 <div
-  className="overview-meta-card overview-meta-tags"
+  className="big-spacer-top overview-meta-tags"
 >
   <button
     className="button-link"
@@ -23,7 +23,7 @@ exports[`should open the tag selector on click 1`] = `
 
 exports[`should open the tag selector on click 2`] = `
 <div
-  className="overview-meta-card overview-meta-tags"
+  className="big-spacer-top overview-meta-tags"
 >
   <button
     className="button-link"
@@ -62,7 +62,7 @@ exports[`should open the tag selector on click 2`] = `
 
 exports[`should open the tag selector on click 3`] = `
 <div
-  className="overview-meta-card overview-meta-tags"
+  className="big-spacer-top overview-meta-tags"
 >
   <button
     className="button-link"
@@ -83,7 +83,7 @@ exports[`should open the tag selector on click 3`] = `
 
 exports[`should render with tags and admin rights 1`] = `
 <div
-  className="overview-meta-card overview-meta-tags"
+  className="big-spacer-top overview-meta-tags"
 >
   <button
     className="button-link"
@@ -104,7 +104,7 @@ exports[`should render with tags and admin rights 1`] = `
 
 exports[`should render without tags and admin rights 1`] = `
 <div
-  className="overview-meta-card overview-meta-tags"
+  className="big-spacer-top overview-meta-tags"
 >
   <TagsList
     allowUpdate={false}
index c79f253c2707d233e7701b0c55a7b194b9e42ba5..4563968dc1a5dfcc183491dd09f82adea1e8fdbc 100644 (file)
   transition: transform 0.5s ease, opacity 0.5s ease;
 }
 
+.overview-sidebar {
+  margin-top: calc(-1 * var(--pagePadding));
+  margin-bottom: calc(-1 * var(--pagePadding));
+  margin-left: var(--pagePadding);
+  padding-left: calc(var(--pagePadding) - 1px);
+  padding-top: var(--pagePadding);
+  padding-bottom: var(--pagePadding);
+  border-left: 1px solid var(--barBorderColor);
+}
+
 /*
  * Title
  */
 
 .overview-meta-card {
   min-width: 200px;
-  padding-bottom: 20px;
   box-sizing: border-box;
 }
 
+.overview-meta-card + .overview-meta-card {
+  margin-top: calc(2 * var(--gridSize));
+  padding-top: calc(2 * var(--gridSize) - 1px);
+  border-top: 1px solid var(--barBorderColor);
+}
+
 .overview-meta-description {
+  margin-top: calc(-0.5 * var(--gridSize));
   line-height: 1.5;
+  color: var(--secondFontColor);
 }
 
 .overview-meta-header {
+  margin-bottom: calc(0.5 * var(--gridSize));
   color: var(--baseFontColor);
 }
 
 }
 
 .overview-analysis + .overview-analysis {
-  margin-top: 8px;
-  padding-top: 8px;
-  border-top: 1px solid var(--barBorderColor);
+  margin-top: calc(2 * var(--gridSize));
 }
 
 .overview-analysis-graph {
index b64d7d91bc41a3e51cc3f0df4d306528bb8cf6c5..7a4fef57e0aaaa1bd0b0060a5cde35eb205b480f 100644 (file)
@@ -2287,6 +2287,19 @@ overview.last_analysis_x=last analysis {0}
 overview.started_on_x=Started on {0}
 overview.last_analysis_on_x=Last analysis on {0}
 overview.on_new_code=On New Code
+overview.about_this_project.APP=About This Application
+overview.about_this_project.TRK=About This Project
+overview.about_this_project.BRC=About This Sub-Project
+overview.about_this_project.DIR=About This Directory
+overview.project_activity.APP=Application Activity
+overview.project_activity.TRK=Project Activity
+overview.project_activity.BRC=Sub-Project Activity
+overview.project_activity.DIR=Directory Activity
+overview.external_links=External Links
+overview.project_key.APP=Application Key
+overview.project_key.TRK=Project Key
+overview.project_key.BRC=Sub-Project Key
+overview.project_key.DIR=Directory Key
 
 overview.metric.code_smells=Code Smells
 overview.metric.new_code_smells=New Code Smells