Kaynağa Gözat

SONAR-7674 Add Activity Stream interface (#1459)

tags/6.3-RC1
Stas Vilchik 7 yıl önce
ebeveyn
işleme
2a91ab92da
67 değiştirilmiş dosya ile 3579 ekleme ve 844 silme
  1. 2
    0
      it/it-tests/src/test/java/it/Category4Suite.java
  2. 1
    1
      it/it-tests/src/test/java/it/projectAdministration/ProjectAdministrationTest.java
  3. 1
    11
      it/it-tests/src/test/java/it/projectEvent/EventTest.java
  4. 35
    31
      it/it-tests/src/test/java/it/projectEvent/ProjectActivityPageTest.java
  5. 6
    3
      it/it-tests/src/test/java/it/qualityGate/QualityGateUiTest.java
  6. 6
    6
      it/it-tests/src/test/java/pageobjects/Navigation.java
  7. 24
    11
      it/it-tests/src/test/java/pageobjects/ProjectActivityPage.java
  8. 104
    0
      it/it-tests/src/test/java/pageobjects/ProjectAnalysisItem.java
  9. 0
    114
      it/it-tests/src/test/resources/projectEvent/EventTest/create_delete_standard_event.html
  10. 2
    1
      server/sonar-web/package.json
  11. 99
    0
      server/sonar-web/src/main/js/api/projectActivity.js
  12. 12
    16
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js
  13. 1
    1
      server/sonar-web/src/main/js/app/components/nav/global/SearchView.js
  14. 2
    0
      server/sonar-web/src/main/js/app/utils/startReactApp.js
  15. 32
    0
      server/sonar-web/src/main/js/apps/overview/actions.js
  16. 118
    0
      server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js
  17. 28
    31
      server/sonar-web/src/main/js/apps/overview/events/Analysis.js
  18. 0
    149
      server/sonar-web/src/main/js/apps/overview/events/EventsList.js
  19. 4
    4
      server/sonar-web/src/main/js/apps/overview/meta/Meta.js
  20. 10
    0
      server/sonar-web/src/main/js/apps/overview/styles.css
  21. 87
    0
      server/sonar-web/src/main/js/apps/projectActivity/actions.js
  22. 33
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/ChangeIcon.js
  23. 33
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/DeleteIcon.js
  24. 7
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/Event.css
  25. 118
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/Event.js
  26. 23
    27
      server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.js
  27. 56
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/Events.js
  28. 87
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js
  29. 88
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js
  30. 91
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
  31. 61
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFooter.js
  32. 63
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js
  33. 34
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddCustomEventForm.js
  34. 146
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js
  35. 34
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddVersionForm.js
  36. 36
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeCustomEventForm.js
  37. 135
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.js
  38. 36
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeVersionForm.js
  39. 136
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js
  40. 37
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveCustomEventForm.js
  41. 113
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveEventForm.js
  42. 37
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveVersionForm.js
  43. 121
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
  44. 27
    0
      server/sonar-web/src/main/js/apps/projectActivity/routes.js
  45. 21
    29
      server/sonar-web/src/main/js/components/ui/FormattedDate.js
  46. 93
    0
      server/sonar-web/src/main/js/store/projectActivity/__tests__/__snapshots__/analyses-test.js.snap
  47. 55
    0
      server/sonar-web/src/main/js/store/projectActivity/__tests__/__snapshots__/analysesByProject-test.js.snap
  48. 75
    0
      server/sonar-web/src/main/js/store/projectActivity/__tests__/__snapshots__/duck-test.js.snap
  49. 71
    0
      server/sonar-web/src/main/js/store/projectActivity/__tests__/__snapshots__/events-test.js.snap
  50. 21
    0
      server/sonar-web/src/main/js/store/projectActivity/__tests__/__snapshots__/paging-test.js.snap
  51. 90
    0
      server/sonar-web/src/main/js/store/projectActivity/__tests__/analyses-test.js
  52. 80
    0
      server/sonar-web/src/main/js/store/projectActivity/__tests__/analysesByProject-test.js
  53. 100
    0
      server/sonar-web/src/main/js/store/projectActivity/__tests__/duck-test.js
  54. 90
    0
      server/sonar-web/src/main/js/store/projectActivity/__tests__/events-test.js
  55. 49
    0
      server/sonar-web/src/main/js/store/projectActivity/__tests__/paging-test.js
  56. 89
    0
      server/sonar-web/src/main/js/store/projectActivity/analyses.js
  57. 53
    0
      server/sonar-web/src/main/js/store/projectActivity/analysesByProject.js
  58. 148
    0
      server/sonar-web/src/main/js/store/projectActivity/duck.js
  59. 79
    0
      server/sonar-web/src/main/js/store/projectActivity/events.js
  60. 34
    0
      server/sonar-web/src/main/js/store/projectActivity/paging.js
  61. 1
    1
      server/sonar-web/src/main/js/store/rootActions.js
  62. 6
    0
      server/sonar-web/src/main/js/store/rootReducer.js
  63. 4
    0
      server/sonar-web/src/main/js/store/utils/configureStore.js
  64. 18
    10
      server/sonar-web/src/main/less/components/modals.less
  65. 17
    1
      server/sonar-web/src/main/less/init/forms.less
  66. 240
    391
      server/sonar-web/yarn.lock
  67. 19
    6
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 2
- 0
it/it-tests/src/test/java/it/Category4Suite.java Dosyayı Görüntüle

@@ -30,6 +30,7 @@ import it.duplication.CrossProjectDuplicationsTest;
import it.duplication.DuplicationsTest;
import it.duplication.NewDuplicationsTest;
import it.projectEvent.EventTest;
import it.projectEvent.ProjectActivityPageTest;
import it.projectSearch.SearchProjectsTest;
import it.qualityProfile.QualityProfilesPageTest;
import it.serverSystem.HttpHeadersTest;
@@ -80,6 +81,7 @@ import static util.ItUtils.xooPlugin;
PurgeTest.class,
// project event
EventTest.class,
ProjectActivityPageTest.class,
// project search
SearchProjectsTest.class,
// http

+ 1
- 1
it/it-tests/src/test/java/it/projectAdministration/ProjectAdministrationTest.java Dosyayı Görüntüle

@@ -128,7 +128,7 @@ public class ProjectAdministrationTest {

// SONAR-4203
@Test
@Ignore("history page is not available yet")
@Ignore("refactor with wsClient")
public void delete_version_of_multimodule_project() {
GregorianCalendar today = new GregorianCalendar();
SonarScanner build = SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))

+ 1
- 11
it/it-tests/src/test/java/it/projectEvent/EventTest.java Dosyayı Görüntüle

@@ -37,9 +37,8 @@ import util.ItUtils;

import static org.assertj.core.api.Assertions.assertThat;
import static util.ItUtils.projectDir;
import static util.selenium.Selenese.runSelenese;

@Ignore("history page is not available yet")
@Ignore("refactor using wsClient")
public class EventTest {

@ClassRule
@@ -50,7 +49,6 @@ public class EventTest {
orchestrator.resetData();
}

@Ignore("UUID column of Events is not handled with Ruby pages and WS")
@Test
public void old_ws_events_does_not_allow_creating_events_on_modules() {
SonarScanner sampleProject = SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"));
@@ -72,14 +70,6 @@ public class EventTest {
.setParam("category", "Foo");
}

@Ignore("UUID column of Events is not handled with Ruby pages and WS")
@Test
public void delete_standard_event() {
executeAnalysis();

runSelenese(orchestrator, "/projectEvent/EventTest/create_delete_standard_event.html");
}

/**
* SONAR-3308
*/

it/it-tests/src/test/java/it/projectAdministration/ProjectHistoryPageTest.java → it/it-tests/src/test/java/it/projectEvent/ProjectActivityPageTest.java Dosyayı Görüntüle

@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package it.projectAdministration;
package it.projectEvent;

import com.sonar.orchestrator.Orchestrator;
import com.sonar.orchestrator.build.SonarScanner;
@@ -28,15 +28,12 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import pageobjects.Navigation;
import pageobjects.ProjectHistoryPage;
import pageobjects.ProjectHistorySnapshotItem;
import pageobjects.ProjectActivityPage;
import pageobjects.ProjectAnalysisItem;

import static com.codeborne.selenide.Condition.exist;
import static com.codeborne.selenide.Condition.text;
import static com.codeborne.selenide.Selenide.confirm;
import static util.ItUtils.projectDir;

public class ProjectHistoryPageTest {
public class ProjectActivityPageTest {

@ClassRule
public static Orchestrator ORCHESTRATOR = Category1Suite.ORCHESTRATOR;
@@ -45,47 +42,54 @@ public class ProjectHistoryPageTest {
public Navigation nav = Navigation.get(ORCHESTRATOR);

@Before
public void setUp() {
public void setUp() throws Exception {
ORCHESTRATOR.resetData();
analyzeProject("shared/xoo-history-v1", "2014-10-19");
analyzeProject("shared/xoo-history-v2", "2014-11-13");
}

@Test
public void should_list_snapshots() {
ProjectHistoryPage page = openPage();

page.getSnapshots().shouldHaveSize(2);
analyzeProject("shared/xoo-history-v1", "2014-10-19");
analyzeProject("shared/xoo-history-v2", "2014-11-13");

List<ProjectHistorySnapshotItem> snapshots = page.getSnapshotsAsItems();
ProjectActivityPage page = openPage();
page.getAnalyses().shouldHaveSize(2);

snapshots.get(0).getVersionText().shouldBe(text("1.0-SNAPSHOT"));
snapshots.get(0).getDeleteButton().shouldNot(exist);
List<ProjectAnalysisItem> analyses = page.getAnalysesAsItems();
analyses.get(0)
.shouldHaveEventWithText("1.0-SNAPSHOT")
.shouldNotHaveDeleteButton();

snapshots.get(1).getVersionText().shouldBe(text("0.9-SNAPSHOT"));
snapshots.get(1).getDeleteButton().should(exist);
analyses.get(1)
.shouldHaveEventWithText("0.9-SNAPSHOT")
.shouldHaveDeleteButton();
}

@Test
public void should_delete_snapshot() {
ProjectHistoryPage page = openPage();
page.getSnapshots().shouldHaveSize(2);
page.getSnapshotsAsItems().get(1).clickDelete();
confirm();
public void add_change_delete_custom_event() {
analyzeProject();
openPage().getLastAnalysis()
.addCustomEvent("foo")
.changeLastEvent("bar")
.deleteLastEvent();
}

page.checkAlertDisplayed();
page.getSnapshots().shouldHaveSize(1);
@Test
public void delete_analysis() {
analyzeProject();
analyzeProject();
openPage().getFirstAnalysis().delete();
}

private ProjectHistoryPage openPage() {
private ProjectActivityPage openPage() {
nav.logIn().submitCredentials("admin", "admin");
return nav.openProjectHistory("sample");
return nav.openProjectActivity("sample");
}

private static void analyzeProject() {
ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample")));
}

private static void analyzeProject(String path, String date) {
ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir(path))
.setProperties("sonar.projectDate", date));
ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir(path)).setProperties("sonar.projectDate", date));
}
}

+ 6
- 3
it/it-tests/src/test/java/it/qualityGate/QualityGateUiTest.java Dosyayı Görüntüle

@@ -27,13 +27,14 @@ import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.sonar.wsclient.qualitygate.NewCondition;
import org.sonar.wsclient.qualitygate.QualityGate;
import org.sonar.wsclient.qualitygate.QualityGateClient;
import org.sonar.wsclient.qualitygate.QualityGateCondition;
import org.sonar.wsclient.qualitygate.UpdateCondition;
import pageobjects.Navigation;
import pageobjects.ProjectActivityPage;
import util.ItUtils;

import static util.ItUtils.projectDir;
@@ -70,7 +71,6 @@ public class QualityGateUiTest {
* SONAR-3326
*/
@Test
@Ignore("history page is not available yet")
public void display_alerts_correctly_in_history_page() {
QualityGateClient qgClient = qgClient();
QualityGate qGate = qgClient.create("AlertsForHistory");
@@ -83,7 +83,10 @@ public class QualityGateUiTest {
qgClient.updateCondition(UpdateCondition.create(lowThresholds.id()).metricKey("lines").operator("GT").warningThreshold("5000").errorThreshold("5000"));
scanSampleWithDate("2012-01-02");

runSelenese(orchestrator, "/qualityGate/QualityGateUiTest/should-display-alerts-correctly-history-page.html");
ProjectActivityPage page = Navigation.get(orchestrator).openProjectActivity("sample");
page
.assertFirstAnalysisOfTheDayHasText("2012-01-02", "Green (was Orange)")
.assertFirstAnalysisOfTheDayHasText("2012-01-01", "Orange");

qgClient.unsetDefault();
qgClient.destroy(qGate.id());

+ 6
- 6
it/it-tests/src/test/java/pageobjects/Navigation.java Dosyayı Görüntüle

@@ -79,18 +79,18 @@ public class Navigation extends ExternalResource {
return open(url, ProjectQualityGatePage.class);
}

public ProjectHistoryPage openProjectHistory(String projectKey) {
// TODO encode projectKey
String url = "/project/history?id=" + projectKey;
return open(url, ProjectHistoryPage.class);
}

public ProjectKeyPage openProjectKey(String projectKey) {
// TODO encode projectKey
String url = "/project/key?id=" + projectKey;
return open(url, ProjectKeyPage.class);
}

public ProjectActivityPage openProjectActivity(String projectKey) {
// TODO encode projectKey
String url = "/project/activity?id=" + projectKey;
return open(url, ProjectActivityPage.class);
}

public BackgroundTasksPage openBackgroundTasksPage() {
return open("/background_tasks", BackgroundTasksPage.class);
}

it/it-tests/src/test/java/pageobjects/ProjectHistoryPage.java → it/it-tests/src/test/java/pageobjects/ProjectActivityPage.java Dosyayı Görüntüle

@@ -19,32 +19,45 @@
*/
package pageobjects;

import com.codeborne.selenide.Condition;
import com.codeborne.selenide.ElementsCollection;
import java.util.List;
import java.util.stream.Collectors;

import static com.codeborne.selenide.Condition.exist;
import static com.codeborne.selenide.Condition.hasText;
import static com.codeborne.selenide.Selenide.$;
import static com.codeborne.selenide.Selenide.$$;

public class ProjectHistoryPage {
public class ProjectActivityPage {

public ProjectHistoryPage() {
$("#project-history").should(exist);
public ProjectActivityPage() {
$("#project-activity").should(Condition.exist);
}

public ElementsCollection getSnapshots() {
return $$("tr.snapshot");
public ElementsCollection getAnalyses() {
return $$(".project-activity-analysis");
}

public List<ProjectHistorySnapshotItem> getSnapshotsAsItems() {
return getSnapshots()
public List<ProjectAnalysisItem> getAnalysesAsItems() {
return getAnalyses()
.stream()
.map(ProjectHistorySnapshotItem::new)
.map(ProjectAnalysisItem::new)
.collect(Collectors.toList());
}

public void checkAlertDisplayed() {
$("#info:not(.hidden)").should(exist);
public ProjectAnalysisItem getLastAnalysis() {
return new ProjectAnalysisItem($(".project-activity-analysis"));
}

public ProjectAnalysisItem getFirstAnalysis() {
return new ProjectAnalysisItem($$(".project-activity-analysis").last());
}

public ProjectActivityPage assertFirstAnalysisOfTheDayHasText(String day, String text) {
$("#project-activity")
.find(".project-activity-day[data-day=\"" + day + "\"]")
.find(".project-activity-analysis")
.should(hasText(text));
return this;
}
}

+ 104
- 0
it/it-tests/src/test/java/pageobjects/ProjectAnalysisItem.java Dosyayı Görüntüle

@@ -0,0 +1,104 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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.
*/
package pageobjects;

import com.codeborne.selenide.Condition;
import com.codeborne.selenide.SelenideElement;

import static com.codeborne.selenide.Condition.text;
import static com.codeborne.selenide.Condition.visible;
import static com.codeborne.selenide.Selenide.$;

public class ProjectAnalysisItem {

private final SelenideElement elt;

public ProjectAnalysisItem(SelenideElement elt) {
this.elt = elt;
}

public ProjectAnalysisItem shouldHaveEventWithText(String text) {
elt.find(".project-activity-events").shouldHave(Condition.text(text));
return this;
}

public ProjectAnalysisItem shouldHaveDeleteButton() {
elt.find(".js-delete-analysis").shouldBe(visible);
return this;
}

public ProjectAnalysisItem shouldNotHaveDeleteButton() {
elt.find(".js-delete-analysis").shouldNotBe(visible);
return this;
}

public void delete() {
elt.find(".js-delete-analysis").click();

SelenideElement modal = $(".modal");
modal.shouldBe(visible);
modal.find("button[type=\"submit\"]").click();

elt.shouldNotBe(visible);
}

public ProjectAnalysisItem addCustomEvent(String name) {
elt.find(".js-create").click();
elt.find(".js-add-event").click();

SelenideElement modal = $(".modal");
modal.shouldBe(visible);
modal.find("input").setValue(name);
modal.find("button[type=\"submit\"]").click();

elt.find(".project-activity-event:last-child").shouldHave(text(name));

return this;
}

public ProjectAnalysisItem changeLastEvent(String newName) {
SelenideElement lastEvent = elt.find(".project-activity-event:last-child");
lastEvent.find(".js-change-event").click();

SelenideElement modal = $(".modal");
modal.shouldBe(visible);
modal.find("input").setValue(newName);
modal.find("button[type=\"submit\"]").click();

lastEvent.shouldHave(text(newName));

return this;
}

public ProjectAnalysisItem deleteLastEvent() {
int eventsCount = elt.findAll(".project-activity-event").size();

SelenideElement lastEvent = elt.find(".project-activity-event:last-child");
lastEvent.find(".js-delete-event").click();

SelenideElement modal = $(".modal");
modal.shouldBe(visible);
modal.find("button[type=\"submit\"]").click();

elt.findAll(".project-activity-event").shouldHaveSize(eventsCount - 1);

return this;
}
}

+ 0
- 114
it/it-tests/src/test/resources/projectEvent/EventTest/create_delete_standard_event.html Dosyayı Görüntüle

@@ -1,114 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head profile="http://selenium-ide.openqa.org/profiles/test-case">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>create_delete_standard_event</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr>
<td rowspan="1" colspan="3">create_delete_standard_event</td>
</tr>
</thead>
<tbody>
<tr>
<td>open</td>
<td>/sessions/logout</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td>/sessions/login</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>login</td>
<td>admin</td>
</tr>
<tr>
<td>type</td>
<td>password</td>
<td>admin</td>
</tr>
<tr>
<td>clickAndWait</td>
<td>commit</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=.js-user-authenticated</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td>/project/history?id=sample</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>link=Create</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>create_event_name_0</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>create_event_name_0</td>
<td>EventToBeDeleted</td>
</tr>
<tr>
<td>clickAndWait</td>
<td>create_save_event_0</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>infomsg</td>
<td></td>
</tr>
<tr>
<td>waitForText</td>
<td>infomsg</td>
<td>Event 'EventToBeDeleted' was created.</td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//td[text()='EventToBeDeleted']</td>
<td></td>
</tr>
<tr>
<td>clickAndWait</td>
<td>link=Remove</td>
<td></td>
</tr>
<tr>
<td>assertConfirmation</td>
<td>Are you sure you want to remove 'EventToBeDeleted' from this snapshot?</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>infomsg</td>
<td></td>
</tr>
<tr>
<td>waitForText</td>
<td>infomsg</td>
<td>Event 'EventToBeDeleted' was deleted.</td>
</tr>
<tr>
<td>assertElementNotPresent</td>
<td>//td[text()='EventToBeDeleted']</td>
<td></td>
</tr>
</tbody>
</table>
</body>
</html>

+ 2
- 1
server/sonar-web/package.json Dosyayı Görüntüle

@@ -66,6 +66,7 @@
"react-dev-utils": "0.2.1",
"react-dom": "15.3.2",
"react-helmet": "3.1.0",
"react-modal": "^1.6.4",
"react-redux": "4.4.1",
"react-router": "2.8.1",
"react-router-redux": "4.0.2",
@@ -93,7 +94,7 @@
"test": "node scripts/test.js",
"coverage": "npm test -- --coverage",
"lint": "eslint src/main/js",
"typecheck": "flow check src/main/js"
"typecheck": "flow src/main/js"
},
"engines": {
"node": ">=4"

+ 99
- 0
server/sonar-web/src/main/js/api/projectActivity.js Dosyayı Görüntüle

@@ -0,0 +1,99 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { getJSON, postJSON, post } from '../helpers/request';

type GetProjectActivityResponse = {
analyses: Array<Object>,
paging: {
total: number,
pageIndex: number,
pageSize: number
}
};

type GetProjectActivityOptions = {
category?: ?string,
pageIndex?: ?number,
pageSize?: ?number
};

export const getProjectActivity = (
project: string,
options?: GetProjectActivityOptions
): Promise<GetProjectActivityResponse> => {
const data: Object = { project };
if (options) {
if (options.category) {
data.category = options.category;
}
if (options.pageIndex) {
data.p = options.pageIndex;
}
if (options.pageSize) {
data.ps = options.pageSize;
}
}

return getJSON('/api/project_analyses/search', data);
};

type CreateEventResponse = {
analysis: string,
key: string,
name: string,
category: string,
description?: string
};

export const createEvent = (
analysis: string,
name: string,
category?: string,
description?: string
): Promise<CreateEventResponse> => {
const data: Object = { analysis, name };
if (category) {
data.category = category;
}
if (description) {
data.description = description;
}
return postJSON('/api/project_analyses/create_event', data).then(r => r.event);
};

export const deleteEvent = (event: string): Promise<*> => (
post('/api/project_analyses/delete_event', { event })
);

export const changeEvent = (event: string, name: ?string, description: ?string): Promise<CreateEventResponse> => {
const data: Object = { event };
if (name) {
data.name = name;
}
if (description) {
data.description = description;
}
return postJSON('/api/project_analyses/update_event', data).then(r => r.event);
};

export const deleteAnalysis = (analysis: string): Promise<*> => (
post('/api/project_analyses/delete', { analysis })
);

+ 12
- 16
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js Dosyayı Görüntüle

@@ -95,6 +95,17 @@ export default class ComponentNavMenu extends React.Component {
);
}

renderActivityLink () {
return (
<li>
<Link to={{ pathname: '/project/activity', query: { id: this.props.component.key } }}
activeClassName="active">
{translate('project_activity.page')}
</Link>
</li>
);
}

renderComponentIssuesLink () {
return (
<li>
@@ -138,7 +149,6 @@ export default class ComponentNavMenu extends React.Component {
{this.renderCustomMeasuresLink()}
{this.renderLinksLink()}
{this.renderPermissionsLink()}
{this.renderHistoryLink()}
{this.renderBackgroundTasksLink()}
{this.renderUpdateKeyLink()}
{this.renderExtensions()}
@@ -238,21 +248,6 @@ export default class ComponentNavMenu extends React.Component {
);
}

renderHistoryLink () {
if (!this.props.conf.showHistory) {
return null;
}
const url = `/project/history?id=${encodeURIComponent(this.props.component.key)}`;
// return this.renderLink(url, translate('project_history.page'), '/project/history');
return (
<li key={url}>
<span className="text-muted" style={{ cursor: 'not-allowed', textDecoration: 'line-through' }}>
{translate('project_history.page')}
</span>
</li>
);
}

renderBackgroundTasksLink () {
if (!this.props.conf.showBackgroundTasks) {
return null;
@@ -336,6 +331,7 @@ export default class ComponentNavMenu extends React.Component {
{this.renderComponentIssuesLink()}
{this.renderComponentMeasuresLink()}
{this.renderCodeLink()}
{this.renderActivityLink()}
{this.renderTools()}
{this.renderAdministration()}
</ul>

+ 1
- 1
server/sonar-web/src/main/js/app/components/nav/global/SearchView.js Dosyayı Görüntüle

@@ -98,7 +98,7 @@ export default Marionette.LayoutView.extend({
},

events: {
'submit': 'onSubmit',
'submit': 'handleSubmit',
'keydown .js-search-input': 'onKeyDown',
'keyup .js-search-input': 'onKeyUp'
},

+ 2
- 0
server/sonar-web/src/main/js/app/utils/startReactApp.js Dosyayı Görüntüle

@@ -45,6 +45,7 @@ import issuesRoutes from '../../apps/issues/routes';
import metricsRoutes from '../../apps/metrics/routes';
import overviewRoutes from '../../apps/overview/routes';
import permissionTemplatesRoutes from '../../apps/permission-templates/routes';
import projectActivityRoutes from '../../apps/projectActivity/routes';
import projectAdminRoutes from '../../apps/project-admin/routes';
import projectsRoutes from '../../apps/projects/routes';
import projectsAdminRoutes from '../../apps/projects-admin/routes';
@@ -109,6 +110,7 @@ const startReactApp = () => {
<Route path="custom_measures">{customMeasuresRoutes}</Route>
<Route path="dashboard">{overviewRoutes}</Route>
<Route path="project">
<Route path="activity">{projectActivityRoutes}</Route>
<Route path="background_tasks">{backgroundTasksRoutes}</Route>
<Route path="settings">{settingsRoutes}</Route>
{projectAdminRoutes}

+ 32
- 0
server/sonar-web/src/main/js/apps/overview/actions.js Dosyayı Görüntüle

@@ -0,0 +1,32 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 * as api from '../../api/projectActivity';
import { receiveProjectActivity } from '../../store/projectActivity/duck';
import { onFail } from '../../store/rootActions';

const PAGE_SIZE = 5;

export const fetchRecentProjectActivity = (project: string) => (dispatch: Function) => (
api.getProjectActivity(project, { pageSize: PAGE_SIZE }).then(
({ analyses, paging }) => dispatch(receiveProjectActivity(project, analyses, paging)),
onFail(dispatch)
)
);

+ 118
- 0
server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js Dosyayı Görüntüle

@@ -0,0 +1,118 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { Link } from 'react-router';
import { connect } from 'react-redux';
import Analysis from './Analysis';
import { translate } from '../../../helpers/l10n';
import { fetchRecentProjectActivity } from '../actions';
import { getProjectActivity } from '../../../store/rootReducer';
import { getAnalyses } from '../../../store/projectActivity/duck';

type Props = {
analyses?: Array<*>,
project: string;
fetchRecentProjectActivity: (project: string) => Promise<*>;
}

class AnalysesList extends React.Component {
mounted: boolean;
props: Props;

state = {
loading: true
};

componentDidMount () {
this.mounted = true;
this.fetchData();
}

componentDidUpdate (prevProps: Props) {
if (prevProps.project !== this.props.project) {
this.fetchData();
}
}

componentWillUnmount () {
this.mounted = false;
}

fetchData () {
this.setState({ loading: true });
this.props.fetchRecentProjectActivity(this.props.project).then(() => {
if (this.mounted) {
this.setState({ loading: false });
}
});
}

renderList (analyses) {
if (!analyses.length) {
return (
<p className="spacer-top note">
{translate('no_results')}
</p>
);
}

return (
<ul className="spacer-top">
{analyses.map(analysis => (
<Analysis key={analysis.key} analysis={analysis}/>
))}
</ul>
);
}

render () {
const { analyses } = this.props;
const { loading } = this.state;

if (loading || !analyses) {
return null;
}

return (
<div className="overview-meta-card">
<h4 className="overview-meta-header">
{translate('project_activity.page')}
</h4>

{this.renderList(analyses)}

<div className="spacer-top small">
<Link to={{ pathname: '/project/activity', query: { id: this.props.project } }}>
{translate('show_more')}
</Link>
</div>
</div>
);
}
}

const mapStateToProps = (state, ownProps: Props) => ({
analyses: getAnalyses(getProjectActivity(state), ownProps.project)
});

const mapDispatchToProps = { fetchRecentProjectActivity };

export default connect(mapStateToProps, mapDispatchToProps)(AnalysesList);

server/sonar-web/src/main/js/apps/overview/events/Event.js → server/sonar-web/src/main/js/apps/overview/events/Analysis.js Dosyayı Görüntüle

@@ -18,39 +18,36 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import moment from 'moment';

import { EventType } from '../propTypes';
import Events from '../../projectActivity/components/Events';
import FormattedDate from '../../../components/ui/FormattedDate';
import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
import { translate } from '../../../helpers/l10n';
import type { Analysis as AnalysisType } from '../../../store/projectActivity/duck';

const Event = ({ event }) => {
return (
<TooltipsContainer>
<li className="spacer-top">
<p>
<strong className="js-event-type">
{translate('event.category', event.type)}
</strong>
{': '}
<span className="js-event-name">{event.name}</span>
{event.text && (
<i
className="spacer-left icon-help"
data-toggle="tooltip"
title={event.text}/>
)}
</p>
<p className="note little-spacer-top js-event-date">
{moment(event.date).format('LL')}
</p>
</li>
</TooltipsContainer>
);
};
export default class Analysis extends React.Component {
props: {
analysis: AnalysisType
};

Event.propTypes = {
event: EventType.isRequired
};
render () {
const { analysis } = this.props;

export default Event;
return (
<TooltipsContainer>
<li className="overview-analysis">
<div className="small little-spacer-bottom">
<strong>
<FormattedDate date={analysis.date} format="LL"/>
</strong>
</div>

{analysis.events.length > 0 ? (
<Events events={analysis.events} canAdmin={false}/>
) : (
<span className="note">{translate('project_activity.project_analyzed')}</span>
)}
</li>
</TooltipsContainer>
);
}
}

+ 0
- 149
server/sonar-web/src/main/js/apps/overview/events/EventsList.js Dosyayı Görüntüle

@@ -1,149 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 moment from 'moment';
import React from 'react';
import shallowCompare from 'react-addons-shallow-compare';

import Event from './Event';
import EventsListFilter from './EventsListFilter';
import { getEvents } from '../../../api/events';
import { translate } from '../../../helpers/l10n';

const LIMIT = 5;

export default class EventsList extends React.Component {
state = {
events: [],
limited: true,
filter: 'All'
};

componentDidMount () {
this.mounted = true;
this.fetchEvents();
}

shouldComponentUpdate (nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}

componentDidUpdate (nextProps) {
if (nextProps.component !== this.props.component) {
this.fetchEvents();
}
}

componentWillUnmount () {
this.mounted = false;
}

fetchEvents () {
getEvents(this.props.component.key).then(events => {
if (this.mounted) {
const nextEvents = events.map(event => {
return {
id: event.id,
date: moment(event.dt).toDate(),
type: event.c,
name: event.n,
text: event.ds
};
});

this.setState({ events: nextEvents });
}
});
}

limitEvents (events) {
return this.state.limited ? events.slice(0, LIMIT) : events;
}

filterEvents (events) {
if (this.state.filter === 'All') {
return events;
} else {
return events.filter(event => event.type === this.state.filter);
}
}

handleClick (e) {
e.preventDefault();
this.setState({ limited: !this.state.limited });
}

handleFilter (filter) {
this.setState({ filter });
}

renderMoreLink () {
const text = this.state.limited ?
translate('widget.events.show_all') :
translate('hide');

return (
<p className="spacer-top note">
<a onClick={this.handleClick.bind(this)} href="#">{text}</a>
</p>
);
}

renderList (events) {
if (events.length) {
return (
<ul>
{events.map(event => (
<Event key={event.id} event={event}/>
))}
</ul>
);
} else {
return (
<p className="spacer-top note">
{translate('no_results')}
</p>
);
}
}

render () {
const filteredEvents = this.filterEvents(this.state.events);
const events = this.limitEvents(filteredEvents);

return (
<div className="overview-meta-card">
<div className="clearfix">
<h4 className="pull-left overview-meta-header">
{translate('widget.events.name')}
</h4>
<div className="pull-right">
<EventsListFilter
currentFilter={this.state.filter}
onFilter={this.handleFilter.bind(this)}/>
</div>
</div>

{this.renderList(events)}

{filteredEvents.length > LIMIT && this.renderMoreLink()}
</div>
);
}
}

+ 4
- 4
server/sonar-web/src/main/js/apps/overview/meta/Meta.js Dosyayı Görüntüle

@@ -23,7 +23,7 @@ import MetaKey from './MetaKey';
import MetaLinks from './MetaLinks';
import MetaQualityGate from './MetaQualityGate';
import MetaQualityProfiles from './MetaQualityProfiles';
import EventsList from './../events/EventsList';
import AnalysesList from '../events/AnalysesList';
import MetaSize from './MetaSize';

const Meta = ({ component, measures }) => {
@@ -40,7 +40,7 @@ const Meta = ({ component, measures }) => {
const shouldShowQualityProfiles = !isView && !isDeveloper && hasQualityProfiles;
const shouldShowQualityGate = !isView && !isDeveloper && hasQualityGate;

const showShowEvents = isProject || isView || isDeveloper;
const showShowAnalyses = isProject || isView || isDeveloper;

return (
<div className="overview-meta">
@@ -64,8 +64,8 @@ const Meta = ({ component, measures }) => {

<MetaKey component={component}/>

{showShowEvents && (
<EventsList component={component}/>
{showShowAnalyses && (
<AnalysesList project={component.key}/>
)}
</div>
);

+ 10
- 0
server/sonar-web/src/main/js/apps/overview/styles.css Dosyayı Görüntüle

@@ -319,6 +319,16 @@
box-sizing: border-box;
}

.overview-analysis {

}

.overview-analysis + .overview-analysis {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #e6e6e6;
}

/*
* Other
*/

+ 87
- 0
server/sonar-web/src/main/js/apps/projectActivity/actions.js Dosyayı Görüntüle

@@ -0,0 +1,87 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 * as api from '../../api/projectActivity';
import {
receiveProjectActivity,
addEvent,
deleteEvent as deleteEventAction,
changeEvent as changeEventAction,
deleteAnalysis as deleteAnalysisAction,
getPaging
} from '../../store/projectActivity/duck';
import { onFail } from '../../store/rootActions';
import { getProjectActivity } from '../../store/rootReducer';

const rejectOnFail = (dispatch: Function) => (error: any) => {
onFail(dispatch)(error);
return Promise.reject();
};

export const fetchProjectActivity = (project: string, filter: ?string) => (dispatch: Function): void => {
api.getProjectActivity(project, { category: filter }).then(
({ analyses, paging }) => dispatch(receiveProjectActivity(project, analyses, paging)),
onFail(dispatch)
);
};

export const fetchMoreProjectActivity = (project: string, filter: ?string) =>
(dispatch: Function, getState: Function): void => {
const projectActivity = getProjectActivity(getState());
const { pageIndex } = getPaging(projectActivity, project);

api.getProjectActivity(project, { category: filter, pageIndex: pageIndex + 1 }).then(
({ analyses, paging }) => dispatch(receiveProjectActivity(project, analyses, paging)),
onFail(dispatch)
);
};

export const addCustomEvent = (analysis: string, name: string, category?: string) =>
(dispatch: Function): Promise<*> => {
return api.createEvent(analysis, name, category).then(
({ analysis, ...event }) => dispatch(addEvent(analysis, event)),
rejectOnFail(dispatch)
);
};

export const deleteEvent = (analysis: string, event: string) => (dispatch: Function): Promise<*> => {
return api.deleteEvent(event).then(
() => dispatch(deleteEventAction(analysis, event)),
rejectOnFail(dispatch)
);
};

export const addVersion = (analysis: string, version: string) => (dispatch: Function): Promise<*> => {
return dispatch(addCustomEvent(analysis, version, 'VERSION'));
};

export const changeEvent = (event: string, name: string) => (dispatch: Function): Promise<*> => {
return api.changeEvent(event, name).then(
() => dispatch(changeEventAction(event, { name })),
rejectOnFail(dispatch)
);
};

export const deleteAnalysis = (project: string, analysis: string) => (dispatch: Function): Promise<*> => {
return api.deleteAnalysis(analysis).then(
() => dispatch(deleteAnalysisAction(project, analysis)),
rejectOnFail(dispatch)
);
};

+ 33
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/ChangeIcon.js Dosyayı Görüntüle

@@ -0,0 +1,33 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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';

export default class ChangeIcon extends React.Component {
render () {
/* eslint-disable max-len */
return (
<svg width="12" height="12" viewBox="0 0 14 14">
<path fill="#236a97"
d="M3.35 12.82l.85-.84L2.02 9.8l-.84.85v.98h1.2v1.2h.97zM8.2 4.24c0-.13-.08-.2-.22-.2-.06 0-.1.02-.15.06l-5 5c-.05.05-.08.1-.08.17 0 .13.07.2.2.2.07 0 .12-.02.16-.06l5.02-5c.05-.04.07-.1.07-.16zm-.5-1.77l3.83 3.84-7.7 7.7H0v-3.84l7.7-7.7zm6.3.88c0 .33-.1.6-.34.84L12.12 5.7 8.28 1.88 9.8.35c.24-.23.5-.35.85-.35.32 0 .6.12.84.35l2.16 2.16c.23.25.34.53.34.85z"/>
</svg>
);
}
}

+ 33
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/DeleteIcon.js Dosyayı Görüntüle

@@ -0,0 +1,33 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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';

export default class DeleteIcon extends React.Component {
render () {
/* eslint-disable max-len */
return (
<svg width="12" height="12" viewBox="0 0 14 14">
<path fill="#d4333f"
d="M14 11.27c0 .3-.1.58-.33.8l-1.6 1.6c-.22.22-.5.33-.8.33-.32 0-.6-.1-.8-.33L7 10.2l-3.46 3.47c-.22.22-.5.33-.8.33-.32 0-.6-.1-.8-.33l-1.6-1.6c-.23-.22-.34-.5-.34-.8 0-.32.1-.6.33-.8L3.8 7 .32 3.54C.1 3.32 0 3.04 0 2.74c0-.32.1-.6.33-.8l1.6-1.6c.22-.23.5-.34.8-.34.32 0 .6.1.8.33L7 3.8 10.46.32c.22-.22.5-.33.8-.33.32 0 .6.1.8.33l1.6 1.6c.23.22.34.5.34.8 0 .32-.1.6-.33.8L10.2 7l3.47 3.46c.22.22.33.5.33.8z"/>
</svg>
);
}
}

+ 7
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/Event.css Dosyayı Görüntüle

@@ -0,0 +1,7 @@
.project-activity-event {

}

.project-activity-event + .project-activity-event {
margin-top: 4px;
}

+ 118
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/Event.js Dosyayı Görüntüle

@@ -0,0 +1,118 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 EventInner from './EventInner';
import ChangeCustomEventForm from './forms/ChangeCustomEventForm';
import RemoveCustomEventForm from './forms/RemoveCustomEventForm';
import DeleteIcon from './DeleteIcon';
import ChangeIcon from './ChangeIcon';
import type { Event as EventType } from '../../../store/projectActivity/duck';

type Props = {
analysis: string,
event: EventType,
isFirst: boolean,
canAdmin: boolean
};

type State = {
changing: boolean,
deleting: boolean
};

export default class Event extends React.Component {
mounted: boolean;
props: Props;

state: State = {
changing: false,
deleting: false
};

componentDidMount () {
this.mounted = true;
}

componentWillUnmount () {
this.mounted = false;
}

startChanging = () => {
this.setState({ changing: true });
};

stopChanging = () => {
if (this.mounted) {
this.setState({ changing: false });
}
};

startDeleting = () => {
this.setState({ deleting: true });
};

stopDeleting = () => {
if (this.mounted) {
this.setState({ deleting: false });
}
};

render () {
const { event, canAdmin } = this.props;
const canChange = ['OTHER', 'VERSION'].includes(event.category);
const canDelete = event.category === 'OTHER' || (event.category === 'VERSION' && !this.props.isFirst);
const showActions = canAdmin && (canChange || canDelete);

return (
<div className="project-activity-event">
<EventInner event={this.props.event}/>

{showActions && (
<div className="project-activity-event-actions">
{canChange && (
<button className="js-change-event button-clean" onClick={this.startChanging}>
<ChangeIcon/>
</button>
)}
{canDelete && (
<button className="js-delete-event button-clean" onClick={this.startDeleting}>
<DeleteIcon/>
</button>
)}
</div>
)}

{this.state.changing && (
<ChangeCustomEventForm
event={this.props.event}
onClose={this.stopChanging}/>
)}

{this.state.deleting && (
<RemoveCustomEventForm
analysis={this.props.analysis}
event={this.props.event}
onClose={this.stopDeleting}/>
)}
</div>
);
}
}

server/sonar-web/src/main/js/apps/overview/events/EventsListFilter.js → server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.js Dosyayı Görüntüle

@@ -17,36 +17,32 @@
* 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 Select from 'react-select';
import type { Event as EventType } from '../../../store/projectActivity/duck';
import { translate } from '../../../helpers/l10n';
import './Event.css';

const TYPES = ['All', 'Version', 'Alert', 'Profile', 'Other'];
export default class EventInner extends React.Component {
props: {
event: EventType
};

const EventsListFilter = ({ currentFilter, onFilter }) => {
const handleChange = selected => onFilter(selected.value);
render () {
const { event } = this.props;

const options = TYPES.map(type => {
return {
value: type,
label: translate('event.category', type)
};
});
if (event.category === 'VERSION') {
return (
<span className="badge project-activity-version-badge">{this.props.event.name}</span>
);
}

return (
<Select
value={currentFilter}
options={options}
clearable={false}
searchable={false}
onChange={handleChange}
style={{ width: '125px' }}/>
);
};

EventsListFilter.propTypes = {
onFilter: React.PropTypes.func.isRequired,
currentFilter: React.PropTypes.string.isRequired
};

export default EventsListFilter;
return (
<span>
<span className="note">{translate('event.category', event.category)}:</span>
{' '}
<strong title={event.description}>{event.name}</strong>
</span>
);
}
}

+ 56
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/Events.js Dosyayı Görüntüle

@@ -0,0 +1,56 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 sortBy from 'lodash/sortBy';
import Event from './Event';
import type { Event as EventType } from '../../../store/projectActivity/duck';

export default class Events extends React.Component {
props: {
analysis: string,
events: Array<EventType>,
isFirst: boolean,
canAdmin: boolean
};

render () {
const sortedEvents: Array<EventType> = sortBy(
this.props.events,
// versions first
(event: EventType) => event.category === 'VERSION' ? 0 : 1,
// then the rest sorted by category
'category'
);

return (
<div className="project-activity-events">
{sortedEvents.map(event => (
<Event
key={event.key}
analysis={this.props.analysis}
event={event}
isFirst={this.props.isFirst}
canAdmin={this.props.canAdmin}/>
))}
</div>
);
}
}

+ 87
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js Dosyayı Görüntüle

@@ -0,0 +1,87 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 groupBy from 'lodash/groupBy';
import moment from 'moment';
import ProjectActivityAnalysis from './ProjectActivityAnalysis';
import FormattedDate from '../../../components/ui/FormattedDate';
import { getProjectActivity } from '../../../store/rootReducer';
import { getAnalyses } from '../../../store/projectActivity/duck';
import { translate } from '../../../helpers/l10n';

class ProjectActivityAnalysesList extends React.Component {
props: {
project: string,
analyses?: Array<{
key: string,
date: string
}>,
canAdmin: boolean
};

render () {
if (!this.props.analyses) {
return null;
}

if (this.props.analyses.length === 0) {
return (
<div className="note">{translate('no_results')}</div>
);
}

const firstAnalysis = this.props.analyses[0];

const byDay = groupBy(this.props.analyses, analysis => moment(analysis.date).startOf('day').valueOf());

return (
<div className="boxed-group boxed-group-inner">
<ul className="project-activity-days-list">
{Object.keys(byDay).map(day => (
<li key={day} className="project-activity-day" data-day={moment(Number(day)).format('YYYY-MM-DD')}>
<div className="project-activity-date">
<FormattedDate date={Number(day)} format="LL"/>
</div>

<ul className="project-activity-analyses-list">
{byDay[day].map(analysis => (
<ProjectActivityAnalysis
key={analysis.key}
analysis={analysis}
isFirst={analysis === firstAnalysis}
project={this.props.project}
canAdmin={this.props.canAdmin}/>
))}
</ul>
</li>
))}
</ul>
</div>
);
}
}

const mapStateToProps = (state, ownProps) => ({
analyses: getAnalyses(getProjectActivity(state), ownProps.project)
});

export default connect(mapStateToProps)(ProjectActivityAnalysesList);

+ 88
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js Dosyayı Görüntüle

@@ -0,0 +1,88 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 Events from './Events';
import AddVersionForm from './forms/AddVersionForm';
import AddCustomEventForm from './forms/AddCustomEventForm';
import RemoveAnalysisForm from './forms/RemoveAnalysisForm';
import FormattedDate from '../../../components/ui/FormattedDate';
import type { Analysis } from '../../../store/projectActivity/duck';
import { translate } from '../../../helpers/l10n';

export default class ProjectActivityAnalysis extends React.Component {
props: {
analysis: Analysis,
isFirst: boolean,
project: string,
canAdmin: boolean
};

render () {
const { date, events } = this.props.analysis;
const { isFirst, canAdmin } = this.props;

const version = events.find(event => event.category === 'VERSION');

return (
<li className="project-activity-analysis clearfix">
{canAdmin && (
<div className="project-activity-analysis-actions">
<div className="dropdown display-inline-block">
<button className="js-create button-small" data-toggle="dropdown">
{translate('create')} <i className="icon-dropdown"/>
</button>
<ul className="dropdown-menu dropdown-menu-right">
{version == null && (
<li>
<AddVersionForm analysis={this.props.analysis}/>
</li>
)}
<li>
<AddCustomEventForm analysis={this.props.analysis}/>
</li>
</ul>
</div>

{!isFirst && (
<div className="display-inline-block little-spacer-left">
<RemoveAnalysisForm
analysis={this.props.analysis}
project={this.props.project}/>
</div>
)}
</div>
)}

<div className="project-activity-time">
<FormattedDate date={date} format="LT" tooltipFormat="LTS"/>
</div>

{events.length > 0 && (
<Events
analysis={this.props.analysis.key}
events={events}
isFirst={this.props.isFirst}
canAdmin={canAdmin}/>
)}
</li>
);
}
}

+ 91
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js Dosyayı Görüntüle

@@ -0,0 +1,91 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 ProjectActivityPageHeader from './ProjectActivityPageHeader';
import ProjectActivityAnalysesList from './ProjectActivityAnalysesList';
import ProjectActivityPageFooter from './ProjectActivityPageFooter';
import { fetchProjectActivity } from '../actions';
import { getComponent } from '../../../store/rootReducer';
import './projectActivity.css';

type Props = {
location: { query: { id: string } },
fetchProjectActivity: (project: string) => void,
filter: ?string,
project: { configuration?: { showHistory: boolean } }
};

type State = {
filter: ?string
};

class ProjectActivityApp extends React.Component {
props: Props;

state: State = {
filter: null
};

componentDidMount () {
document.querySelector('html').classList.add('dashboard-page');
this.props.fetchProjectActivity(this.props.location.query.id);
}

componentWillUnmount () {
document.querySelector('html').classList.remove('dashboard-page');
}

handleFilter = (filter: ?string) => {
this.setState({ filter });
this.props.fetchProjectActivity(this.props.location.query.id, filter);
};

render () {
const project = this.props.location.query.id;
const { configuration } = this.props.project;
const canAdmin = configuration ? configuration.showHistory : false;

return (
<div id="project-activity" className="page page-limited">
<ProjectActivityPageHeader
project={project}
filter={this.state.filter}
changeFilter={this.handleFilter}/>

<ProjectActivityAnalysesList
project={project}
canAdmin={canAdmin}/>

<ProjectActivityPageFooter
project={project}/>
</div>
);
}
}

const mapStateToProps = (state, ownProps: Props) => ({
project: getComponent(state, ownProps.location.query.id)
});

const mapDispatchToProps = { fetchProjectActivity };

export default connect(mapStateToProps, mapDispatchToProps)(ProjectActivityApp);

+ 61
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFooter.js Dosyayı Görüntüle

@@ -0,0 +1,61 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 ListFooter from '../../../components/controls/ListFooter';
import { getProjectActivity } from '../../../store/rootReducer';
import { getAnalyses, getPaging } from '../../../store/projectActivity/duck';
import { fetchMoreProjectActivity } from '../actions';
import type { Paging } from '../../../store/projectActivity/duck';

class ProjectActivityPageFooter extends React.Component {
props: {
analyses: Array<*>,
paging: ?Paging,
project: string,
fetchMoreProjectActivity: (project: string) => void
};

handleLoadMore = () => {
this.props.fetchMoreProjectActivity(this.props.project);
};

render () {
const { analyses, paging } = this.props;

if (!paging || analyses.length === 0) {
return null;
}

return (
<ListFooter count={analyses.length} total={paging.total} loadMore={this.handleLoadMore}/>
);
}
}

const mapStateToProps = (state, ownProps) => ({
analyses: getAnalyses(getProjectActivity(state), ownProps.project),
paging: getPaging(getProjectActivity(state), ownProps.project)
});

const mapDispatchToProps = { fetchMoreProjectActivity };

export default connect(mapStateToProps, mapDispatchToProps)(ProjectActivityPageFooter);

+ 63
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js Dosyayı Görüntüle

@@ -0,0 +1,63 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 Select from 'react-select';
import { translate } from '../../../helpers/l10n';

type Props = {
changeFilter: (filter: ?string) => void,
filter: ?string,
project: string
};

export default class ProjectActivityPageHeader extends React.Component {
props: Props;

handleChange = (option: null | { value: string }) => {
this.props.changeFilter(option && option.value);
}

render () {
const selectOptions = ['VERSION', 'QUALITY_GATE', 'QUALITY_PROFILE', 'OTHER'].map(category => ({
label: translate('event.category', category),
value: category
}));

return (
<header className="page-header">
<div className="page-actions">
<Select
className="input-medium"
placeholder={translate('filter_verb') + '...'}
clearable={true}
searchable={false}
value={this.props.filter}
options={selectOptions}
onChange={this.handleChange}/>
</div>

<div className="page-description">
{translate('project_activity.page.description')}
</div>
</header>
);
}
}

+ 34
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddCustomEventForm.js Dosyayı Görüntüle

@@ -0,0 +1,34 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { addCustomEvent } from '../../actions';
import AddEventForm from './AddEventForm';

const AddCustomEventForm = props => (
<AddEventForm {...props} addEventButtonText="project_activity.add_custom_event"/>
);

const mapStateToProps = null;

const mapDispatchToProps = { addEvent: addCustomEvent };

export default connect(mapStateToProps, mapDispatchToProps)(AddCustomEventForm);

+ 146
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js Dosyayı Görüntüle

@@ -0,0 +1,146 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 Modal from 'react-modal';
import type { Analysis } from '../../../../store/projectActivity/duck';
import { translate } from '../../../../helpers/l10n';

type Props = {
addEvent: () => Promise<*>,
analysis: Analysis,
addEventButtonText: string
};

type State = {
open: boolean,
processing: boolean;
name: string;
}

export default class AddEventForm extends React.Component {
mounted: boolean;
props: Props;

state: State = {
open: false,
processing: false,
name: ''
};

componentDidMount () {
this.mounted = true;
}

componentWillUnmount () {
this.mounted = false;
}

openForm = (e: Object) => {
e.preventDefault();
if (this.mounted) {
this.setState({ open: true });
}
};

closeForm = () => {
if (this.mounted) {
this.setState({ open: false, name: '' });
}
};

changeInput = (e: Object) => {
if (this.mounted) {
this.setState({ name: e.target.value });
}
};

stopProcessing = () => {
if (this.mounted) {
this.setState({ processing: false });
}
};

stopProcessingAndClose = () => {
if (this.mounted) {
this.setState({ open: false, processing: false, name: '' });
}
};

handleSubmit = (e: Object) => {
e.preventDefault();
this.setState({ processing: true });
this.props.addEvent(this.props.analysis.key, this.state.name)
.then(this.stopProcessingAndClose, this.stopProcessing);
};

renderModal () {
return (
<Modal isOpen={true}
contentLabel="modal form"
className="modal"
overlayClassName="modal-overlay"
onRequestClose={this.closeForm}>

<header className="modal-head">
<h2>{translate(this.props.addEventButtonText)}</h2>
</header>

<form onSubmit={this.handleSubmit}>
<div className="modal-body">
<div className="modal-field">
<label>{translate('name')}</label>
<input
value={this.state.name}
autoFocus={true}
disabled={this.state.processing}
className="input-medium"
type="text"
onChange={this.changeInput}/>
</div>
</div>

<footer className="modal-foot">
{this.state.processing ? (
<i className="spinner"/>
) : (
<div>
<button type="submit">{translate('save')}</button>
<button type="reset" className="button-link" onClick={this.closeForm}>
{translate('cancel')}
</button>
</div>
)}
</footer>
</form>

</Modal>
);
}

render () {
return (
<a className="js-add-event button-small" href="#" onClick={this.openForm}>
{translate(this.props.addEventButtonText)}
{this.state.open && this.renderModal()}
</a>
);
}
}

+ 34
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddVersionForm.js Dosyayı Görüntüle

@@ -0,0 +1,34 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { addVersion } from '../../actions';
import AddEventForm from './AddEventForm';

const AddVersionForm = props => (
<AddEventForm {...props} addEventButtonText="project_activity.add_version"/>
);

const mapStateToProps = null;

const mapDispatchToProps = { addEvent: addVersion };

export default connect(mapStateToProps, mapDispatchToProps)(AddVersionForm);

+ 36
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeCustomEventForm.js Dosyayı Görüntüle

@@ -0,0 +1,36 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 ChangeEventForm from './ChangeEventForm';
import { changeEvent } from '../../actions';

const ChangeCustomEventForm = props => (
<ChangeEventForm
{...props}
changeEventButtonText="project_activity.change_custom_event"/>
);

const mapStateToProps = null;

const mapDispatchToProps = { changeEvent };

export default connect(mapStateToProps, mapDispatchToProps)(ChangeCustomEventForm);

+ 135
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.js Dosyayı Görüntüle

@@ -0,0 +1,135 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 Modal from 'react-modal';
import type { Event } from '../../../../store/projectActivity/duck';
import { translate } from '../../../../helpers/l10n';

type Props = {
changeEvent: () => Promise<*>,
changeEventButtonText: string,
event: Event,
onClose: () => void
};

type State = {
processing: boolean,
name: string
}

export default class ChangeEventForm extends React.Component {
mounted: boolean;
props: Props;
state: State;

constructor (props: Props) {
super(props);
this.state = {
processing: false,
name: props.event.name
};
}

componentDidMount () {
this.mounted = true;
}

componentWillUnmount () {
this.mounted = false;
}

closeForm = () => {
if (this.mounted) {
this.setState({ name: this.props.event.name });
}
this.props.onClose();
};

changeInput = (e: Object) => {
if (this.mounted) {
this.setState({ name: e.target.value });
}
};

stopProcessing = () => {
if (this.mounted) {
this.setState({ processing: false });
}
};

stopProcessingAndClose = () => {
if (this.mounted) {
this.setState({ processing: false });
}
this.props.onClose();
};

handleSubmit = (e: Object) => {
e.preventDefault();
this.setState({ processing: true });
this.props.changeEvent(this.props.event.key, this.state.name)
.then(this.stopProcessingAndClose, this.stopProcessing);
};

render () {
return (
<Modal isOpen={true}
contentLabel="modal form"
className="modal"
overlayClassName="modal-overlay"
onRequestClose={this.closeForm}>

<header className="modal-head">
<h2>{translate(this.props.changeEventButtonText)}</h2>
</header>

<form onSubmit={this.handleSubmit}>
<div className="modal-body">
<div className="modal-field">
<label>{translate('name')}</label>
<input
value={this.state.name}
autoFocus={true}
disabled={this.state.processing}
className="input-medium"
type="text"
onChange={this.changeInput}/>
</div>
</div>

<footer className="modal-foot">
{this.state.processing ? (
<i className="spinner"/>
) : (
<div>
<button type="submit">{translate('change_verb')}</button>
<button type="reset" className="button-link" onClick={this.closeForm}>
{translate('cancel')}
</button>
</div>
)}
</footer>
</form>

</Modal>
);
}
}

+ 36
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeVersionForm.js Dosyayı Görüntüle

@@ -0,0 +1,36 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 ChangeEventForm from './ChangeEventForm';
import { changeEvent } from '../../actions';

const ChangeVersionForm = props => (
<ChangeEventForm
{...props}
changeEventButtonText="project_activity.change_version"/>
);

const mapStateToProps = null;

const mapDispatchToProps = { changeEvent };

export default connect(mapStateToProps, mapDispatchToProps)(ChangeVersionForm);

+ 136
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js Dosyayı Görüntüle

@@ -0,0 +1,136 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 Modal from 'react-modal';
import type { Analysis } from '../../../../store/projectActivity/duck';
import { translate } from '../../../../helpers/l10n';
import { deleteAnalysis } from '../../actions';

type Props = {
analysis: Analysis,
deleteAnalysis: () => Promise<*>,
project: string
};

type State = {
open: boolean,
processing: boolean
}

class RemoveAnalysisForm extends React.Component {
mounted: boolean;
props: Props;

state: State = {
open: false,
processing: false
};

componentDidMount () {
this.mounted = true;
}

componentWillUnmount () {
this.mounted = false;
}

openForm = () => {
if (this.mounted) {
this.setState({ open: true });
}
};

closeForm = () => {
if (this.mounted) {
this.setState({ open: false });
}
};

stopProcessing = () => {
if (this.mounted) {
this.setState({ processing: false });
}
};

stopProcessingAndClose = () => {
if (this.mounted) {
this.setState({ open: false, processing: false });
}
};

handleSubmit = (e: Object) => {
e.preventDefault();
this.setState({ processing: true });
this.props.deleteAnalysis(this.props.project, this.props.analysis.key)
.then(this.stopProcessingAndClose, this.stopProcessing);
};

renderModal () {
return (
<Modal isOpen={true}
contentLabel="modal form"
className="modal"
overlayClassName="modal-overlay"
onRequestClose={this.closeForm}>

<header className="modal-head">
<h2>{translate('project_activity.delete_analysis')}</h2>
</header>

<form onSubmit={this.handleSubmit}>
<div className="modal-body">
{translate('project_activity.delete_analysis.question')}
</div>

<footer className="modal-foot">
{this.state.processing ? (
<i className="spinner"/>
) : (
<div>
<button type="submit" className="button-red">{translate('delete')}</button>
<button type="reset" className="button-link" onClick={this.closeForm}>
{translate('cancel')}
</button>
</div>
)}
</footer>
</form>

</Modal>
);
}

render () {
return (
<button className="js-delete-analysis button-small button-red" onClick={this.openForm}>
{translate('delete')}
{this.state.open && this.renderModal()}
</button>
);
}
}

const mapStateToProps = null;

const mapDispatchToProps = { deleteAnalysis };

export default connect(mapStateToProps, mapDispatchToProps)(RemoveAnalysisForm);

+ 37
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveCustomEventForm.js Dosyayı Görüntüle

@@ -0,0 +1,37 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 RemoveEventForm from './RemoveEventForm';
import { deleteEvent } from '../../actions';

const RemoveCustomEventForm = props => (
<RemoveEventForm
{...props}
removeEventButtonText="project_activity.remove_custom_event"
removeEventQuestion="project_activity.remove_custom_event.question"/>
);

const mapStateToProps = null;

const mapDispatchToProps = { deleteEvent };

export default connect(mapStateToProps, mapDispatchToProps)(RemoveCustomEventForm);

+ 113
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveEventForm.js Dosyayı Görüntüle

@@ -0,0 +1,113 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 Modal from 'react-modal';
import type { Analysis, Event } from '../../../../store/projectActivity/duck';
import { translate } from '../../../../helpers/l10n';

type Props = {
analysis: Analysis,
deleteEvent: () => Promise<*>,
event: Event,
removeEventButtonText: string,
removeEventQuestion: string,
onClose: () => void
};

type State = {
processing: boolean
}

export default class RemoveVersionForm extends React.Component {
mounted: boolean;
props: Props;

state: State = {
processing: false
};

componentDidMount () {
this.mounted = true;
}

componentWillUnmount () {
this.mounted = false;
}

closeForm = () => {
this.props.onClose();
};

stopProcessing = () => {
if (this.mounted) {
this.setState({ processing: false });
}
};

stopProcessingAndClose = () => {
if (this.mounted) {
this.setState({ processing: false });
}
this.props.onClose();
};

handleSubmit = (e: Object) => {
e.preventDefault();
this.setState({ processing: true });
this.props.deleteEvent(this.props.analysis, this.props.event.key)
.then(this.stopProcessingAndClose, this.stopProcessing);
};

render () {
return (
<Modal isOpen={true}
contentLabel="modal form"
className="modal"
overlayClassName="modal-overlay"
onRequestClose={this.closeForm}>

<header className="modal-head">
<h2>{translate(this.props.removeEventButtonText)}</h2>
</header>

<form onSubmit={this.handleSubmit}>
<div className="modal-body">
{translate(this.props.removeEventQuestion)}
</div>

<footer className="modal-foot">
{this.state.processing ? (
<i className="spinner"/>
) : (
<div>
<button type="submit" className="button-red" autoFocus={true}>{translate('delete')}</button>
<button type="reset" className="button-link" onClick={this.closeForm}>
{translate('cancel')}
</button>
</div>
)}
</footer>
</form>

</Modal>
);
}
}

+ 37
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveVersionForm.js Dosyayı Görüntüle

@@ -0,0 +1,37 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 RemoveEventForm from './RemoveEventForm';
import { deleteEvent } from '../../actions';

const RemoveVersionForm = props => (
<RemoveEventForm
{...props}
removeEventButtonText="project_activity.remove_version"
removeEventQuestion="project_activity.remove_version.question"/>
);

const mapStateToProps = null;

const mapDispatchToProps = { deleteEvent };

export default connect(mapStateToProps, mapDispatchToProps)(RemoveVersionForm);

+ 121
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css Dosyayı Görüntüle

@@ -0,0 +1,121 @@
.project-activity-days-list {

}

.project-activity-day {
margin-bottom: 40px;
}

.project-activity-date {
margin-bottom: 16px;
font-size: 15px;
font-weight: bold;
}

.project-activity-analyses-list {

}

.project-activity-analysis {
position: relative;
min-height: 20px;
padding-top: 6px;
padding-bottom: 6px;
border-top: 1px solid #e6e6e6;
border-bottom: 1px solid #e6e6e6;
}

.project-activity-analysis:hover {
background-color: #ecf6fe;
}

.project-activity-analysis + .project-activity-analysis {
border-top: none;
}

.project-activity-analysis-actions {
float: right;
padding-right: 10px;
}

.project-activity-analysis-actions:first-child,
.project-activity-analysis-actions:empty {
margin-top: 0;
}

.project-activity-analysis-actions > button + button,
.project-activity-analysis-actions > button + form,
.project-activity-analysis-actions > form + button,
.project-activity-analysis-actions > form + form {
margin-left: 8px;
}

.project-activity-analysis-form {
display: inline-block;
vertical-align: top;
line-height: 20px;
margin-bottom: 10px;
padding: 9px;
border: 1px solid #faebcc;
border-radius: 2px;
background-color: #fcf8e3;
}

.project-activity-analysis-form + .project-activity-analysis-form {
margin-left: 8px;
}

.project-activity-time {
float: left;
width: 130px;
line-height: 20px;
padding-right: 50px;
box-sizing: border-box;
font-size: 12px;
font-weight: bold;
text-align: right;
}

.project-activity-time::after {
position: absolute;
z-index: 21;
top: 11px;
left: 100px;
display: block;
width: 10px;
height: 10px;
border: 2px solid #4b9fd5;
border-radius: 10px;
box-sizing: border-box;
content: "";
}

.project-activity-events {
overflow: hidden;
}

.project-activity-event {
line-height: 20px;
}

.project-activity-event-actions {
display: inline-block;
margin-left: 8px;
}

.project-activity-event-actions button {
height: 20px;
}

.project-activity-event-actions button + button {
margin-left: 4px;
}

.project-activity-version-badge {
vertical-align: middle;
padding: 4px 8px;
border-radius: 2px;
font-weight: bold;
font-size: 12px;
letter-spacing: 0;
}

+ 27
- 0
server/sonar-web/src/main/js/apps/projectActivity/routes.js Dosyayı Görüntüle

@@ -0,0 +1,27 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { IndexRoute } from 'react-router';
import ProjectActivityApp from './components/ProjectActivityApp';

export default (
<IndexRoute component={ProjectActivityApp}/>
);

it/it-tests/src/test/java/pageobjects/ProjectHistorySnapshotItem.java → server/sonar-web/src/main/js/components/ui/FormattedDate.js Dosyayı Görüntüle

@@ -17,40 +17,32 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package pageobjects;
// @flow
import React from 'react';
import moment from 'moment';

import com.codeborne.selenide.SelenideElement;
import org.openqa.selenium.NoSuchElementException;
export default class FormattedDate extends React.Component {
props: {
date: string | number,
format?: string,
tooltipFormat?: string
};

public class ProjectHistorySnapshotItem {
static defaultProps = {
format: 'LLL'
};

private final SelenideElement elt;
render () {
const { date, format, tooltipFormat } = this.props;

public ProjectHistorySnapshotItem(SelenideElement elt) {
this.elt = elt;
}

public SelenideElement getVersionText() {
return elt.$("td:nth-child(5) table td:nth-child(1)");
}

public SelenideElement getType() {
try {
return elt.$(".js-type");
} catch (NoSuchElementException e) {
return null;
}
}

public SelenideElement getUrl() {
return elt.$(".js-url");
}
const m = moment(date);

public SelenideElement getDeleteButton() {
return elt.$("td:nth-child(9) input[type=\"submit\"]");
}
const title = tooltipFormat ? m.format(tooltipFormat) : undefined;

public void clickDelete() {
getDeleteButton().click();
return (
<time dateTime={m.format()} title={title}>
{m.format(format)}
</time>
);
}
}

+ 93
- 0
server/sonar-web/src/main/js/store/projectActivity/__tests__/__snapshots__/analyses-test.js.snap Dosyayı Görüntüle

@@ -0,0 +1,93 @@
exports[`test reducer 1`] = `Object {}`;

exports[`test reducer 2`] = `
Object {
"AVgAgC1Vdo07z3PUnnkt": Object {
"date": "2016-10-26T12:17:29+0200",
"events": Array [
"AVkWNYNYr4pSN7TrXcjY"
],
"key": "AVgAgC1Vdo07z3PUnnkt"
},
"AVgFqeOSKpGuA48ADATE": Object {
"date": "2016-10-27T12:21:15+0200",
"events": Array [],
"key": "AVgFqeOSKpGuA48ADATE"
},
"AVgGkRvCrrTJiPpCD-rG": Object {
"date": "2016-10-27T16:33:50+0200",
"events": Array [
"AVjUDBiSiXOcXjpycvde"
],
"key": "AVgGkRvCrrTJiPpCD-rG"
}
}
`;

exports[`test reducer 3`] = `
Object {
"AVgAgC1Vdo07z3PUnnkt": Object {
"date": "2016-10-26T12:17:29+0200",
"events": Array [
"AVkWNYNYr4pSN7TrXcjY"
],
"key": "AVgAgC1Vdo07z3PUnnkt"
},
"AVgFqeOSKpGuA48ADATE": Object {
"date": "2016-10-27T12:21:15+0200",
"events": Array [],
"key": "AVgFqeOSKpGuA48ADATE"
},
"AVgGkRvCrrTJiPpCD-rG": Object {
"date": "2016-10-27T16:33:50+0200",
"events": Array [
"AVjUDBiSiXOcXjpycvde",
"AVkWcQ8Hr4pSN7TrXcjZ"
],
"key": "AVgGkRvCrrTJiPpCD-rG"
}
}
`;

exports[`test reducer 4`] = `
Object {
"AVgAgC1Vdo07z3PUnnkt": Object {
"date": "2016-10-26T12:17:29+0200",
"events": Array [
"AVkWNYNYr4pSN7TrXcjY"
],
"key": "AVgAgC1Vdo07z3PUnnkt"
},
"AVgFqeOSKpGuA48ADATE": Object {
"date": "2016-10-27T12:21:15+0200",
"events": Array [],
"key": "AVgFqeOSKpGuA48ADATE"
},
"AVgGkRvCrrTJiPpCD-rG": Object {
"date": "2016-10-27T16:33:50+0200",
"events": Array [
"AVjUDBiSiXOcXjpycvde"
],
"key": "AVgGkRvCrrTJiPpCD-rG"
}
}
`;

exports[`test reducer 5`] = `
Object {
"AVgAgC1Vdo07z3PUnnkt": Object {
"date": "2016-10-26T12:17:29+0200",
"events": Array [
"AVkWNYNYr4pSN7TrXcjY"
],
"key": "AVgAgC1Vdo07z3PUnnkt"
},
"AVgGkRvCrrTJiPpCD-rG": Object {
"date": "2016-10-27T16:33:50+0200",
"events": Array [
"AVjUDBiSiXOcXjpycvde"
],
"key": "AVgGkRvCrrTJiPpCD-rG"
}
}
`;

+ 55
- 0
server/sonar-web/src/main/js/store/projectActivity/__tests__/__snapshots__/analysesByProject-test.js.snap Dosyayı Görüntüle

@@ -0,0 +1,55 @@
exports[`test reducer 1`] = `Object {}`;

exports[`test reducer 2`] = `
Object {
"project-foo": Array [
"AVgFqeOSKpGuA48ADATE",
"AVgAgC1Vdo07z3PUnnkt"
]
}
`;

exports[`test reducer 3`] = `
Object {
"project-foo": Array [
"AVgFqeOSKpGuA48ADATE",
"AVgAgC1Vdo07z3PUnnkt",
"AVgFqeOSKpGuA48ADATX"
]
}
`;

exports[`test reducer 4`] = `
Object {
"project-bar": Array [
"AVgGkRvCrrTJiPpCD-rG"
],
"project-foo": Array [
"AVgFqeOSKpGuA48ADATE",
"AVgAgC1Vdo07z3PUnnkt",
"AVgFqeOSKpGuA48ADATX"
]
}
`;

exports[`test reducer 5`] = `
Object {
"project-bar": Array [
"AVgGkRvCrrTJiPpCD-rG"
],
"project-foo": Array [
"AVgAgC1Vdo07z3PUnnkt",
"AVgFqeOSKpGuA48ADATX"
]
}
`;

exports[`test reducer 6`] = `
Object {
"project-bar": Array [],
"project-foo": Array [
"AVgAgC1Vdo07z3PUnnkt",
"AVgFqeOSKpGuA48ADATX"
]
}
`;

+ 75
- 0
server/sonar-web/src/main/js/store/projectActivity/__tests__/__snapshots__/duck-test.js.snap Dosyayı Görüntüle

@@ -0,0 +1,75 @@
exports[`actions addEvent 1`] = `
Object {
"analysis": "foo",
"event": Object {
"key": "bar"
},
"type": "ADD_PROJECT_ACTIVITY_EVENT"
}
`;

exports[`actions changeEvent 1`] = `
Object {
"changes": Object {
"name": "bar"
},
"event": "foo",
"type": "CHANGE_PROJECT_ACTIVITY_EVENT"
}
`;

exports[`actions deleteAnalysis 1`] = `
Object {
"analysis": "bar",
"project": "foo",
"type": "DELETE_PROJECT_ACTIVITY_ANALYSIS"
}
`;

exports[`actions deleteEvent 1`] = `
Object {
"analysis": "foo",
"event": "bar",
"type": "DELETE_PROJECT_ACTIVITY_EVENT"
}
`;

exports[`selectors getAnalyses 1`] = `
Array [
Object {
"date": "2016-10-27T16:33:50+0200",
"events": Array [
Object {
"category": "VERSION",
"key": "AVjUDBiSiXOcXjpycvde",
"name": "2.18-SNAPSHOT"
}
],
"key": "AVgGkRvCrrTJiPpCD-rG"
},
Object {
"date": "2016-10-27T12:21:15+0200",
"events": Array [],
"key": "AVgFqeOSKpGuA48ADATE"
},
Object {
"date": "2016-10-26T12:17:29+0200",
"events": Array [
Object {
"category": "OTHER",
"key": "AVkWNYNYr4pSN7TrXcjY",
"name": "foo"
}
],
"key": "AVgAgC1Vdo07z3PUnnkt"
}
]
`;

exports[`selectors getPaging 1`] = `
Object {
"pageIndex": 1,
"pageSize": 100,
"total": 3
}
`;

+ 71
- 0
server/sonar-web/src/main/js/store/projectActivity/__tests__/__snapshots__/events-test.js.snap Dosyayı Görüntüle

@@ -0,0 +1,71 @@
exports[`test reducer 1`] = `Object {}`;

exports[`test reducer 2`] = `
Object {
"AVjUDBiSiXOcXjpycvde": Object {
"category": "VERSION",
"key": "AVjUDBiSiXOcXjpycvde",
"name": "2.18-SNAPSHOT"
},
"AVkWNYNYr4pSN7TrXcjY": Object {
"category": "OTHER",
"key": "AVkWNYNYr4pSN7TrXcjY",
"name": "foo"
}
}
`;

exports[`test reducer 3`] = `
Object {
"AVjUDBiSiXOcXjpycvde": Object {
"category": "VERSION",
"key": "AVjUDBiSiXOcXjpycvde",
"name": "2.18-SNAPSHOT"
},
"AVkWNYNYr4pSN7TrXcjY": Object {
"category": "OTHER",
"key": "AVkWNYNYr4pSN7TrXcjY",
"name": "foo"
},
"AVkWcQ8Hr4pSN7TrXcjZ": Object {
"category": "OTHER",
"key": "AVkWcQ8Hr4pSN7TrXcjZ",
"name": "custom"
}
}
`;

exports[`test reducer 4`] = `
Object {
"AVjUDBiSiXOcXjpycvde": Object {
"category": "VERSION",
"key": "AVjUDBiSiXOcXjpycvde",
"name": "2.18-SNAPSHOT"
},
"AVkWNYNYr4pSN7TrXcjY": Object {
"category": "OTHER",
"key": "AVkWNYNYr4pSN7TrXcjY",
"name": "foo"
},
"AVkWcQ8Hr4pSN7TrXcjZ": Object {
"category": "OTHER",
"key": "AVkWcQ8Hr4pSN7TrXcjZ",
"name": "new name"
}
}
`;

exports[`test reducer 5`] = `
Object {
"AVjUDBiSiXOcXjpycvde": Object {
"category": "VERSION",
"key": "AVjUDBiSiXOcXjpycvde",
"name": "2.18-SNAPSHOT"
},
"AVkWNYNYr4pSN7TrXcjY": Object {
"category": "OTHER",
"key": "AVkWNYNYr4pSN7TrXcjY",
"name": "foo"
}
}
`;

+ 21
- 0
server/sonar-web/src/main/js/store/projectActivity/__tests__/__snapshots__/paging-test.js.snap Dosyayı Görüntüle

@@ -0,0 +1,21 @@
exports[`test reducer 1`] = `Object {}`;

exports[`test reducer 2`] = `
Object {
"project-foo": Object {
"pageIndex": 1,
"pageSize": 100,
"total": 3
}
}
`;

exports[`test reducer 3`] = `
Object {
"project-foo": Object {
"pageIndex": 2,
"pageSize": 30,
"total": 5
}
}
`;

+ 90
- 0
server/sonar-web/src/main/js/store/projectActivity/__tests__/analyses-test.js Dosyayı Görüntüle

@@ -0,0 +1,90 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { configureTestStore } from '../../utils/configureStore';
import analyses, { getAnalysis } from '../analyses';
import { receiveProjectActivity, addEvent, deleteEvent, deleteAnalysis } from '../duck';

const PROJECT = 'project-foo';

const ANALYSES = [
{
key: 'AVgGkRvCrrTJiPpCD-rG',
date: '2016-10-27T16:33:50+0200',
events: [
{
key: 'AVjUDBiSiXOcXjpycvde',
category: 'VERSION',
name: '2.18-SNAPSHOT'
}
]
},
{
key: 'AVgFqeOSKpGuA48ADATE',
date: '2016-10-27T12:21:15+0200',
events: []
},
{
key: 'AVgAgC1Vdo07z3PUnnkt',
date: '2016-10-26T12:17:29+0200',
events: [
{
key: 'AVkWNYNYr4pSN7TrXcjY',
category: 'OTHER',
name: 'foo'
}
]
}
];

const PAGING = {
total: 3,
pageIndex: 1,
pageSize: 100
};

const NEW_EVENT = {
key: 'AVkWcQ8Hr4pSN7TrXcjZ',
category: 'OTHER',
name: 'custom'
};

it('reducer', () => {
const store = configureTestStore(analyses);
expect(store.getState()).toMatchSnapshot();

store.dispatch(receiveProjectActivity(PROJECT, ANALYSES, PAGING));
expect(store.getState()).toMatchSnapshot();

store.dispatch(addEvent(ANALYSES[0].key, NEW_EVENT));
expect(store.getState()).toMatchSnapshot();

store.dispatch(deleteEvent(ANALYSES[0].key, NEW_EVENT.key));
expect(store.getState()).toMatchSnapshot();

store.dispatch(deleteAnalysis(PROJECT, ANALYSES[1].key));
expect(store.getState()).toMatchSnapshot();
});

it('selector `getAnalysis`', () => {
const analysis = ANALYSES[0];
const store = configureTestStore(analyses, { [analysis.key]: analysis });
expect(getAnalysis(store.getState(), analysis.key)).toBe(analysis);
expect(getAnalysis(store.getState(), 'random')).toBeFalsy();
});

+ 80
- 0
server/sonar-web/src/main/js/store/projectActivity/__tests__/analysesByProject-test.js Dosyayı Görüntüle

@@ -0,0 +1,80 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { configureTestStore } from '../../utils/configureStore';
import analysesByProject from '../analysesByProject';
import { receiveProjectActivity, deleteAnalysis } from '../duck';

const PROJECT_FOO = 'project-foo';
const PROJECT_BAR = 'project-bar';

const ANALYSES_FOO = [
{
key: 'AVgFqeOSKpGuA48ADATE',
date: '2016-10-27T12:21:15+0200',
events: []
},
{
key: 'AVgAgC1Vdo07z3PUnnkt',
date: '2016-10-26T12:17:29+0200',
events: []
}
];

const ANALYSES_FOO_2 = [
{
key: 'AVgFqeOSKpGuA48ADATX',
date: '2016-10-27T12:21:15+0200',
events: []
}
];

const ANALYSES_BAR = [
{
key: 'AVgGkRvCrrTJiPpCD-rG',
date: '2016-10-27T16:33:50+0200',
events: []
}
];

const PAGING = {
total: 3,
pageIndex: 1,
pageSize: 100
};

it('reducer', () => {
const store = configureTestStore(analysesByProject);
expect(store.getState()).toMatchSnapshot();

store.dispatch(receiveProjectActivity(PROJECT_FOO, ANALYSES_FOO, PAGING));
expect(store.getState()).toMatchSnapshot();

store.dispatch(receiveProjectActivity(PROJECT_FOO, ANALYSES_FOO_2, { pageIndex: 2 }));
expect(store.getState()).toMatchSnapshot();

store.dispatch(receiveProjectActivity(PROJECT_BAR, ANALYSES_BAR, PAGING));
expect(store.getState()).toMatchSnapshot();

store.dispatch(deleteAnalysis(PROJECT_FOO, 'AVgFqeOSKpGuA48ADATE'));
expect(store.getState()).toMatchSnapshot();

store.dispatch(deleteAnalysis(PROJECT_BAR, 'AVgGkRvCrrTJiPpCD-rG'));
expect(store.getState()).toMatchSnapshot();
});

+ 100
- 0
server/sonar-web/src/main/js/store/projectActivity/__tests__/duck-test.js Dosyayı Görüntüle

@@ -0,0 +1,100 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { configureTestStore } from '../../utils/configureStore';
import reducer, {
receiveProjectActivity,
getAnalyses,
getPaging,
addEvent,
changeEvent,
deleteEvent,
deleteAnalysis
} from '../duck';

const PROJECT = 'project-foo';

const ANALYSES = [
{
key: 'AVgGkRvCrrTJiPpCD-rG',
date: '2016-10-27T16:33:50+0200',
events: [
{
key: 'AVjUDBiSiXOcXjpycvde',
category: 'VERSION',
name: '2.18-SNAPSHOT'
}
]
},
{
key: 'AVgFqeOSKpGuA48ADATE',
date: '2016-10-27T12:21:15+0200',
events: []
},
{
key: 'AVgAgC1Vdo07z3PUnnkt',
date: '2016-10-26T12:17:29+0200',
events: [
{
key: 'AVkWNYNYr4pSN7TrXcjY',
category: 'OTHER',
name: 'foo'
}
]
}
];

const PAGING = {
total: 3,
pageIndex: 1,
pageSize: 100
};

describe('actions', () => {
it('addEvent', () => {
expect(addEvent('foo', { key: 'bar' })).toMatchSnapshot();
});

it('changeEvent', () => {
expect(changeEvent('foo', { name: 'bar' })).toMatchSnapshot();
});

it('deleteEvent', () => {
expect(deleteEvent('foo', 'bar')).toMatchSnapshot();
});

it('deleteAnalysis', () => {
expect(deleteAnalysis('foo', 'bar')).toMatchSnapshot();
});
});


describe('selectors', () => {
it('getAnalyses', () => {
const store = configureTestStore(reducer);
store.dispatch(receiveProjectActivity(PROJECT, ANALYSES, PAGING));
expect(getAnalyses(store.getState(), PROJECT)).toMatchSnapshot();
});

it('getPaging', () => {
const store = configureTestStore(reducer);
store.dispatch(receiveProjectActivity(PROJECT, ANALYSES, PAGING));
expect(getPaging(store.getState(), PROJECT)).toMatchSnapshot();
});
});

+ 90
- 0
server/sonar-web/src/main/js/store/projectActivity/__tests__/events-test.js Dosyayı Görüntüle

@@ -0,0 +1,90 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { configureTestStore } from '../../utils/configureStore';
import events, { getEvent } from '../events';
import { receiveProjectActivity, addEvent, changeEvent, deleteEvent } from '../duck';

const PROJECT = 'project-foo';

const ANALYSES = [
{
key: 'AVgGkRvCrrTJiPpCD-rG',
date: '2016-10-27T16:33:50+0200',
events: [
{
key: 'AVjUDBiSiXOcXjpycvde',
category: 'VERSION',
name: '2.18-SNAPSHOT'
}
]
},
{
key: 'AVgFqeOSKpGuA48ADATE',
date: '2016-10-27T12:21:15+0200',
events: []
},
{
key: 'AVgAgC1Vdo07z3PUnnkt',
date: '2016-10-26T12:17:29+0200',
events: [
{
key: 'AVkWNYNYr4pSN7TrXcjY',
category: 'OTHER',
name: 'foo'
}
]
}
];

const PAGING = {
total: 3,
pageIndex: 1,
pageSize: 100
};

const NEW_EVENT = {
key: 'AVkWcQ8Hr4pSN7TrXcjZ',
category: 'OTHER',
name: 'custom'
};

it('reducer', () => {
const store = configureTestStore(events);
expect(store.getState()).toMatchSnapshot();

store.dispatch(receiveProjectActivity(PROJECT, ANALYSES, PAGING));
expect(store.getState()).toMatchSnapshot();

store.dispatch(addEvent(ANALYSES[0].key, NEW_EVENT));
expect(store.getState()).toMatchSnapshot();

store.dispatch(changeEvent(NEW_EVENT.key, { name: 'new name' }));
expect(store.getState()).toMatchSnapshot();

store.dispatch(deleteEvent(ANALYSES[0].key, NEW_EVENT.key));
expect(store.getState()).toMatchSnapshot();
});

it('selector `getEvent`', () => {
const event = ANALYSES[0].events[0];
const store = configureTestStore(events, { [event.key]: event });
expect(getEvent(store.getState(), event.key)).toBe(event);
expect(getEvent(store.getState(), 'random')).toBeFalsy();
});

+ 49
- 0
server/sonar-web/src/main/js/store/projectActivity/__tests__/paging-test.js Dosyayı Görüntüle

@@ -0,0 +1,49 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { configureTestStore } from '../../utils/configureStore';
import paging from '../paging';
import { receiveProjectActivity } from '../duck';

const PROJECT = 'project-foo';

const ANALYSES = [];

const PAGING_1 = {
total: 3,
pageIndex: 1,
pageSize: 100
};

const PAGING_2 = {
total: 5,
pageIndex: 2,
pageSize: 30
};

it('reducer', () => {
const store = configureTestStore(paging);
expect(store.getState()).toMatchSnapshot();

store.dispatch(receiveProjectActivity(PROJECT, ANALYSES, PAGING_1));
expect(store.getState()).toMatchSnapshot();

store.dispatch(receiveProjectActivity(PROJECT, ANALYSES, PAGING_2));
expect(store.getState()).toMatchSnapshot();
});

+ 89
- 0
server/sonar-web/src/main/js/store/projectActivity/analyses.js Dosyayı Görüntüle

@@ -0,0 +1,89 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 keyBy from 'lodash/keyBy';
import type {
Action,
ReceiveProjectActivityAction,
AddEventAction,
DeleteEventAction,
DeleteAnalysisAction
} from './duck';

type Analysis = {
key: string;
date: string;
events: Array<string>
};

export type State = {
[key: string]: Analysis
};

const receiveProjectActivity = (state: State, action: ReceiveProjectActivityAction): State => {
const analysesWithFlatEvents = action.analyses.map(analysis => ({
...analysis,
events: analysis.events.map(event => event.key)
}));
return { ...state, ...keyBy(analysesWithFlatEvents, 'key') };
};

const addEvent = (state: State, action: AddEventAction): State => {
const analysis = state[action.analysis];
const newAnalysis = {
...analysis,
events: [...analysis.events, action.event.key]
};
return { ...state, [action.analysis]: newAnalysis };
};

const deleteEvent = (state: State, action: DeleteEventAction): State => {
const analysis = state[action.analysis];
const newAnalysis = {
...analysis,
events: analysis.events.filter(event => event !== action.event)
};
return { ...state, [action.analysis]: newAnalysis };
};

const deleteAnalysis = (state: State, action: DeleteAnalysisAction): State => {
const newState = { ...state };
delete newState[action.analysis];
return newState;
};

export default (state: State = {}, action: Action): State => {
switch (action.type) {
case 'RECEIVE_PROJECT_ACTIVITY':
return receiveProjectActivity(state, action);
case 'ADD_PROJECT_ACTIVITY_EVENT':
return addEvent(state, action);
case 'DELETE_PROJECT_ACTIVITY_EVENT':
return deleteEvent(state, action);
case 'DELETE_PROJECT_ACTIVITY_ANALYSIS':
return deleteAnalysis(state, action);
default:
return state;
}
};

export const getAnalysis = (state: State, key: string): Analysis => (
state[key]
);

+ 53
- 0
server/sonar-web/src/main/js/store/projectActivity/analysesByProject.js Dosyayı Görüntüle

@@ -0,0 +1,53 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 type { Action, ReceiveProjectActivityAction, DeleteAnalysisAction } from './duck';

export type State = {
[key: string]: Array<string>
};

const receiveProjectActivity = (state: State, action: ReceiveProjectActivityAction): State => {
const analyses = state[action.project] || [];
const newAnalyses = action.analyses.map(analysis => analysis.key);
return {
...state,
[action.project]: action.paging.pageIndex === 1 ? newAnalyses : [...analyses, ...newAnalyses]
};
};

const deleteAnalysis = (state: State, action: DeleteAnalysisAction): State => {
const analyses = state[action.project];
return {
...state,
[action.project]: analyses.filter(key => key !== action.analysis)
};
};

export default (state: State = {}, action: Action): State => {
switch (action.type) {
case 'RECEIVE_PROJECT_ACTIVITY':
return receiveProjectActivity(state, action);
case 'DELETE_PROJECT_ACTIVITY_ANALYSIS':
return deleteAnalysis(state, action);
default:
return state;
}
};

+ 148
- 0
server/sonar-web/src/main/js/store/projectActivity/duck.js Dosyayı Görüntüle

@@ -0,0 +1,148 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { combineReducers } from 'redux';
import analyses, * as fromAnalyses from './analyses';
import type { State as AnalysesState } from './analyses';
import analysesByProject from './analysesByProject';
import type { State as AnalysesByProjectState } from './analysesByProject';
import events, * as fromEvents from './events';
import type { State as EventsState } from './events';
import paging from './paging';
import type { State as PagingState } from './paging';

export type Event = {
key: string,
name: string;
category: string;
description?: string;
};

export type Analysis = {
key: string;
date: string;
events: Array<Event>
};

export type Paging = {
total: number,
pageIndex: number,
pageSize: number
};

export type ReceiveProjectActivityAction = {
type: 'RECEIVE_PROJECT_ACTIVITY',
project: string,
analyses: Array<Analysis>,
paging: Paging
};

export type AddEventAction = {
type: 'ADD_PROJECT_ACTIVITY_EVENT',
analysis: string,
event: Event
};

export type DeleteEventAction = {
type: 'DELETE_PROJECT_ACTIVITY_EVENT',
analysis: string,
event: string
};

export type ChangeEventAction = {
type: 'CHANGE_PROJECT_ACTIVITY_EVENT',
event: string,
changes: Object
};

export type DeleteAnalysisAction = {
type: 'DELETE_PROJECT_ACTIVITY_ANALYSIS',
project: string,
analysis: string
};

export type Action =
ReceiveProjectActivityAction |
AddEventAction |
DeleteEventAction |
ChangeEventAction |
DeleteAnalysisAction;

export const receiveProjectActivity = (
project: string,
analyses: Array<Analysis>,
paging: Paging
): ReceiveProjectActivityAction => ({
type: 'RECEIVE_PROJECT_ACTIVITY',
project,
analyses,
paging
});

export const addEvent = (analysis: string, event: Event): AddEventAction => ({
type: 'ADD_PROJECT_ACTIVITY_EVENT',
analysis,
event
});

export const deleteEvent = (analysis: string, event: string): DeleteEventAction => ({
type: 'DELETE_PROJECT_ACTIVITY_EVENT',
analysis,
event
});

export const changeEvent = (event: string, changes: Object): ChangeEventAction => ({
type: 'CHANGE_PROJECT_ACTIVITY_EVENT',
event,
changes
});

export const deleteAnalysis = (project: string, analysis: string): DeleteAnalysisAction => ({
type: 'DELETE_PROJECT_ACTIVITY_ANALYSIS',
project,
analysis
});

type State = {
analyses: AnalysesState,
analysesByProject: AnalysesByProjectState,
events: EventsState,
filter: string,
paging: PagingState,
};

export default combineReducers({ analyses, analysesByProject, events, paging });

const getEvent = (state: State, key: string): Event => (
fromEvents.getEvent(state.events, key)
);

const getAnalysis = (state: State, key: string) => {
const analysis = fromAnalyses.getAnalysis(state.analyses, key);
const events: Array<Event> = analysis.events.map(key => getEvent(state, key));
return { ...analysis, events };
};

export const getAnalyses = (state: State, project: string) => (
state.analysesByProject[project] && state.analysesByProject[project].map(key => getAnalysis(state, key))
);
export const getPaging = (state: State, project: string) => (
state.paging[project]
);

+ 79
- 0
server/sonar-web/src/main/js/store/projectActivity/events.js Dosyayı Görüntüle

@@ -0,0 +1,79 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 keyBy from 'lodash/keyBy';
import type {
Action,
ReceiveProjectActivityAction,
AddEventAction,
DeleteEventAction,
ChangeEventAction
} from './duck';

export type State = {
[key: string]: {
key: string,
name: string;
category: string;
description?: string;
}
};

const receiveProjectActivity = (state: State, action: ReceiveProjectActivityAction): State => {
const events = {};
action.analyses.forEach(analysis => {
Object.assign(events, keyBy(analysis.events, 'key'));
});
return { ...state, ...events };
};

const addEvent = (state: State, action: AddEventAction): State => {
return { ...state, [action.event.key]: action.event };
};

const deleteEvent = (state: State, action: DeleteEventAction): State => {
const newState = { ...state };
delete newState[action.event];
return newState;
};

const changeEvent = (state: State, action: ChangeEventAction): State => {
const newEvent = { ...state[action.event], ...action.changes };
return { ...state, [action.event]: newEvent };
};

export default (state: State = {}, action: Action): State => {
switch (action.type) {
case 'RECEIVE_PROJECT_ACTIVITY':
return receiveProjectActivity(state, action);
case 'ADD_PROJECT_ACTIVITY_EVENT':
return addEvent(state, action);
case 'DELETE_PROJECT_ACTIVITY_EVENT':
return deleteEvent(state, action);
case 'CHANGE_PROJECT_ACTIVITY_EVENT':
return changeEvent(state, action);
default:
return state;
}
};

export const getEvent = (state: State, key: string) => (
state[key]
);

+ 34
- 0
server/sonar-web/src/main/js/store/projectActivity/paging.js Dosyayı Görüntüle

@@ -0,0 +1,34 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 type { Paging, ReceiveProjectActivityAction } from './duck';

export type State = {
[key: string]: Paging
};

export default (state: State = {}, action: ReceiveProjectActivityAction): State => {
if (action.type === 'RECEIVE_PROJECT_ACTIVITY') {
return { ...state, [action.project]: action.paging };
}

return state;
};


+ 1
- 1
server/sonar-web/src/main/js/store/rootActions.js Dosyayı Görüntüle

@@ -26,7 +26,7 @@ import { addGlobalErrorMessage } from './globalMessages/duck';
import { parseError } from '../apps/code/utils';
import { setAppState } from './appState/duck';

const onFail = dispatch => error => (
export const onFail = dispatch => error => (
parseError(error).then(message => dispatch(addGlobalErrorMessage(message)))
);


+ 6
- 0
server/sonar-web/src/main/js/store/rootReducer.js Dosyayı Görüntüle

@@ -25,6 +25,7 @@ import favorites, * as fromFavorites from './favorites/duck';
import languages, * as fromLanguages from './languages/reducer';
import measures, * as fromMeasures from './measures/reducer';
import globalMessages, * as fromGlobalMessages from './globalMessages/duck';
import projectActivity from './projectActivity/duck';

import measuresApp, * as fromMeasuresApp from '../apps/component-measures/store/rootReducer';
import permissionsApp, * as fromPermissionsApp from '../apps/permissions/shared/store/rootReducer';
@@ -40,6 +41,7 @@ export default combineReducers({
favorites,
languages,
measures,
projectActivity,
users,

// apps
@@ -83,6 +85,10 @@ export const getComponentMeasures = (state, componentKey) => (
fromMeasures.getComponentMeasures(state.measures, componentKey)
);

export const getProjectActivity = state => (
state.projectActivity
);

export const getProjects = state => (
fromProjectsApp.getProjects(state.projectsApp)
);

+ 4
- 0
server/sonar-web/src/main/js/store/utils/configureStore.js Dosyayı Görüntüle

@@ -38,3 +38,7 @@ const finalCreateStore = compose(
export default function configureStore (rootReducer, initialState) {
return finalCreateStore(rootReducer, initialState);
}

export const configureTestStore = (rootReducer, initialState) => (
createStore(rootReducer, initialState)
);

+ 18
- 10
server/sonar-web/src/main/less/components/modals.less Dosyayı Görüntüle

@@ -20,7 +20,8 @@
@import (reference) "../mixins";
@import (reference) "../variables";

.modal {
.modal,
.ReactModal__Content {
position: fixed;
z-index: @modal-z-index;
top: 0;
@@ -32,7 +33,8 @@
transition: all 0.2s ease;
}

.modal.in {
.modal.in,
.ReactModal__Content--after-open {
top: 15%;
opacity: 1;
}
@@ -42,20 +44,26 @@
margin-left: -45vw;
}

.modal-overlay {
.modal-overlay,
.ReactModal__Overlay {
position: fixed;
z-index: @modal-overlay-z-index;
top: 0; bottom: 0; left: 0; right: 0;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.7);
opacity: 0;
transition: all 0.2s ease;
}

.modal-overlay.in {
.modal-overlay.in,
.ReactModal__Overlay--after-open {
opacity: 1;
}

.modal-open {
.modal-open,
.ReactModal__Body--open {
overflow: hidden;
}

@@ -69,8 +77,8 @@

.modal-head {
padding: 0 10px;
background-color: #EFEFEF;
border-bottom: 1px solid #DDD;
background-color: #efefef;
border-bottom: 1px solid #ddd;
}

.modal-head h1, .modal-head h2 {
@@ -167,9 +175,9 @@ ul.modal-head-metadata li {
.modal-foot {
text-align: right;
padding: 8px 10px;
border-top: 1px solid #CCC;
border-top: 1px solid #ccc;
line-height: 30px;
background-color: #EFEFEF;
background-color: #efefef;

button,
.button,

+ 17
- 1
server/sonar-web/src/main/less/init/forms.less Dosyayı Görüntüle

@@ -152,7 +152,6 @@ input[type="submit"].button-success {
.button-clean,
.button-clean:hover,
.button-clean:focus {
margin: 0;
padding: 0;
line-height: 1;
border: none;
@@ -161,6 +160,14 @@ input[type="submit"].button-success {
color: @baseFontColor;
}

.button-clean path {
transition: opacity 0.3s ease;
}

.button-clean:hover path {
opacity: 0.8;
}

.button-link {
display: inline;
height: auto;
@@ -189,6 +196,15 @@ input[type="submit"].button-success {
}
}

.button-small {
height: 20px;
line-height: 18px;

& > svg {
margin-top: 2px;
}
}

.button-group {
display: inline-block;
vertical-align: middle;

+ 240
- 391
server/sonar-web/yarn.lock
Dosya farkı çok büyük olduğundan ihmal edildi
Dosyayı Görüntüle


+ 19
- 6
sonar-core/src/main/resources/org/sonar/l10n/core.properties Dosyayı Görüntüle

@@ -438,10 +438,10 @@ project_links.url=URL
#------------------------------------------------------------------------------

event.category.All=All
event.category.Version=Version
event.category.Alert=Quality Gate
event.category.Profile=Quality Profile
event.category.Other=Other
event.category.VERSION=Version
event.category.QUALITY_GATE=Quality Gate
event.category.QUALITY_PROFILE=Quality Profile
event.category.OTHER=Other


#------------------------------------------------------------------------------
@@ -552,7 +552,8 @@ source.page=Source
timemachine.page=Time Machine
comparison.page=Compare
view_projects.page=Projects

project_activity.page=Activity
project_activity.page.description=The page shows the history of project analyses.

#------------------------------------------------------------------------------
#
@@ -1150,10 +1151,22 @@ manual_rules.add_manual_rule=Add Manual Rule

#------------------------------------------------------------------------------
#
# PROJECT HISTORY SERVICE
# PROJECT ACTIVITY/HISTORY SERVICE
#
#------------------------------------------------------------------------------

project_activity.project_analyzed=Project Analyzed
project_activity.add_version=Create Version
project_activity.remove_version=Remove Version
project_activity.remove_version.question=Are you sure you want to delete this version?
project_activity.change_version=Change Version
project_activity.add_custom_event=Create Custom Event
project_activity.change_custom_event=Change Event
project_activity.remove_custom_event=Delete Event
project_activity.remove_custom_event.question=Are you sure you want to delete this event?
project_activity.delete_analysis=Delete Analysis
project_activity.delete_analysis.question=Are you sure you want to delete this analysis from the project history?

project_history.col.year=Year
project_history.col.month=Month
project_history.col.day=Day

Loading…
İptal
Kaydet