diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2015-03-06 10:44:45 +0100 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2015-03-06 10:44:45 +0100 |
commit | 9f6c589ed8c8c03d4f93bad772a91941a2f62cb3 (patch) | |
tree | 21d7bb776c6e119dd0f5c1db35bce5cbc7c10c10 | |
parent | 1432f744dbff4834badc39fd66fe19268712a1a5 (diff) | |
parent | 4cbfea9653a895bafb1ee7d2de521b7d78345320 (diff) | |
download | sonarqube-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
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;">×</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}} {{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 Binary files differindex 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 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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 Binary files differindex 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 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 Binary files differindex 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 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. |