aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2015-03-06 10:44:45 +0100
committerStas Vilchik <vilchiks@gmail.com>2015-03-06 10:44:45 +0100
commit9f6c589ed8c8c03d4f93bad772a91941a2f62cb3 (patch)
tree21d7bb776c6e119dd0f5c1db35bce5cbc7c10c10
parent1432f744dbff4834badc39fd66fe19268712a1a5 (diff)
parent4cbfea9653a895bafb1ee7d2de521b7d78345320 (diff)
downloadsonarqube-9f6c589ed8c8c03d4f93bad772a91941a2f62cb3.tar.gz
sonarqube-9f6c589ed8c8c03d4f93bad772a91941a2f62cb3.zip
Merge remote-tracking branch 'remotes/origin/feature/workspace' into branch-5.2
Conflicts: server/sonar-web/Gruntfile.coffee server/sonar-web/src/main/hbs/source-viewer/source-viewer-duplication-popup.hbs server/sonar-web/src/main/less/components.less
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java18
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/test/ws/TestsTestCasesAction.java41
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsJsonWriterTest.java6
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsTestCasesActionTest.java126
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsWsTest.java6
-rw-r--r--server/sonar-web/Gruntfile.coffee3
-rw-r--r--server/sonar-web/src/main/hbs/source-viewer/source-viewer-coverage-popup.hbs4
-rw-r--r--server/sonar-web/src/main/hbs/source-viewer/source-viewer-duplication-popup.hbs4
-rw-r--r--server/sonar-web/src/main/hbs/source-viewer/source-viewer-more-actions.hbs2
-rw-r--r--server/sonar-web/src/main/hbs/workspace/workspace-item.hbs7
-rw-r--r--server/sonar-web/src/main/hbs/workspace/workspace-items.hbs1
-rw-r--r--server/sonar-web/src/main/hbs/workspace/workspace-viewer-header.hbs8
-rw-r--r--server/sonar-web/src/main/hbs/workspace/workspace-viewer.hbs3
-rw-r--r--server/sonar-web/src/main/js/components/navigator/workspace-list-view.js9
-rw-r--r--server/sonar-web/src/main/js/nav/app.js3
-rw-r--r--server/sonar-web/src/main/js/source-viewer/more-actions.js12
-rw-r--r--server/sonar-web/src/main/js/source-viewer/popups/coverage-popup.js14
-rw-r--r--server/sonar-web/src/main/js/source-viewer/popups/duplication-popup.js17
-rw-r--r--server/sonar-web/src/main/js/source-viewer/viewer.js17
-rw-r--r--server/sonar-web/src/main/js/workspace/main.js114
-rw-r--r--server/sonar-web/src/main/js/workspace/models/item.js37
-rw-r--r--server/sonar-web/src/main/js/workspace/models/items.js47
-rw-r--r--server/sonar-web/src/main/js/workspace/views/item-view.js46
-rw-r--r--server/sonar-web/src/main/js/workspace/views/items-view.js36
-rw-r--r--server/sonar-web/src/main/js/workspace/views/viewer-header-view.js68
-rw-r--r--server/sonar-web/src/main/js/workspace/views/viewer-view.js64
-rw-r--r--server/sonar-web/src/main/less/components.less1
-rw-r--r--server/sonar-web/src/main/less/components/source.less1
-rw-r--r--server/sonar-web/src/main/less/components/workspace.less111
-rw-r--r--server/sonar-web/src/main/less/init/icons.less12
-rw-r--r--server/sonar-web/src/main/less/init/type.less9
-rw-r--r--server/sonar-web/src/main/less/variables.less8
-rwxr-xr-xserver/sonar-web/src/main/webapp/fonts/sonar-5.1.eotbin17092 -> 17564 bytes
-rwxr-xr-xserver/sonar-web/src/main/webapp/fonts/sonar-5.1.svg3
-rwxr-xr-xserver/sonar-web/src/main/webapp/fonts/sonar-5.1.ttfbin16928 -> 17400 bytes
-rwxr-xr-xserver/sonar-web/src/main/webapp/fonts/sonar-5.1.woffbin17004 -> 17476 bytes
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties1
38 files changed, 779 insertions, 83 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java
index 3ae866f26b8..4d50a8a254e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/DuplicationsJsonWriter.java
@@ -105,18 +105,21 @@ public class DuplicationsJsonWriter implements ServerComponent {
private void addFile(JsonWriter json, ComponentDto file) {
json.prop("key", file.key());
+ json.prop("uuid", file.uuid());
json.prop("name", file.longName());
}
private void addProject(JsonWriter json, @Nullable ComponentDto project, @Nullable ComponentDto subProject) {
if (project != null) {
json.prop("project", project.key());
+ json.prop("projectUuid", project.uuid());
json.prop("projectName", project.longName());
// Do not return sub project if sub project and project are the same
boolean displaySubProject = subProject != null && !subProject.getId().equals(project.getId());
if (displaySubProject) {
json.prop("subProject", subProject.key());
+ json.prop("subProjectUuid", subProject.uuid());
json.prop("subProjectName", subProject.longName());
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java
index 4b3f6f69d6f..e3d309a57cc 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/duplication/ws/ShowAction.java
@@ -20,6 +20,7 @@
package org.sonar.server.duplication.ws;
+import com.google.common.base.Preconditions;
import com.google.common.io.Resources;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.server.ws.Request;
@@ -68,17 +69,28 @@ public class ShowAction implements RequestHandler {
action
.createParam("key")
- .setRequired(true)
.setDescription("File key")
.setExampleValue("my_project:/src/foo/Bar.php");
+
+ action
+ .createParam("uuid")
+ .setDescription("File UUID")
+ .setExampleValue("584a89f2-8037-4f7b-b82c-8b45d2d63fb2");
}
@Override
public void handle(Request request, Response response) {
- String fileKey = request.mandatoryParam("key");
- UserSession.get().checkComponentPermission(UserRole.CODEVIEWER, fileKey);
+ String fileKey = request.param("key");
+ String fileUuid = request.param("uuid");
+ Preconditions.checkArgument(fileKey != null || fileUuid != null, "At least one of 'key' or 'uuid' must be provided");
DbSession session = dbClient.openSession(false);
+ if (fileKey == null) {
+ fileKey = componentDao.getByUuid(session, fileUuid).key();
+ }
+
+ UserSession.get().checkComponentPermission(UserRole.CODEVIEWER, fileKey);
+
try {
ComponentDto component = findComponent(fileKey, session);
JsonWriter json = response.newJsonWriter().beginObject();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/test/ws/TestsTestCasesAction.java b/server/sonar-server/src/main/java/org/sonar/server/test/ws/TestsTestCasesAction.java
index ca381b4e438..f4fa253b13c 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/test/ws/TestsTestCasesAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/test/ws/TestsTestCasesAction.java
@@ -20,6 +20,7 @@
package org.sonar.server.test.ws;
+import com.google.common.base.Preconditions;
import com.google.common.io.Resources;
import org.sonar.api.component.Component;
import org.sonar.api.server.ws.Request;
@@ -31,7 +32,11 @@ import org.sonar.api.test.TestCase;
import org.sonar.api.test.Testable;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.api.web.UserRole;
+import org.sonar.core.component.ComponentDto;
import org.sonar.core.component.SnapshotPerspectives;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.server.component.ComponentService;
+import org.sonar.server.db.DbClient;
import org.sonar.server.user.UserSession;
import java.util.Map;
@@ -41,12 +46,17 @@ import static com.google.common.collect.Maps.newHashMap;
public class TestsTestCasesAction implements RequestHandler {
private static final String KEY = "key";
+ private static final String UUID = "uuid";
private static final String LINE = "line";
private final SnapshotPerspectives snapshotPerspectives;
+ private final ComponentService componentService;
+ private final DbClient dbClient;
- public TestsTestCasesAction(SnapshotPerspectives snapshotPerspectives) {
+ public TestsTestCasesAction(SnapshotPerspectives snapshotPerspectives, ComponentService componentService, DbClient dbClient) {
this.snapshotPerspectives = snapshotPerspectives;
+ this.componentService = componentService;
+ this.dbClient = dbClient;
}
void define(WebService.NewController controller) {
@@ -58,11 +68,15 @@ public class TestsTestCasesAction implements RequestHandler {
action
.createParam(KEY)
- .setRequired(true)
.setDescription("File key")
.setExampleValue("my_project:/src/foo/Bar.php");
action
+ .createParam(UUID)
+ .setDescription("File UUID")
+ .setExampleValue("584a89f2-8037-4f7b-b82c-8b45d2d63fb2");
+
+ action
.createParam(LINE)
.setRequired(true)
.setDescription("Line of the file used to get test cases")
@@ -71,8 +85,17 @@ public class TestsTestCasesAction implements RequestHandler {
@Override
public void handle(Request request, Response response) {
- String fileKey = request.mandatoryParam(KEY);
- UserSession.get().checkComponentPermission(UserRole.CODEVIEWER, fileKey);
+ String fileKey = request.param(KEY);
+ String fileUuid = request.param(UUID);
+ Preconditions.checkArgument(fileKey != null || fileUuid != null, "At least one of 'key' or 'uuid' must be provided");
+
+ if (fileKey != null) {
+ UserSession.get().checkComponentPermission(UserRole.CODEVIEWER, fileKey);
+ } else {
+ ComponentDto component = componentService.getByUuid(fileUuid);
+ fileKey = component.getKey();
+ UserSession.get().checkComponentPermission(UserRole.CODEVIEWER, fileKey);
+ }
int line = request.mandatoryParamAsInt(LINE);
Testable testable = snapshotPerspectives.as(MutableTestable.class, fileKey);
@@ -108,6 +131,15 @@ public class TestsTestCasesAction implements RequestHandler {
}
private void writeFiles(Map<String, Integer> refByTestPlan, Map<String, Component> componentsByKey, JsonWriter json) {
+ DbSession session = dbClient.openSession(false);
+ try {
+ for (ComponentDto componentDto : dbClient.componentDao().getByKeys(session, componentsByKey.keySet())) {
+ componentsByKey.put(componentDto.key(), componentDto);
+ }
+ } finally {
+ session.close();
+ }
+
json.name("files").beginObject();
for (Map.Entry<String, Integer> entry : refByTestPlan.entrySet()) {
String componentKey = entry.getKey();
@@ -115,6 +147,7 @@ public class TestsTestCasesAction implements RequestHandler {
Component file = componentsByKey.get(componentKey);
json.name(Integer.toString(ref)).beginObject();
json.prop("key", file.key());
+ json.prop("uuid", ((ComponentDto) file).uuid());
json.prop("longName", file.longName());
json.endObject();
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsJsonWriterTest.java b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsJsonWriterTest.java
index 65b3ac7f3b9..73df3a7c827 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsJsonWriterTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/duplication/ws/DuplicationsJsonWriterTest.java
@@ -40,7 +40,9 @@ import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class DuplicationsJsonWriterTest {
@@ -227,7 +229,7 @@ public class DuplicationsJsonWriterTest {
jsonWriter.beginObject();
writer.write(blocks, jsonWriter, session);
jsonWriter.endObject();
- JSONAssert.assertEquals(output.toString(), expected, true);
+ JSONAssert.assertEquals(expected, output.toString(), false);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsTestCasesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsTestCasesActionTest.java
index 5c5eb7eaae8..3e8f5a9f442 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsTestCasesActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsTestCasesActionTest.java
@@ -23,6 +23,7 @@ package org.sonar.server.test.ws;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.sonar.api.test.MutableTestable;
@@ -30,7 +31,12 @@ import org.sonar.api.test.TestCase;
import org.sonar.api.test.TestPlan;
import org.sonar.api.web.UserRole;
import org.sonar.core.component.ComponentDto;
+import org.sonar.core.component.ComponentVertex;
import org.sonar.core.component.SnapshotPerspectives;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.server.component.ComponentService;
+import org.sonar.server.component.db.ComponentDao;
+import org.sonar.server.db.DbClient;
import org.sonar.server.user.MockUserSession;
import org.sonar.server.ws.WsTester;
@@ -41,66 +47,122 @@ import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class TestsTestCasesActionTest {
- static final String FILE_KEY = "src/main/java/org/foo/Foo.java";
+ private static final String FILE_KEY = "src/main/java/org/foo/Foo.java";
+
+ private static final String EXPECTED_JSON = "{\n" +
+ " \"tests\": [\n" +
+ " {\n" +
+ " \"name\": \"test1\",\n" +
+ " \"status\": \"OK\",\n" +
+ " \"durationInMs\": 10,\n" +
+ " \"_ref\": \"1\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"test2\",\n" +
+ " \"status\": \"ERROR\",\n" +
+ " \"durationInMs\": 97,\n" +
+ " \"_ref\": \"2\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"files\": {\n" +
+ " \"1\": {\n" +
+ " \"key\": \"org.foo.BarTest.java\",\n" +
+ " \"uuid\": \"ABCD\",\n" +
+ " \"longName\": \"src/test/java/org/foo/BarTest.java\"\n" +
+ " },\n" +
+ " \"2\": {\n" +
+ " \"key\": \"org.foo.FileTest.java\",\n" +
+ " \"uuid\": \"BCDE\",\n" +
+ " \"longName\": \"src/test/java/org/foo/FileTest.java\"\n" +
+ " }\n" +
+ " }\n" +
+ "}";
+
@Mock
MutableTestable testable;
+ @Mock
+ ComponentService componentService;
+
+ @Mock
+ DbClient dbClient;
+
+ @Mock
+ ComponentDao componentDao;
+
WsTester tester;
@Before
public void setUp() throws Exception {
SnapshotPerspectives snapshotPerspectives = mock(SnapshotPerspectives.class);
when(snapshotPerspectives.as(MutableTestable.class, FILE_KEY)).thenReturn(testable);
- tester = new WsTester(new TestsWs(mock(TestsShowAction.class), new TestsTestCasesAction(snapshotPerspectives), mock(TestsCoveredFilesAction.class)));
+ when(dbClient.componentDao()).thenReturn(componentDao);
+ when(dbClient.openSession(false)).thenReturn(mock(DbSession.class));
+ tester = new WsTester(new TestsWs(mock(TestsShowAction.class), new TestsTestCasesAction(snapshotPerspectives, componentService, dbClient), mock(TestsCoveredFilesAction.class)));
}
@Test
public void testable() throws Exception {
MockUserSession.set().addComponentPermission(UserRole.CODEVIEWER, "SonarQube", FILE_KEY);
- TestCase testCase1 = testCase("test1", TestCase.Status.OK, 10L, "org.foo.BarTest.java", "src/test/java/org/foo/BarTest.java");
- TestCase testCase2 = testCase("test2", TestCase.Status.ERROR, 97L, "org.foo.FileTest.java", "src/test/java/org/foo/FileTest.java");
+ String key1 = "org.foo.BarTest.java";
+ String name1 = "src/test/java/org/foo/BarTest.java";
+ String uuid1 = "ABCD";
+ TestCase testCase1 = testCase("test1", TestCase.Status.OK, 10L, key1);
+ ComponentDto component1 = new ComponentDto().setKey(key1).setLongName(name1).setUuid(uuid1);
+ String key2 = "org.foo.FileTest.java";
+ String name2 = "src/test/java/org/foo/FileTest.java";
+ String uuid2 = "BCDE";
+ TestCase testCase2 = testCase("test2", TestCase.Status.ERROR, 97L, key2);
+ ComponentDto component2 = new ComponentDto().setKey(key2).setLongName(name2).setUuid(uuid2);
when(testable.testCasesOfLine(10)).thenReturn(newArrayList(testCase1, testCase2));
+ when(componentDao.getByKeys(Matchers.isA(DbSession.class), Matchers.anyCollectionOf(String.class))).thenReturn(newArrayList(component1, component2));
WsTester.TestRequest request = tester.newGetRequest("api/tests", "test_cases").setParam("key", FILE_KEY).setParam("line", "10");
- request.execute().assertJson("{\n" +
- " \"tests\": [\n" +
- " {\n" +
- " \"name\": \"test1\",\n" +
- " \"status\": \"OK\",\n" +
- " \"durationInMs\": 10,\n" +
- " \"_ref\": \"1\"\n" +
- " },\n" +
- " {\n" +
- " \"name\": \"test2\",\n" +
- " \"status\": \"ERROR\",\n" +
- " \"durationInMs\": 97,\n" +
- " \"_ref\": \"2\"\n" +
- " }\n" +
- " ],\n" +
- " \"files\": {\n" +
- " \"1\": {\n" +
- " \"key\": \"org.foo.BarTest.java\",\n" +
- " \"longName\": \"src/test/java/org/foo/BarTest.java\"\n" +
- " },\n" +
- " \"2\": {\n" +
- " \"key\": \"org.foo.FileTest.java\",\n" +
- " \"longName\": \"src/test/java/org/foo/FileTest.java\"\n" +
- " }\n" +
- " }\n" +
- "}");
+ request.execute().assertJson(EXPECTED_JSON);
+ }
+
+ @Test
+ public void testable_with_uuid() throws Exception {
+ String uuid = "1234";
+ when(componentService.getByUuid(uuid)).thenReturn(new ComponentDto().setKey(FILE_KEY));
+ MockUserSession.set().addComponentPermission(UserRole.CODEVIEWER, "SonarQube", FILE_KEY);
+
+ String key1 = "org.foo.BarTest.java";
+ String name1 = "src/test/java/org/foo/BarTest.java";
+ String uuid1 = "ABCD";
+ TestCase testCase1 = testCase("test1", TestCase.Status.OK, 10L, key1);
+ ComponentDto component1 = new ComponentDto().setKey(key1).setLongName(name1).setUuid(uuid1);
+ String key2 = "org.foo.FileTest.java";
+ String name2 = "src/test/java/org/foo/FileTest.java";
+ String uuid2 = "BCDE";
+ TestCase testCase2 = testCase("test2", TestCase.Status.ERROR, 97L, key2);
+ ComponentDto component2 = new ComponentDto().setKey(key2).setLongName(name2).setUuid(uuid2);
+ when(testable.testCasesOfLine(10)).thenReturn(newArrayList(testCase1, testCase2));
+ when(componentDao.getByKeys(Matchers.isA(DbSession.class), Matchers.anyCollectionOf(String.class))).thenReturn(newArrayList(component1, component2));
+
+ WsTester.TestRequest request = tester.newGetRequest("api/tests", "test_cases").setParam("uuid", uuid).setParam("line", "10");
+
+ request.execute().assertJson(EXPECTED_JSON);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void fail_on_missing_parameters() throws Exception {
+ tester.newGetRequest("api/tests", "test_cases").execute();
}
- private TestCase testCase(String name, TestCase.Status status, Long durationInMs, String testPlanKey, String testPlanLongName) {
+ private TestCase testCase(String name, TestCase.Status status, Long durationInMs, String testPlanKey) {
TestCase testCase = mock(TestCase.class);
when(testCase.name()).thenReturn(name);
when(testCase.status()).thenReturn(status);
when(testCase.durationInMs()).thenReturn(durationInMs);
TestPlan testPlan = mock(TestPlan.class);
- when(testPlan.component()).thenReturn(new ComponentDto().setKey(testPlanKey).setLongName(testPlanLongName));
+ ComponentVertex component = mock(ComponentVertex.class);
+ when(component.key()).thenReturn(testPlanKey);
+ when(testPlan.component()).thenReturn(component);
when(testCase.testPlan()).thenReturn(testPlan);
return testCase;
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsWsTest.java
index 9d3f4ce3b25..69453884513 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsWsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsWsTest.java
@@ -24,6 +24,7 @@ import org.junit.Before;
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
import org.sonar.core.component.SnapshotPerspectives;
+import org.sonar.server.component.ComponentService;
import org.sonar.server.db.DbClient;
import org.sonar.server.ws.WsTester;
@@ -37,7 +38,10 @@ public class TestsWsTest {
@Before
public void setUp() throws Exception {
SnapshotPerspectives snapshotPerspectives = mock(SnapshotPerspectives.class);
- WsTester tester = new WsTester(new TestsWs(new TestsShowAction(mock(DbClient.class), snapshotPerspectives), new TestsTestCasesAction(snapshotPerspectives), new TestsCoveredFilesAction(snapshotPerspectives)));
+ WsTester tester = new WsTester(new TestsWs(
+ new TestsShowAction(mock(DbClient.class), snapshotPerspectives),
+ new TestsTestCasesAction(snapshotPerspectives, mock(ComponentService.class), mock(DbClient.class)),
+ new TestsCoveredFilesAction(snapshotPerspectives)));
controller = tester.controller("api/tests");
}
diff --git a/server/sonar-web/Gruntfile.coffee b/server/sonar-web/Gruntfile.coffee
index 57b0b7f30dc..f306c495a9d 100644
--- a/server/sonar-web/Gruntfile.coffee
+++ b/server/sonar-web/Gruntfile.coffee
@@ -305,6 +305,9 @@ module.exports = (grunt) ->
'<%= grunt.option("assetsDir") || pkg.assets %>js/templates/widgets.js': [
'<%= pkg.sources %>hbs/widgets/**/*.hbs'
]
+ '<%= grunt.option("assetsDir") || pkg.assets %>js/templates/workspace.js': [
+ '<%= pkg.sources %>hbs/workspace/**/*.hbs'
+ ]
clean:
diff --git a/server/sonar-web/src/main/hbs/source-viewer/source-viewer-coverage-popup.hbs b/server/sonar-web/src/main/hbs/source-viewer/source-viewer-coverage-popup.hbs
index df7cfc0388a..c30e1f500a9 100644
--- a/server/sonar-web/src/main/hbs/source-viewer/source-viewer-coverage-popup.hbs
+++ b/server/sonar-web/src/main/hbs/source-viewer/source-viewer-coverage-popup.hbs
@@ -15,7 +15,7 @@
{{#each testFiles}}
<div class="bubble-popup-section">
- <a class="component-viewer-popup-test-file link-action" data-key="{{file.key}}" title="{{file.longName}}">
+ <a class="component-viewer-popup-test-file link-action" data-uuid="{{file.uuid}}" title="{{file.longName}}">
{{file.longName}}
</a>
<ul class="bubble-popup-list">
@@ -24,7 +24,7 @@
<i class="component-viewer-popup-test-status {{testStatusIconClass status}}"></i>
<span class="component-viewer-popup-test-name">
<a class="component-viewer-popup-test-file link-action" title="{{name}}"
- data-key="{{../file.key}}" data-method="{{name}}">
+ data-uuid="{{../file.uuid}}" data-method="{{name}}">
{{name}}
</a>
</span>
diff --git a/server/sonar-web/src/main/hbs/source-viewer/source-viewer-duplication-popup.hbs b/server/sonar-web/src/main/hbs/source-viewer/source-viewer-duplication-popup.hbs
index 79947f601c6..d3553ed3e58 100644
--- a/server/sonar-web/src/main/hbs/source-viewer/source-viewer-duplication-popup.hbs
+++ b/server/sonar-web/src/main/hbs/source-viewer/source-viewer-duplication-popup.hbs
@@ -17,7 +17,7 @@
{{#notEq file.key ../component.key}}
<div class="component-name-path">
- <a class="link-action" data-key="{{file.key}}" title="{{file.name}}">
+ <a data-uuid="{{file.uuid}}" title="{{file.name}}">
<span>{{collapsedDirFromPath file.name}}</span><span
class="component-name-file">{{fileFromPath file.name}}</span>
</a>
@@ -27,7 +27,7 @@
<div class="component-name-path">
Lines:
{{#joinEach blocks ','}}
- <a class="link-action" data-key="{{../file.key}}" data-line="{{from}}">
+ <a class="link-action" data-uuid="{{../file.uuid}}" data-line="{{from}}">
{{from}} – {{sum from size -1}}
</a>
{{/joinEach}}
diff --git a/server/sonar-web/src/main/hbs/source-viewer/source-viewer-more-actions.hbs b/server/sonar-web/src/main/hbs/source-viewer/source-viewer-more-actions.hbs
index 0538df1fcb6..f782b2da64e 100644
--- a/server/sonar-web/src/main/hbs/source-viewer/source-viewer-more-actions.hbs
+++ b/server/sonar-web/src/main/hbs/source-viewer/source-viewer-more-actions.hbs
@@ -2,4 +2,6 @@
<br>
<a class="js-new-window">{{t 'component_viewer.new_window'}}</a>
<br>
+<a class="js-workspace">{{t 'component_viewer.open_in_workspace'}}</a>
+<br>
<a class="js-raw-source">{{t 'component_viewer.show_raw_source'}}</a>
diff --git a/server/sonar-web/src/main/hbs/workspace/workspace-item.hbs b/server/sonar-web/src/main/hbs/workspace/workspace-item.hbs
new file mode 100644
index 00000000000..24cf2862541
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/workspace/workspace-item.hbs
@@ -0,0 +1,7 @@
+{{#if q}}
+ {{qualifierIcon q}}
+{{/if}}
+
+{{default name uuid}}
+
+<button class="js-close button-clean" style="color: #fff;">&times;</button>
diff --git a/server/sonar-web/src/main/hbs/workspace/workspace-items.hbs b/server/sonar-web/src/main/hbs/workspace/workspace-items.hbs
new file mode 100644
index 00000000000..6fe99c04b19
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/workspace/workspace-items.hbs
@@ -0,0 +1 @@
+<ul class="workspace-nav-list"></ul>
diff --git a/server/sonar-web/src/main/hbs/workspace/workspace-viewer-header.hbs b/server/sonar-web/src/main/hbs/workspace/workspace-viewer-header.hbs
new file mode 100644
index 00000000000..9e3984a8bfe
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/workspace/workspace-viewer-header.hbs
@@ -0,0 +1,8 @@
+<h6 class="workspace-viewer-name">{{qualifierIcon q}}&nbsp;{{name}}</h6>
+
+<div class="workspace-viewer-actions">
+ <a href="#" class="js-minimize icon-minimize spacer-right"></a>
+ <a href="#" class="js-full-screen icon-bigger-size workspace-for-normal-size spacer-right"></a>
+ <a href="#" class="js-normal-size icon-smaller-size workspace-for-full-screen spacer-right"></a>
+ <a href="#" class="js-close icon-close"></a>
+</div>
diff --git a/server/sonar-web/src/main/hbs/workspace/workspace-viewer.hbs b/server/sonar-web/src/main/hbs/workspace/workspace-viewer.hbs
new file mode 100644
index 00000000000..45515fbecb0
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/workspace/workspace-viewer.hbs
@@ -0,0 +1,3 @@
+<div class="workspace-viewer-header"></div>
+
+<div class="workspace-viewer-container"></div>
diff --git a/server/sonar-web/src/main/js/components/navigator/workspace-list-view.js b/server/sonar-web/src/main/js/components/navigator/workspace-list-view.js
index dda154b3717..0296b1589aa 100644
--- a/server/sonar-web/src/main/js/components/navigator/workspace-list-view.js
+++ b/server/sonar-web/src/main/js/components/navigator/workspace-list-view.js
@@ -87,16 +87,7 @@ define(function () {
}
},
- disablePointerEvents: function () {
- clearTimeout(this.scrollTimer);
- $('body').addClass('disabled-pointer-events');
- this.scrollTimer = setTimeout(function () {
- $('body').removeClass('disabled-pointer-events');
- }, 250);
- },
-
onScroll: function () {
- this.disablePointerEvents();
if ($(window).scrollTop() + $(window).height() >= this.ui.loadMore.offset().top) {
this.loadMoreThrottled();
}
diff --git a/server/sonar-web/src/main/js/nav/app.js b/server/sonar-web/src/main/js/nav/app.js
index 0a67c7bf979..9d495f433b1 100644
--- a/server/sonar-web/src/main/js/nav/app.js
+++ b/server/sonar-web/src/main/js/nav/app.js
@@ -20,7 +20,8 @@
define([
'nav/global-navbar-view',
'nav/context-navbar-view',
- 'nav/settings-navbar-view'
+ 'nav/settings-navbar-view',
+ 'workspace/main'
], function (GlobalNavbarView, ContextNavbarView, SettingsNavbarView) {
var $ = jQuery,
diff --git a/server/sonar-web/src/main/js/source-viewer/more-actions.js b/server/sonar-web/src/main/js/source-viewer/more-actions.js
index fd96c43ffa4..fc3f4048076 100644
--- a/server/sonar-web/src/main/js/source-viewer/more-actions.js
+++ b/server/sonar-web/src/main/js/source-viewer/more-actions.js
@@ -18,8 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
define([
+ 'workspace/main',
'templates/source-viewer'
-], function () {
+], function (Workspace) {
var $ = jQuery;
@@ -30,6 +31,7 @@ define([
events: {
'click .js-measures': 'showMeasures',
'click .js-new-window': 'openNewWindow',
+ 'click .js-workspace': 'openInWorkspace',
'click .js-raw-source': 'showRawSource'
},
@@ -49,6 +51,14 @@ define([
this.options.parent.getPermalink();
},
+ openInWorkspace: function () {
+ var uuid = this.options.parent.model.id;
+ if (Workspace == null) {
+ Workspace = require('workspace/main');
+ }
+ Workspace.openComponent({ uuid: uuid });
+ },
+
showRawSource: function () {
this.options.parent.showRawSources();
}
diff --git a/server/sonar-web/src/main/js/source-viewer/popups/coverage-popup.js b/server/sonar-web/src/main/js/source-viewer/popups/coverage-popup.js
index 96afb6ff4ed..34f080e9655 100644
--- a/server/sonar-web/src/main/js/source-viewer/popups/coverage-popup.js
+++ b/server/sonar-web/src/main/js/source-viewer/popups/coverage-popup.js
@@ -19,8 +19,9 @@
*/
define([
'common/popup',
+ 'workspace/main',
'templates/source-viewer'
-], function (Popup) {
+], function (Popup, Workspace) {
var $ = jQuery;
@@ -28,7 +29,7 @@ define([
template: Templates['source-viewer-coverage-popup'],
events: {
- 'click a[data-key]': 'goToFile'
+ 'click a[data-uuid]': 'goToFile'
},
onRender: function () {
@@ -37,10 +38,11 @@ define([
},
goToFile: function (e) {
- var key = $(e.currentTarget).data('key'),
- url = baseUrl + '/component/index?id=' + encodeURIComponent(key),
- windowParams = 'resizable=1,scrollbars=1,status=1';
- window.open(url, key, windowParams);
+ var uuid = $(e.currentTarget).data('uuid');
+ if (Workspace == null) {
+ Workspace = require('workspace/main');
+ }
+ Workspace.openComponent({ uuid: uuid });
},
serializeData: function () {
diff --git a/server/sonar-web/src/main/js/source-viewer/popups/duplication-popup.js b/server/sonar-web/src/main/js/source-viewer/popups/duplication-popup.js
index a00749fcedc..e1479daa14d 100644
--- a/server/sonar-web/src/main/js/source-viewer/popups/duplication-popup.js
+++ b/server/sonar-web/src/main/js/source-viewer/popups/duplication-popup.js
@@ -19,8 +19,9 @@
*/
define([
'common/popup',
+ 'workspace/main',
'templates/source-viewer'
-], function (Popup) {
+], function (Popup, Workspace) {
var $ = jQuery;
@@ -28,15 +29,17 @@ define([
template: Templates['source-viewer-duplication-popup'],
events: {
- 'click a[data-key]': 'goToFile'
+ 'click a[data-uuid]': 'goToFile'
},
goToFile: function (e) {
- var key = $(e.currentTarget).data('key'),
- line = $(e.currentTarget).data('line'),
- url = baseUrl + '/component/index?id=' + encodeURIComponent(key) + (line ? ('&line=' + line) : ''),
- windowParams = 'resizable=1,scrollbars=1,status=1';
- window.open(url, key, windowParams);
+ var uuid = $(e.currentTarget).data('uuid'),
+ line = $(e.currentTarget).data('line');
+ console.log(uuid);
+ if (Workspace == null) {
+ Workspace = require('workspace/main');
+ }
+ Workspace.openComponent({ uuid: uuid, line: line });
},
serializeData: function () {
diff --git a/server/sonar-web/src/main/js/source-viewer/viewer.js b/server/sonar-web/src/main/js/source-viewer/viewer.js
index 84f635b9ae9..d177c92d9a3 100644
--- a/server/sonar-web/src/main/js/source-viewer/viewer.js
+++ b/server/sonar-web/src/main/js/source-viewer/viewer.js
@@ -27,6 +27,7 @@ define([
'source-viewer/popups/coverage-popup',
'source-viewer/popups/duplication-popup',
'source-viewer/popups/line-actions-popup',
+ 'workspace/main',
'templates/source-viewer'
],
function (Source,
@@ -37,7 +38,8 @@ define([
SCMPopupView,
CoveragePopupView,
DuplicationPopupView,
- LineActionsPopupView) {
+ LineActionsPopupView,
+ Workspace) {
var $ = jQuery,
HIGHLIGHTED_ROW_CLASS = 'source-line-highlighted';
@@ -234,7 +236,7 @@ define([
requestDuplications: function () {
var that = this,
url = baseUrl + '/api/duplications/show',
- options = {key: this.model.key()};
+ options = { uuid : this.model.id };
return $.get(url, options, function (data) {
var hasDuplications = (data != null) && (data.duplications != null),
duplications = [];
@@ -392,7 +394,7 @@ define([
row = _.findWhere(this.model.get('source'), { line: line }),
url = baseUrl + '/api/tests/test_cases',
options = {
- key: this.model.key(),
+ uuid: this.model.id,
line: line
};
return $.get(url, options).done(function (data) {
@@ -504,16 +506,7 @@ define([
this.$el.scrollParent().off('scroll.source-viewer');
},
- disablePointerEvents: function () {
- clearTimeout(this.scrollTimer);
- $('body').addClass('disabled-pointer-events');
- this.scrollTimer = setTimeout((function () {
- $('body').removeClass('disabled-pointer-events');
- }), 250);
- },
-
onScroll: function () {
- this.disablePointerEvents();
var p = this.$el.scrollParent();
if (p.is(document)) {
p = $(window);
diff --git a/server/sonar-web/src/main/js/workspace/main.js b/server/sonar-web/src/main/js/workspace/main.js
new file mode 100644
index 00000000000..77993cb5c85
--- /dev/null
+++ b/server/sonar-web/src/main/js/workspace/main.js
@@ -0,0 +1,114 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.
+ */
+define([
+ 'workspace/models/item',
+ 'workspace/models/items',
+ 'workspace/views/items-view',
+ 'workspace/views/viewer-view'
+], function (Item, Items, ItemsView, ViewerView) {
+
+ var $ = jQuery,
+
+ instance = null,
+
+ Workspace = function () {
+ if (instance != null) {
+ throw new Error('Cannot instantiate more than one Workspace, use Workspace.getInstance()');
+ }
+ this.initialize();
+ };
+
+ Workspace.prototype = {
+ initialize: function () {
+ var that = this;
+
+ this.items = new Items();
+ this.items.load();
+
+ this.itemsView = new ItemsView({ collection: this.items });
+ this.itemsView.render().$el.appendTo(document.body);
+ this.itemsView.on('click', function (uuid, model) {
+ model.collection.remove(model);
+ that.showComponentViewer(model.toJSON());
+ });
+ },
+
+ save: function () {
+ this.items.save();
+ },
+
+ load: function () {
+ this.items.load();
+ },
+
+ addComponent: function (options) {
+ if (options == null || typeof options.uuid !== 'string') {
+ throw new Error('You must specify the component\'s uuid');
+ }
+ this.items.add(options);
+ this.save();
+ },
+
+ openComponent: function (options) {
+ if (options == null || typeof options.uuid !== 'string') {
+ throw new Error('You must specify the component\'s uuid');
+ }
+ this.showComponentViewer(options);
+ },
+
+ showComponentViewer: function (options) {
+ var that = this,
+ model = new Item(options);
+ if (this.viewerView != null) {
+ this.viewerView.close();
+ }
+ $('.source-viewer').addClass('with-workspace');
+ this.viewerView = new ViewerView({
+ model: model
+ });
+ model
+ .on('minimize', function () {
+ that.addComponent(model.toJSON());
+ that.closeComponentViewer();
+ })
+ .on('close', function () {
+ that.closeComponentViewer();
+ });
+ this.viewerView.render().$el.appendTo(document.body);
+ },
+
+ closeComponentViewer: function () {
+ if (this.viewerView != null) {
+ this.viewerView.close();
+ $('.with-workspace').removeClass('with-workspace');
+ }
+ }
+ };
+
+ Workspace.getInstance = function () {
+ if (instance == null) {
+ instance = new Workspace();
+ }
+ return instance;
+ };
+
+ return Workspace.getInstance();
+
+});
diff --git a/server/sonar-web/src/main/js/workspace/models/item.js b/server/sonar-web/src/main/js/workspace/models/item.js
new file mode 100644
index 00000000000..076f57a3e36
--- /dev/null
+++ b/server/sonar-web/src/main/js/workspace/models/item.js
@@ -0,0 +1,37 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.
+ */
+define(function () {
+
+ return Backbone.Model.extend({
+ idAttribute: 'uuid',
+
+ validate: function () {
+ if (!this.has('uuid')) {
+ return 'uuid is missing';
+ }
+ },
+
+ destroy: function (options) {
+ this.stopListening();
+ this.trigger('destroy', this, this.collection, options);
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/workspace/models/items.js b/server/sonar-web/src/main/js/workspace/models/items.js
new file mode 100644
index 00000000000..f582af5709d
--- /dev/null
+++ b/server/sonar-web/src/main/js/workspace/models/items.js
@@ -0,0 +1,47 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.
+ */
+define(['workspace/models/item'], function (Item) {
+
+ var STORAGE_KEY = 'sonarqube-workspace';
+
+ return Backbone.Collection.extend({
+ model: Item,
+
+ initialize: function () {
+ this.on('remove', this.save);
+ },
+
+ save: function () {
+ var dump = JSON.stringify(this.toJSON());
+ window.localStorage.setItem(STORAGE_KEY, dump);
+ },
+
+ load: function () {
+ var dump = window.localStorage.getItem(STORAGE_KEY);
+ if (dump != null) {
+ try {
+ var parsed = JSON.parse(dump);
+ this.reset(parsed);
+ } catch (err) { }
+ }
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/workspace/views/item-view.js b/server/sonar-web/src/main/js/workspace/views/item-view.js
new file mode 100644
index 00000000000..cbc5919ba60
--- /dev/null
+++ b/server/sonar-web/src/main/js/workspace/views/item-view.js
@@ -0,0 +1,46 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.
+ */
+define([
+ 'templates/workspace'
+], function () {
+
+ return Marionette.ItemView.extend({
+ tagName: 'li',
+ className: 'workspace-nav-item',
+ template: Templates['workspace-item'],
+
+ events: {
+ 'click': 'onClick',
+ 'click .js-close': 'onCloseClick'
+ },
+
+ onClick: function (e) {
+ e.preventDefault();
+ this.options.collectionView.trigger('click', this.model.id, this.model);
+ },
+
+ onCloseClick: function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ this.model.destroy();
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/workspace/views/items-view.js b/server/sonar-web/src/main/js/workspace/views/items-view.js
new file mode 100644
index 00000000000..e6c721bf089
--- /dev/null
+++ b/server/sonar-web/src/main/js/workspace/views/items-view.js
@@ -0,0 +1,36 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.
+ */
+define([
+ 'workspace/views/item-view',
+ 'templates/workspace'
+], function (ItemView) {
+
+ return Marionette.CompositeView.extend({
+ className: 'workspace-nav',
+ template: Templates['workspace-items'],
+ itemViewContainer: '.workspace-nav-list',
+ itemView: ItemView,
+
+ itemViewOptions: function () {
+ return { collectionView: this };
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/workspace/views/viewer-header-view.js b/server/sonar-web/src/main/js/workspace/views/viewer-header-view.js
new file mode 100644
index 00000000000..052550f1642
--- /dev/null
+++ b/server/sonar-web/src/main/js/workspace/views/viewer-header-view.js
@@ -0,0 +1,68 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.
+ */
+define([
+ 'templates/workspace'
+], function () {
+
+ return Marionette.ItemView.extend({
+ template: Templates['workspace-viewer-header'],
+
+ modelEvents: {
+ 'change': 'render'
+ },
+
+ events: {
+ 'click .js-minimize': 'onMinimizeClick',
+ 'click .js-full-screen': 'onFullScreenClick',
+ 'click .js-normal-size': 'onNormalSizeClick',
+ 'click .js-close': 'onCloseClick'
+ },
+
+ onMinimizeClick: function (e) {
+ e.preventDefault();
+ this.model.trigger('minimize');
+ },
+
+ onFullScreenClick: function (e) {
+ e.preventDefault();
+ this.toFullScreen();
+ },
+
+ onNormalSizeClick: function (e) {
+ e.preventDefault();
+ this.toNormalSize();
+ },
+
+ onCloseClick: function (e) {
+ e.preventDefault();
+ this.model.trigger('close');
+ },
+
+
+ toFullScreen: function () {
+ this.$el.closest('.workspace-viewer').addClass('workspace-viewer-full-screen');
+ },
+
+ toNormalSize: function () {
+ this.$el.closest('.workspace-viewer').removeClass('workspace-viewer-full-screen');
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/workspace/views/viewer-view.js b/server/sonar-web/src/main/js/workspace/views/viewer-view.js
new file mode 100644
index 00000000000..19f772facc0
--- /dev/null
+++ b/server/sonar-web/src/main/js/workspace/views/viewer-view.js
@@ -0,0 +1,64 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.
+ */
+define([
+ 'workspace/views/viewer-header-view',
+ 'source-viewer/viewer',
+ 'templates/workspace'
+], function (HeaderView, SourceViewer) {
+
+ return Marionette.Layout.extend({
+ className: 'workspace-viewer',
+ template: Templates['workspace-viewer'],
+
+ regions: {
+ headerRegion: '.workspace-viewer-header',
+ viewerRegion: '.workspace-viewer-container'
+ },
+
+ onRender: function () {
+ this.showHeader();
+ this.showViewer();
+ },
+
+ showHeader: function () {
+ var headerView = new HeaderView({ model: this.model });
+ this.headerRegion.show(headerView);
+ },
+
+ showViewer: function () {
+ if (SourceViewer == null) {
+ SourceViewer = require('source-viewer/viewer');
+ }
+ var that = this,
+ viewer = new SourceViewer(),
+ options = this.model.toJSON();
+ viewer.open(this.model.id);
+ viewer.on('loaded', function () {
+ that.model.set(viewer.model.toJSON());
+ if (options.line != null) {
+ viewer.highlightLine(options.line);
+ viewer.scrollToLine(options.line);
+ }
+ });
+ this.viewerRegion.show(viewer);
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/less/components.less b/server/sonar-web/src/main/less/components.less
index 7907d0b7bde..650d6fcf2ce 100644
--- a/server/sonar-web/src/main/less/components.less
+++ b/server/sonar-web/src/main/less/components.less
@@ -42,3 +42,4 @@
@import "components/panels";
@import "components/badges";
@import "components/columns";
+@import "components/workspace";
diff --git a/server/sonar-web/src/main/less/components/source.less b/server/sonar-web/src/main/less/components/source.less
index 0e1e0de3770..9b88fa38cde 100644
--- a/server/sonar-web/src/main/less/components/source.less
+++ b/server/sonar-web/src/main/less/components/source.less
@@ -29,6 +29,7 @@
width: 100%;
border: 1px solid @barBorderColor;
.box-sizing(border-box);
+ background-color: #fff;
overflow-x: auto;
overflow-y: hidden;
}
diff --git a/server/sonar-web/src/main/less/components/workspace.less b/server/sonar-web/src/main/less/components/workspace.less
new file mode 100644
index 00000000000..20339d2995a
--- /dev/null
+++ b/server/sonar-web/src/main/less/components/workspace.less
@@ -0,0 +1,111 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 (reference) "../mixins";
+@import (reference) "../variables";
+
+.workspace-nav {
+ position: fixed;
+ z-index: @workspace-nav-z-index;
+ bottom: 0;
+ right: 0;
+ height: 30px;
+}
+
+.workspace-nav-list {
+ float: right;
+}
+
+.workspace-nav-item {
+ float: left;
+ height: 30px;
+ line-height: 30px;
+ margin-right: 10px;
+ padding: 0 10px;
+ background-color: #404040;
+ color: #fff;
+ font-size: @smallFontSize;
+ font-weight: 300;
+ cursor: pointer;
+}
+
+.workspace-viewer {
+ position: fixed;
+ z-index: @workspace-viewer-z-index;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ .box-sizing(border-box);
+ background-color: #fff;
+ box-shadow: 0 -6px 12px rgba(0, 0, 0, .175);
+}
+
+.workspace-viewer-full-screen {
+ .workspace-viewer-container {
+ height: ~"calc(100vh - 30px - @{navbarGlobalHeight} - 10px)";
+ }
+
+ .workspace-for-full-screen {
+ display: inline;
+ }
+
+ .workspace-for-normal-size {
+ display: none;
+ }
+}
+
+.workspace-viewer-header {
+ @topPadding: (30px - @formControlHeight) / 2;
+ height: 30px;
+ padding: @topPadding 10px;
+ .box-sizing(border-box);
+ background-color: #404040;
+ color: #fff;
+ font-weight: 300;
+}
+
+.workspace-viewer-name {
+ float: left;
+ line-height: @formControlHeight;
+}
+
+.workspace-viewer-actions {
+ float: right;
+ line-height: @formControlHeight;
+
+ a { color: inherit; }
+}
+
+.workspace-viewer-container {
+ height: ~"calc(40vh - 30px)";
+ padding: 5px 10px;
+ overflow-y: scroll;
+ overflow-x: auto;
+ .box-sizing(border-box);
+}
+
+.workspace-for-full-screen {
+ display: none;
+}
+
+
+// Misc
+.with-workspace {
+ padding-bottom: 40vh;
+}
diff --git a/server/sonar-web/src/main/less/init/icons.less b/server/sonar-web/src/main/less/init/icons.less
index ed82a9d84f0..f2b19caefdd 100644
--- a/server/sonar-web/src/main/less/init/icons.less
+++ b/server/sonar-web/src/main/less/init/icons.less
@@ -569,6 +569,18 @@ a[class^="icon-"], a[class*=" icon-"] {
content: "\f141";
font-size: @iconSmallFontSize;
}
+.icon-bigger-size:before {
+ content: "\f065";
+ font-size: @iconSmallFontSize;
+}
+.icon-smaller-size:before {
+ content: "\f066";
+ font-size: @iconSmallFontSize;
+}
+.icon-minimize:before {
+ content: "\f068";
+ font-size: @iconSmallFontSize;
+}
/*
diff --git a/server/sonar-web/src/main/less/init/type.less b/server/sonar-web/src/main/less/init/type.less
index 41c333ce123..f14c38ef8cb 100644
--- a/server/sonar-web/src/main/less/init/type.less
+++ b/server/sonar-web/src/main/less/init/type.less
@@ -52,13 +52,20 @@ h3, .h3 {
font-weight: 500;
}
-h4, .h4, h5, .h5, h6, .h6 {
+h4, .h4, h5, .h5 {
line-height: @formControlHeight;
color: @secondFontColor;
font-size: @smallFontSize;
font-weight: 400;
}
+h6 {
+ line-height: inherit;
+ color: inherit;
+ font-size: inherit;
+ font-weight: inherit;
+}
+
h1 img, .h1 img, h2 img, .h2 img, h3 img, .h3 img, h4 img, .h4 img {
vertical-align: middle;
}
diff --git a/server/sonar-web/src/main/less/variables.less b/server/sonar-web/src/main/less/variables.less
index b8c6e9e6be2..08e1a0499d9 100644
--- a/server/sonar-web/src/main/less/variables.less
+++ b/server/sonar-web/src/main/less/variables.less
@@ -149,3 +149,11 @@
@navbarGlobalHeight: 30px;
@navbarContextHeight: 60px;
@pageFooterHeight: 60px;
+
+
+
+/*
+ * z-index
+ */
+@workspace-nav-z-index: 301;
+@workspace-viewer-z-index: 300;
diff --git a/server/sonar-web/src/main/webapp/fonts/sonar-5.1.eot b/server/sonar-web/src/main/webapp/fonts/sonar-5.1.eot
index 8150a71a115..4e519106bb1 100755
--- a/server/sonar-web/src/main/webapp/fonts/sonar-5.1.eot
+++ b/server/sonar-web/src/main/webapp/fonts/sonar-5.1.eot
Binary files differ
diff --git a/server/sonar-web/src/main/webapp/fonts/sonar-5.1.svg b/server/sonar-web/src/main/webapp/fonts/sonar-5.1.svg
index 6ed29b1be77..3b377b7638f 100755
--- a/server/sonar-web/src/main/webapp/fonts/sonar-5.1.svg
+++ b/server/sonar-web/src/main/webapp/fonts/sonar-5.1.svg
@@ -55,7 +55,10 @@
<glyph unicode="&#xf061;" d="M841.143 411.429q0-30.857-21.143-52l-372-372q-22.286-21.143-52-21.143-29.143 0-51.429 21.143l-42.857 42.857q-21.714 21.714-21.714 52t21.714 52l167.429 167.429h-402.286q-29.714 0-48.286 21.429t-18.571 51.714v73.143q0 30.286 18.571 51.714t48.286 21.429h402.286l-167.429 168q-21.714 20.571-21.714 51.429t21.714 51.429l42.857 42.857q21.714 21.714 51.429 21.714 30.286 0 52-21.714l372-372q21.143-20 21.143-51.429z" />
<glyph unicode="&#xf062;" d="M920.571 405.143q0-29.143-21.143-51.429l-42.857-42.857q-21.714-21.714-52-21.714-30.857 0-51.429 21.714l-168 167.429v-402.286q0-29.714-21.429-48.286t-51.714-18.571h-73.143q-30.286 0-51.714 18.571t-21.429 48.286v402.286l-168-167.429q-20.571-21.714-51.429-21.714t-51.429 21.714l-42.857 42.857q-21.714 21.714-21.714 51.429 0 30.286 21.714 52l372 372q20 21.143 51.429 21.143 30.857 0 52-21.143l372-372q21.143-22.286 21.143-52z" horiz-adv-x="951" />
<glyph unicode="&#xf063;" d="M920.571 484.571q0-30.286-21.143-51.429l-372-372.571q-22.286-21.143-52-21.143-30.286 0-51.429 21.143l-372 372.571q-21.714 20.571-21.714 51.429 0 30.286 21.714 52l42.286 42.857q22.286 21.143 52 21.143 30.286 0 51.429-21.143l168-168v402.286q0 29.714 21.714 51.429t51.429 21.714h73.143q29.714 0 51.429-21.714t21.714-51.429v-402.286l168 168q21.143 21.143 51.429 21.143 29.714 0 52-21.143l42.857-42.857q21.143-22.286 21.143-52z" horiz-adv-x="951" />
+<glyph unicode="&#xf065;" d="M503.385 332.821q0-8.615-6.627-15.243l-220.023-220.023 95.433-95.433q12.592-12.592 12.592-29.822t-12.59-29.822-29.822-12.59h-296.899q-17.231 0-29.822 12.59t-12.59 29.822v296.899q0 17.231 12.59 29.822t29.822 12.592 29.822-12.592l95.433-95.433 220.023 220.023q6.627 6.627 15.243 6.627t15.243-6.627l75.55-75.55q6.627-6.627 6.627-15.243zM1020.969 905.413v-296.899q0-17.231-12.592-29.822t-29.822-12.592-29.822 12.592l-95.433 95.433-220.023-220.023q-6.627-6.627-15.243-6.627t-15.243 6.627l-75.55 75.55q-6.627 6.627-6.627 15.243t6.627 15.243l220.023 220.023-95.433 95.433q-12.59 12.592-12.59 29.822t12.59 29.822 29.822 12.592h296.899q17.231 0 29.822-12.592t12.592-29.822z" />
+<glyph unicode="&#xf066;" d="M438.857 402.286v-256q0-14.857-10.857-25.714t-25.714-10.857-25.714 10.857l-82.286 82.286-189.714-189.714q-5.714-5.714-13.143-5.714t-13.143 5.714l-65.143 65.143q-5.714 5.714-5.714 13.143t5.714 13.143l189.714 189.714-82.286 82.286q-10.857 10.857-10.857 25.714t10.857 25.714 25.714 10.857h256q14.857 0 25.714-10.857t10.857-25.714zM870.286 786.286q0-7.429-5.714-13.143l-189.714-189.714 82.286-82.286q10.857-10.857 10.857-25.714t-10.857-25.714-25.714-10.857h-256q-14.857 0-25.714 10.857t-10.857 25.714v256q0 14.857 10.857 25.714t25.714 10.857 25.714-10.857l82.286-82.286 189.714 189.714q5.714 5.714 13.143 5.714t13.143-5.714l65.143-65.143q5.714-5.714 5.714-13.143z" />
<glyph unicode="&#xf067;" d="M804.571 539.429v-109.714q0-22.857-16-38.857t-38.857-16h-237.714v-237.714q0-22.857-16-38.857t-38.857-16h-109.714q-22.857 0-38.857 16t-16 38.857v237.714h-237.714q-22.857 0-38.857 16t-16 38.857v109.714q0 22.857 16 38.857t38.857 16h237.714v237.714q0 22.857 16 38.857t38.857 16h109.714q22.857 0 38.857-16t16-38.857v-237.714h237.714q22.857 0 38.857-16t16-38.857z" horiz-adv-x="805" />
+<glyph unicode="&#xf068;" d="M804.571 91.434v-109.714q0-22.857-16-38.857t-38.857-16h-694.857q-22.857 0-38.857 16t-16 38.857v109.714q0 22.857 16 38.857t38.857 16h694.857q22.857 0 38.857-16t16-38.857z" horiz-adv-x="805" />
<glyph unicode="&#xf069;" d="M846.857 360q26.286-14.857 34-44.286t-7.143-55.714l-36.571-62.857q-14.857-26.286-44.286-34t-55.714 7.143l-152 87.429v-175.429q0-29.714-21.714-51.429t-51.429-21.714h-73.143q-29.714 0-51.429 21.714t-21.714 51.429v175.429l-152-87.429q-26.286-14.857-55.714-7.143t-44.286 34l-36.571 62.857q-14.857 26.286-7.143 55.714t34 44.286l152 88-152 88q-26.286 14.857-34 44.286t7.143 55.714l36.571 62.857q14.857 26.286 44.286 34t55.714-7.143l152-87.429v175.429q0 29.714 21.714 51.429t51.429 21.714h73.143q29.714 0 51.429-21.714t21.714-51.429v-175.429l152 87.429q26.286 14.857 55.714 7.143t44.286-34l36.571-62.857q14.857-26.286 7.143-55.714t-34-44.286l-152-88z" horiz-adv-x="951" />
<glyph unicode="&#xf073;" d="M73.143 9.143h164.571v164.571h-164.571v-164.571zM274.286 9.143h182.857v164.571h-182.857v-164.571zM73.143 210.286h164.571v182.857h-164.571v-182.857zM274.286 210.286h182.857v182.857h-182.857v-182.857zM73.143 429.714h164.571v164.571h-164.571v-164.571zM493.714 9.143h182.857v164.571h-182.857v-164.571zM274.286 429.714h182.857v164.571h-182.857v-164.571zM713.143 9.143h164.571v164.571h-164.571v-164.571zM493.714 210.286h182.857v182.857h-182.857v-182.857zM292.571 704v164.571q0 7.429-5.429 12.857t-12.857 5.429h-36.571q-7.429 0-12.857-5.429t-5.429-12.857v-164.571q0-7.429 5.429-12.857t12.857-5.429h36.571q7.429 0 12.857 5.429t5.429 12.857zM713.143 210.286h164.571v182.857h-164.571v-182.857zM493.714 429.714h182.857v164.571h-182.857v-164.571zM713.143 429.714h164.571v164.571h-164.571v-164.571zM731.429 704v164.571q0 7.429-5.429 12.857t-12.857 5.429h-36.571q-7.429 0-12.857-5.429t-5.429-12.857v-164.571q0-7.429 5.429-12.857t12.857-5.429h36.571q7.429 0 12.857 5.429t5.429 12.857zM950.857 740.571v-731.429q0-29.714-21.714-51.429t-51.429-21.714h-804.571q-29.714 0-51.429 21.714t-21.714 51.429v731.429q0 29.714 21.714 51.429t51.429 21.714h73.143v54.857q0 37.714 26.857 64.571t64.571 26.857h36.571q37.714 0 64.571-26.857t26.857-64.571v-54.857h219.429v54.857q0 37.714 26.857 64.571t64.571 26.857h36.571q37.714 0 64.571-26.857t26.857-64.571v-54.857h73.143q29.714 0 51.429-21.714t21.714-51.429z" horiz-adv-x="951" />
<glyph unicode="&#xf075;" d="M1024 448q0-99.429-68.571-183.714t-186.286-133.143-257.143-48.857q-40 0-82.857 4.571-113.143-100-262.857-138.286-28-8-65.143-12.571-9.714-1.143-17.429 5.143t-10 16.571v0.571q-1.714 2.286-0.286 6.857t1.143 5.714 2.571 5.429l3.429 5.143t4 4.857 4.571 5.143q4 4.571 17.714 19.714t19.714 21.714 17.714 22.571 18.571 29.143 15.429 33.714 14.857 43.429q-89.714 50.857-141.429 125.714t-51.714 160.571q0 74.286 40.571 142t109.143 116.857 163.429 78 198.857 28.857q139.429 0 257.143-48.857t186.286-133.143 68.571-183.714z" />
diff --git a/server/sonar-web/src/main/webapp/fonts/sonar-5.1.ttf b/server/sonar-web/src/main/webapp/fonts/sonar-5.1.ttf
index 86a81f3f492..903dd7bef1e 100755
--- a/server/sonar-web/src/main/webapp/fonts/sonar-5.1.ttf
+++ b/server/sonar-web/src/main/webapp/fonts/sonar-5.1.ttf
Binary files differ
diff --git a/server/sonar-web/src/main/webapp/fonts/sonar-5.1.woff b/server/sonar-web/src/main/webapp/fonts/sonar-5.1.woff
index 40582fe91e5..1d1d8b90d54 100755
--- a/server/sonar-web/src/main/webapp/fonts/sonar-5.1.woff
+++ b/server/sonar-web/src/main/webapp/fonts/sonar-5.1.woff
Binary files differ
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index b8ab2278d5a..13e651e836c 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -2889,6 +2889,7 @@ component_viewer.show_full_source=Show Full Source
component_viewer.show_raw_source=Show Raw Source
component_viewer.more_actions=More Actions
component_viewer.new_window=Open in New Window
+component_viewer.open_in_workspace=Open in Workspace
component_viewer.get_permalink=Get Permalink
component_viewer.covered_lines=Covered Lines
component_viewer.issues_limit_reached=For usability reasons, only the {0} first issues will be fully displayed. Remaining issues will simply be underlined.