aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2017-09-06 14:33:21 +0200
committerStas Vilchik <stas.vilchik@sonarsource.com>2017-09-11 11:28:29 +0200
commit048982bb3d8d5b2c715f95bc7818c90314e72a14 (patch)
tree1ce0d8a4051104e8790e6d0d2ea59f8d77df0e25
parentb734fdfd93438affe6c77d1da40117afd99e02c4 (diff)
downloadsonarqube-048982bb3d8d5b2c715f95bc7818c90314e72a14.tar.gz
sonarqube-048982bb3d8d5b2c715f95bc7818c90314e72a14.zip
SONAR-4566 Identify old project on projects management page
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/App.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx20
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap118
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap18
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap26
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/utils.ts1
-rw-r--r--server/sonar-web/src/main/js/components/controls/DateInput.tsx38
-rw-r--r--server/sonar-web/src/main/js/components/controls/styles.css9
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties1
-rw-r--r--tests/src/test/java/org/sonarqube/pageobjects/ProjectsManagementPage.java2
14 files changed, 252 insertions, 24 deletions
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
index 60dd83ace5c..da819c20f4f 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
@@ -38,6 +38,7 @@ export interface Props {
}
interface State {
+ analyzedBefore?: string;
createProjectForm: boolean;
page: number;
projects: Project[];
@@ -78,6 +79,7 @@ export default class App extends React.PureComponent<Props, State> {
}
getFilters = () => ({
+ analyzedBefore: this.state.analyzedBefore,
organization: this.props.organization.key,
p: this.state.page !== 1 ? this.state.page : undefined,
ps: PAGE_SIZE,
@@ -147,6 +149,9 @@ export default class App extends React.PureComponent<Props, State> {
);
};
+ handleDateChanged = (analyzedBefore?: string) =>
+ this.setState({ ready: false, page: 1, analyzedBefore }, this.requestProjects);
+
onProjectSelected = (project: string) => {
const newSelection = uniq([...this.state.selection, project]);
this.setState({ selection: newSelection });
@@ -187,8 +192,10 @@ export default class App extends React.PureComponent<Props, State> {
/>
<Search
+ analyzedBefore={this.state.analyzedBefore}
onAllSelected={this.onAllSelected}
onAllDeselected={this.onAllDeselected}
+ onDateChanged={this.handleDateChanged}
onDeleteProjects={this.requestProjects}
onProvisionedChanged={this.onProvisionedChanged}
onQualifierChanged={this.onQualifierChanged}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
index 60f951dd32c..9ddf15b20a5 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
@@ -25,6 +25,7 @@ import Checkbox from '../../components/controls/Checkbox';
import QualifierIcon from '../../components/shared/QualifierIcon';
import { translate } from '../../helpers/l10n';
import { getComponentPermissionsUrl } from '../../helpers/urls';
+import DateTooltipFormatter from '../../components/intl/DateTooltipFormatter';
interface Props {
onApplyTemplateClick: (project: Project) => void;
@@ -61,14 +62,20 @@ export default class ProjectRow extends React.PureComponent<Props> {
</Link>
</td>
+ <td className="thin nowrap">
+ {project.visibility === Visibility.Private && <PrivateBadge />}
+ </td>
+
<td className="nowrap">
<span className="note">
{project.key}
</span>
</td>
- <td className="width-20">
- {project.visibility === Visibility.Private && <PrivateBadge />}
+ <td className="thin nowrap text-right">
+ {project.lastAnalysisDate
+ ? <DateTooltipFormatter date={project.lastAnalysisDate} />
+ : <span className="note">—</span>}
</td>
<td className="thin nowrap">
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx
index ff6264dce90..af1b365a771 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx
@@ -51,6 +51,16 @@ export default class Projects extends React.PureComponent<Props> {
<table
className={classNames('data', 'zebra', { 'new-loading': !this.props.ready })}
id="projects-management-page-projects">
+ <thead>
+ <tr>
+ <th />
+ <th>Name</th>
+ <th />
+ <th>Key</th>
+ <th className="thin nowrap text-right">Last Analysis</th>
+ <th />
+ </tr>
+ </thead>
<tbody>
{this.props.projects.map(project =>
<ProjectRow
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx
index 7b943df0908..eecfd7b4917 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx
@@ -29,10 +29,13 @@ import Checkbox from '../../components/controls/Checkbox';
import { translate } from '../../helpers/l10n';
import QualifierIcon from '../../components/shared/QualifierIcon';
import Tooltip from '../../components/controls/Tooltip';
+import DateInput from '../../components/controls/DateInput';
export interface Props {
+ analyzedBefore?: string;
onAllDeselected: () => void;
onAllSelected: () => void;
+ onDateChanged: (analyzedBefore?: string) => void;
onDeleteProjects: () => void;
onProvisionedChanged: (provisioned: boolean) => void;
onQualifierChanged: (qualifier: string) => void;
@@ -176,10 +179,24 @@ export default class Search extends React.PureComponent<Props, State> {
</td>
: null;
+ renderDateFilter = () => {
+ return (
+ <td className="thin nowrap text-middle">
+ <DateInput
+ inputClassName="input-medium"
+ name="analyzed-before"
+ onChange={this.props.onDateChanged}
+ placeholder={translate('analyzed_before')}
+ value={this.props.analyzedBefore}
+ />
+ </td>
+ );
+ };
+
render() {
const isSomethingSelected = this.props.projects.length > 0 && this.props.selection.length > 0;
return (
- <div className="panel panel-vertical bordered-bottom spacer-bottom">
+ <div className="big-spacer-bottom">
<table className="data">
<tbody>
<tr>
@@ -187,6 +204,7 @@ export default class Search extends React.PureComponent<Props, State> {
{this.props.ready ? this.renderCheckbox() : <i className="spinner" />}
</td>
{this.renderQualifierFilter()}
+ {this.renderDateFilter()}
{this.renderTypeFilter()}
<td className="text-middle">
<form onSubmit={this.onSubmit} className="search-box">
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx
index 7b7bb09d5ae..982ef5ce35a 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx
@@ -32,6 +32,9 @@ const project = {
it('renders', () => {
expect(shallowRender()).toMatchSnapshot();
+ expect(
+ shallowRender({ project: { ...project, lastAnalysisDate: '2017-04-08T00:00:00.000Z' } })
+ ).toMatchSnapshot();
});
it('checks project', () => {
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx
index 2c0ee5f2348..66e0439de3f 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx
@@ -53,6 +53,17 @@ it('does not render provisioned filter for portfolios', () => {
expect(wrapper.find('Checkbox[id="projects-provisioned"]').exists()).toBeFalsy();
});
+it('updates analysis date', () => {
+ const onDateChanged = jest.fn();
+ const wrapper = shallowRender({ onDateChanged });
+
+ wrapper.find('DateInput').prop<Function>('onChange')('2017-04-08T00:00:00.000Z');
+ expect(onDateChanged).toBeCalledWith('2017-04-08T00:00:00.000Z');
+
+ wrapper.find('DateInput').prop<Function>('onChange')(undefined);
+ expect(onDateChanged).toBeCalledWith(undefined);
+});
+
it('searches', () => {
const onSearch = jest.fn();
const wrapper = shallowRender({ onSearch });
@@ -94,6 +105,7 @@ function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
<Search
onAllDeselected={jest.fn()}
onAllSelected={jest.fn()}
+ onDateChanged={jest.fn()}
onDeleteProjects={jest.fn()}
onProvisionedChanged={jest.fn()}
onQualifierChanged={jest.fn()}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
index b306b2fe020..d0e3ac5b4f8 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
@@ -37,6 +37,11 @@ exports[`renders 1`] = `
</Link>
</td>
<td
+ className="thin nowrap"
+ >
+ <PrivateBadge />
+ </td>
+ <td
className="nowrap"
>
<span
@@ -46,11 +51,122 @@ exports[`renders 1`] = `
</span>
</td>
<td
- className="width-20"
+ className="thin nowrap text-right"
+ >
+ <span
+ className="note"
+ >
+ —
+ </span>
+ </td>
+ <td
+ className="thin nowrap"
+ >
+ <div
+ className="dropdown"
+ >
+ <button
+ className="dropdown-toggle"
+ data-toggle="dropdown"
+ >
+ actions
+
+ <i
+ className="icon-dropdown"
+ />
+ </button>
+ <ul
+ className="dropdown-menu dropdown-menu-right"
+ >
+ <li>
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project_roles",
+ "query": Object {
+ "id": "project",
+ },
+ }
+ }
+ >
+ edit_permissions
+ </Link>
+ </li>
+ <li>
+ <a
+ className="js-apply-template"
+ href="#"
+ onClick={[Function]}
+ >
+ projects_role.apply_template
+ </a>
+ </li>
+ </ul>
+ </div>
+ </td>
+</tr>
+`;
+
+exports[`renders 2`] = `
+<tr>
+ <td
+ className="thin"
+ >
+ <Checkbox
+ checked={true}
+ onCheck={[Function]}
+ thirdState={false}
+ />
+ </td>
+ <td
+ className="nowrap"
+ >
+ <Link
+ className="link-with-icon"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "id": "project",
+ },
+ }
+ }
+ >
+ <QualifierIcon
+ qualifier="TRK"
+ />
+
+ <span>
+ Project
+ </span>
+ </Link>
+ </td>
+ <td
+ className="thin nowrap"
>
<PrivateBadge />
</td>
<td
+ className="nowrap"
+ >
+ <span
+ className="note"
+ >
+ project
+ </span>
+ </td>
+ <td
+ className="thin nowrap text-right"
+ >
+ <DateTooltipFormatter
+ date="2017-04-08T00:00:00.000Z"
+ />
+ </td>
+ <td
className="thin nowrap"
>
<div
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap
index 14bb03d1ec6..2c60880eb28 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap
@@ -5,6 +5,24 @@ exports[`renders list of projects 1`] = `
className="data zebra new-loading"
id="projects-management-page-projects"
>
+ <thead>
+ <tr>
+ <th />
+ <th>
+ Name
+ </th>
+ <th />
+ <th>
+ Key
+ </th>
+ <th
+ className="thin nowrap text-right"
+ >
+ Last Analysis
+ </th>
+ <th />
+ </tr>
+ </thead>
<tbody>
<ProjectRow
onApplyTemplateClick={[Function]}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap
index c8c1006646c..89a31381713 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap
@@ -29,7 +29,7 @@ exports[`deletes projects 1`] = `
exports[`render qualifiers filter 1`] = `
<div
- className="panel panel-vertical bordered-bottom spacer-bottom"
+ className="big-spacer-bottom"
>
<table
className="data"
@@ -115,6 +115,17 @@ exports[`render qualifiers filter 1`] = `
<td
className="thin nowrap text-middle"
>
+ <DateInput
+ format="yy-mm-dd"
+ inputClassName="input-medium"
+ name="analyzed-before"
+ onChange={[Function]}
+ placeholder="last_analysis_before"
+ />
+ </td>
+ <td
+ className="thin nowrap text-middle"
+ >
<Checkbox
checked={false}
className="link-checkbox-control"
@@ -185,7 +196,7 @@ exports[`render qualifiers filter 1`] = `
exports[`renders 1`] = `
<div
- className="panel panel-vertical bordered-bottom spacer-bottom"
+ className="big-spacer-bottom"
>
<table
className="data"
@@ -205,6 +216,17 @@ exports[`renders 1`] = `
<td
className="thin nowrap text-middle"
>
+ <DateInput
+ format="yy-mm-dd"
+ inputClassName="input-medium"
+ name="analyzed-before"
+ onChange={[Function]}
+ placeholder="last_analysis_before"
+ />
+ </td>
+ <td
+ className="thin nowrap text-middle"
+ >
<Checkbox
checked={false}
className="link-checkbox-control"
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/utils.ts b/server/sonar-web/src/main/js/apps/projectsManagement/utils.ts
index c78021e1fce..aa549df26c1 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/utils.ts
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/utils.ts
@@ -23,6 +23,7 @@ export const QUALIFIERS_ORDER = ['TRK', 'VW', 'APP'];
export interface Project {
key: string;
+ lastAnalysisDate?: string;
name: string;
qualifier: string;
visibility: Visibility;
diff --git a/server/sonar-web/src/main/js/components/controls/DateInput.tsx b/server/sonar-web/src/main/js/components/controls/DateInput.tsx
index 33f6c7d9cf8..6a226b1b398 100644
--- a/server/sonar-web/src/main/js/components/controls/DateInput.tsx
+++ b/server/sonar-web/src/main/js/components/controls/DateInput.tsx
@@ -22,21 +22,22 @@ import * as React from 'react';
import * as classNames from 'classnames';
import { pick } from 'lodash';
import './styles.css';
+import CloseIcon from '../icons-components/CloseIcon';
interface Props {
className?: string;
- value?: string;
format?: string;
+ inputClassName?: string;
name: string;
+ onChange: (value?: string) => void;
placeholder: string;
- onChange: (value: string) => void;
+ value?: string;
}
export default class DateInput extends React.PureComponent<Props> {
input: HTMLInputElement;
static defaultProps = {
- value: '',
format: 'yy-mm-dd'
};
@@ -44,23 +45,23 @@ export default class DateInput extends React.PureComponent<Props> {
this.attachDatePicker();
}
- componentWillReceiveProps(nextProps: Props) {
- if (nextProps.value != null && this.input) {
- this.input.value = nextProps.value;
- }
- }
-
- handleChange() {
+ handleChange = () => {
const { value } = this.input;
this.props.onChange(value);
- }
+ };
+
+ handleResetClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.props.onChange(undefined);
+ };
attachDatePicker() {
const opts = {
dateFormat: this.props.format,
changeMonth: true,
changeYear: true,
- onSelect: this.handleChange.bind(this)
+ onSelect: this.handleChange
};
if ($.fn && ($.fn as any).datepicker && this.input) {
@@ -74,11 +75,12 @@ export default class DateInput extends React.PureComponent<Props> {
return (
<span className={classNames('date-input-control', this.props.className)}>
<input
- className="date-input-control-input"
- ref={node => (this.input = node as HTMLInputElement)}
- type="text"
- defaultValue={this.props.value}
+ className={classNames('date-input-control-input', this.props.inputClassName)}
+ onChange={this.handleChange}
readOnly={true}
+ ref={node => (this.input = node!)}
+ type="text"
+ value={this.props.value || ''}
{...inputProps}
/>
<span className="date-input-control-icon">
@@ -86,6 +88,10 @@ export default class DateInput extends React.PureComponent<Props> {
<path d="M5.5 6h2v2h-2V6zm3 0h2v2h-2V6zm3 0h2v2h-2V6zm-9 6h2v2h-2v-2zm3 0h2v2h-2v-2zm3 0h2v2h-2v-2zm-3-3h2v2h-2V9zm3 0h2v2h-2V9zm3 0h2v2h-2V9zm-9 0h2v2h-2V9zm11-9v1h-2V0h-7v1h-2V0h-2v16h15V0h-2zm1 15h-13V4h13v11z" />
</svg>
</span>
+ {this.props.value != undefined &&
+ <a className="date-input-control-reset" href="#" onClick={this.handleResetClick}>
+ <CloseIcon className="" />
+ </a>}
</span>
);
}
diff --git a/server/sonar-web/src/main/js/components/controls/styles.css b/server/sonar-web/src/main/js/components/controls/styles.css
index 3b4e315805b..d54cab0df84 100644
--- a/server/sonar-web/src/main/js/components/controls/styles.css
+++ b/server/sonar-web/src/main/js/components/controls/styles.css
@@ -5,7 +5,7 @@
}
.date-input-control-input {
- width: 105px;
+ width: 130px;
padding-left: 24px !important;
cursor: pointer;
}
@@ -25,6 +25,13 @@
fill: #4b9fd5;
}
+.date-input-control-reset {
+ position: absolute;
+ top: 4px;
+ right: 4px;
+ border: none;
+}
+
.boolean-toggle {
display: inline-block;
vertical-align: middle;
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 6c022ba3d1d..0345b483fb7 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -225,6 +225,7 @@ added_since_previous_version_detailed=Added since previous version ({0})
added_since_version=Added since version {0}
all_violations=All violations
all_issues=All issues
+analyzed_before=Analyzed before
and_worse=and worse
are_you_sure=Are you sure?
assigned_to=Assigned to
diff --git a/tests/src/test/java/org/sonarqube/pageobjects/ProjectsManagementPage.java b/tests/src/test/java/org/sonarqube/pageobjects/ProjectsManagementPage.java
index c6dd13c2cbd..c2031a8596b 100644
--- a/tests/src/test/java/org/sonarqube/pageobjects/ProjectsManagementPage.java
+++ b/tests/src/test/java/org/sonarqube/pageobjects/ProjectsManagementPage.java
@@ -32,7 +32,7 @@ public class ProjectsManagementPage {
}
public ProjectsManagementPage shouldHaveProjectsCount(int count) {
- $$("#projects-management-page-projects tr").shouldHaveSize(count);
+ $$("#projects-management-page-projects tbody tr").shouldHaveSize(count);
return this;
}