aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@gmail.com>2011-12-19 15:00:29 +0100
committerSimon Brandhof <simon.brandhof@gmail.com>2011-12-19 15:18:09 +0100
commitc5edb54175be630d973a3a994e6cb44c46d3edfb (patch)
treede9aee036f00cfb2ce10a7d0d02ffa5035564cd3
parent610521d0c0b66af51935816efdcaa63b2da709ad (diff)
downloadsonarqube-c5edb54175be630d973a3a994e6cb44c46d3edfb.tar.gz
sonarqube-c5edb54175be630d973a3a994e6cb44c46d3edfb.zip
SONAR-983 first implementation of the search engine of projects, directories and files
* The POST request to the URL /search/reset starts the indexation of resources * The search engine is available at /search
-rw-r--r--sonar-core/src/main/java/org/sonar/persistence/DaoUtils.java17
-rw-r--r--sonar-core/src/main/java/org/sonar/persistence/MyBatis.java2
-rw-r--r--sonar-core/src/main/java/org/sonar/persistence/resource/ResourceDto.java64
-rw-r--r--sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexDao.java86
-rw-r--r--sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexDto.java22
-rw-r--r--sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexMapper.java4
-rw-r--r--sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexerFilter.java35
-rw-r--r--sonar-core/src/main/resources/org/sonar/persistence/resource/ResourceIndexMapper.xml26
-rw-r--r--sonar-core/src/main/resources/org/sonar/persistence/schema-derby.ddl1
-rw-r--r--sonar-core/src/test/java/org/sonar/core/plugin/AbstractPluginRepositoryTest.java160
-rw-r--r--sonar-core/src/test/java/org/sonar/persistence/resource/ResourceIndexDaoTest.java9
-rw-r--r--sonar-core/src/test/resources/logback-test.xml5
-rw-r--r--sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testIndex-result.xml12
-rw-r--r--sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testIndexAll-result.xml32
-rw-r--r--sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testIndexAll.xml13
-rw-r--r--sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testSearch.xml22
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java12
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/search_controller.rb63
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb52
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/models/resource_index.rb30
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/search/index.html.erb46
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/db/migrate/237_create_table_resource_index.rb2
-rw-r--r--sonar-server/src/main/webapp/stylesheets/style.css9
23 files changed, 490 insertions, 234 deletions
diff --git a/sonar-core/src/main/java/org/sonar/persistence/DaoUtils.java b/sonar-core/src/main/java/org/sonar/persistence/DaoUtils.java
index ce9df3619d6..64fb1b64980 100644
--- a/sonar-core/src/main/java/org/sonar/persistence/DaoUtils.java
+++ b/sonar-core/src/main/java/org/sonar/persistence/DaoUtils.java
@@ -19,23 +19,30 @@
*/
package org.sonar.persistence;
-import java.util.Arrays;
-import java.util.List;
-
import org.sonar.persistence.dashboard.ActiveDashboardDao;
import org.sonar.persistence.dashboard.DashboardDao;
import org.sonar.persistence.duplication.DuplicationDao;
+import org.sonar.persistence.resource.ResourceIndexDao;
import org.sonar.persistence.review.ReviewDao;
import org.sonar.persistence.rule.RuleDao;
import org.sonar.persistence.template.LoadedTemplateDao;
+import java.util.Arrays;
+import java.util.List;
+
public final class DaoUtils {
private DaoUtils() {
}
public static List<Class<?>> getDaoClasses() {
- return Arrays.<Class<?>> asList(RuleDao.class, DuplicationDao.class, ReviewDao.class, ActiveDashboardDao.class, DashboardDao.class,
- LoadedTemplateDao.class);
+ return Arrays.asList(
+ ActiveDashboardDao.class,
+ DashboardDao.class,
+ DuplicationDao.class,
+ LoadedTemplateDao.class,
+ ResourceIndexDao.class,
+ ReviewDao.class,
+ RuleDao.class);
}
}
diff --git a/sonar-core/src/main/java/org/sonar/persistence/MyBatis.java b/sonar-core/src/main/java/org/sonar/persistence/MyBatis.java
index 59331636f09..1edfd4e3236 100644
--- a/sonar-core/src/main/java/org/sonar/persistence/MyBatis.java
+++ b/sonar-core/src/main/java/org/sonar/persistence/MyBatis.java
@@ -30,6 +30,7 @@ import org.sonar.api.ServerComponent;
import org.sonar.persistence.dashboard.*;
import org.sonar.persistence.duplication.DuplicationMapper;
import org.sonar.persistence.duplication.DuplicationUnitDto;
+import org.sonar.persistence.resource.ResourceDto;
import org.sonar.persistence.resource.ResourceIndexDto;
import org.sonar.persistence.resource.ResourceIndexMapper;
import org.sonar.persistence.review.ReviewDto;
@@ -62,6 +63,7 @@ public class MyBatis implements BatchComponent, ServerComponent {
loadAlias(conf, "DuplicationUnit", DuplicationUnitDto.class);
loadAlias(conf, "LoadedTemplate", LoadedTemplateDto.class);
loadAlias(conf, "Review", ReviewDto.class);
+ loadAlias(conf, "Resource", ResourceDto.class);
loadAlias(conf, "ResourceIndex", ResourceIndexDto.class);
loadAlias(conf, "Rule", RuleDto.class);
loadAlias(conf, "Widget", WidgetDto.class);
diff --git a/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceDto.java b/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceDto.java
new file mode 100644
index 00000000000..9793712838d
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceDto.java
@@ -0,0 +1,64 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.persistence.resource;
+
+public final class ResourceDto {
+
+ private Integer id;
+ private String name;
+ private String longName;
+ private Integer rootId;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public ResourceDto setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public ResourceDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Integer getRootId() {
+ return rootId;
+ }
+
+ public ResourceDto setRootId(Integer rootId) {
+ this.rootId = rootId;
+ return this;
+ }
+
+ public String getLongName() {
+ return longName;
+ }
+
+ public ResourceDto setLongName(String longName) {
+ this.longName = longName;
+ return this;
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexDao.java b/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexDao.java
index 395be04b431..83c86353e23 100644
--- a/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexDao.java
+++ b/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexDao.java
@@ -19,9 +19,13 @@
*/
package org.sonar.persistence.resource;
+import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.ResultContext;
+import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.utils.TimeProfiler;
import org.sonar.persistence.MyBatis;
import java.util.Collections;
@@ -38,47 +42,87 @@ public class ResourceIndexDao {
this.mybatis = mybatis;
}
- public List<ResourceIndexDto> search(String input) {
- if (StringUtils.isBlank(input) || input.length() < MINIMUM_SEARCH_SIZE) {
+ public List<ResourceIndexDto> search(String keyword) {
+ if (StringUtils.isBlank(keyword) || keyword.length() < MINIMUM_SEARCH_SIZE) {
return Collections.emptyList();
}
SqlSession sqlSession = mybatis.openSession();
try {
ResourceIndexMapper mapper = sqlSession.getMapper(ResourceIndexMapper.class);
- return mapper.selectLikeKey(normalize(input) + "%");
+ return mapper.selectByKeyword(normalize(keyword) + "%");
} finally {
sqlSession.close();
}
}
- public void index(String resourceName, int resourceId, int projectId) {
- if (StringUtils.isBlank(resourceName)) {
+ void index(ResourceDto resource, SqlSession session) {
+ String name = resource.getName();
+ if (StringUtils.isBlank(name)) {
return;
}
- String normalizedName = normalize(resourceName);
+ String normalizedName = normalize(name);
if (normalizedName.length() >= MINIMUM_KEY_SIZE) {
- SqlSession sqlSession = mybatis.openSession(ExecutorType.BATCH);
- try {
- ResourceIndexMapper mapper = sqlSession.getMapper(ResourceIndexMapper.class);
- ResourceIndexDto dto = new ResourceIndexDto().setResourceId(resourceId).setProjectId(projectId);
-
- for (int position = 0; position <= normalizedName.length() - MINIMUM_KEY_SIZE; position++) {
- dto.setPosition(position);
- dto.setKey(StringUtils.substring(normalizedName, position));
- mapper.insert(dto);
+ ResourceIndexMapper mapper = session.getMapper(ResourceIndexMapper.class);
+
+ Integer rootId;
+ if (resource.getRootId() != null) {
+ ResourceDto root = mapper.selectRootId(resource.getRootId());
+ if (root != null) {
+ rootId = (Integer) ObjectUtils.defaultIfNull(root.getRootId(), root.getId());
+ } else {
+ rootId = resource.getRootId();
}
+ } else {
+ rootId = resource.getId();
+ }
- sqlSession.commit();
+ ResourceIndexDto dto = new ResourceIndexDto()
+ .setResourceId(resource.getId())
+ .setProjectId(rootId)
+ .setNameSize(name.length());
- } finally {
- sqlSession.close();
+ for (int position = 0; position <= normalizedName.length() - MINIMUM_KEY_SIZE; position++) {
+ dto.setPosition(position);
+ dto.setKey(StringUtils.substring(normalizedName, position));
+ mapper.insert(dto);
}
+
+ session.commit();
+ }
+ }
+
+ public void index(String resourceName, int resourceId, int projectId) {
+ SqlSession sqlSession = mybatis.openSession();
+ try {
+ index(new ResourceDto().setId(resourceId).setName(resourceName).setRootId(projectId), sqlSession);
+
+ } finally {
+ sqlSession.close();
+ }
+ }
+
+
+ public void index(ResourceIndexerFilter filter) {
+ TimeProfiler profiler = new TimeProfiler().start("Index resources");
+ final SqlSession sqlSession = mybatis.openSession(ExecutorType.BATCH);
+ try {
+ sqlSession.select("selectResourcesToIndex", filter, new ResultHandler() {
+ public void handleResult(ResultContext context) {
+ ResourceDto resource = (ResourceDto) context.getResultObject();
+ index(resource, sqlSession);
+ }
+ });
+ } finally {
+ sqlSession.close();
+ profiler.stop();
}
}
static String normalize(String input) {
- String result = StringUtils.trim(input);
- result = StringUtils.lowerCase(result);
- return result;
+ return StringUtils.lowerCase(input);
+ }
+
+ public static boolean isValidInput(String input) {
+ return StringUtils.isNotBlank(input) && input.length() >= MINIMUM_SEARCH_SIZE;
}
}
diff --git a/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexDto.java b/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexDto.java
index 1a351cd0bb5..5272747b0ec 100644
--- a/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexDto.java
+++ b/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexDto.java
@@ -23,6 +23,7 @@ public final class ResourceIndexDto {
private String key;
private int position;
+ private int nameSize;
private int resourceId;
private int projectId;
@@ -39,8 +40,8 @@ public final class ResourceIndexDto {
return position;
}
- public ResourceIndexDto setPosition(int position) {
- this.position = position;
+ public ResourceIndexDto setPosition(int i) {
+ this.position = i;
return this;
}
@@ -48,8 +49,8 @@ public final class ResourceIndexDto {
return resourceId;
}
- public ResourceIndexDto setResourceId(int resourceId) {
- this.resourceId = resourceId;
+ public ResourceIndexDto setResourceId(int i) {
+ this.resourceId = i;
return this;
}
@@ -57,8 +58,17 @@ public final class ResourceIndexDto {
return projectId;
}
- public ResourceIndexDto setProjectId(int projectId) {
- this.projectId = projectId;
+ public ResourceIndexDto setProjectId(int i) {
+ this.projectId = i;
+ return this;
+ }
+
+ public int getNameSize() {
+ return nameSize;
+ }
+
+ public ResourceIndexDto setNameSize(int i) {
+ this.nameSize = i;
return this;
}
}
diff --git a/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexMapper.java b/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexMapper.java
index 110195b37ab..2d28ed4cc95 100644
--- a/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexMapper.java
+++ b/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexMapper.java
@@ -23,7 +23,9 @@ import java.util.List;
public interface ResourceIndexMapper {
- List<ResourceIndexDto> selectLikeKey(String key);
+ List<ResourceIndexDto> selectByKeyword(String keyword);
+
+ ResourceDto selectRootId(int id);
void insert(ResourceIndexDto dto);
}
diff --git a/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexerFilter.java b/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexerFilter.java
new file mode 100644
index 00000000000..01527375cba
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/persistence/resource/ResourceIndexerFilter.java
@@ -0,0 +1,35 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.persistence.resource;
+
+import org.sonar.api.resources.Scopes;
+
+public final class ResourceIndexerFilter {
+ private boolean enabled = true;
+ private String[] scopes = new String[]{Scopes.PROJECT, Scopes.DIRECTORY, Scopes.FILE};
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public String[] getScopes() {
+ return scopes;
+ }
+}
diff --git a/sonar-core/src/main/resources/org/sonar/persistence/resource/ResourceIndexMapper.xml b/sonar-core/src/main/resources/org/sonar/persistence/resource/ResourceIndexMapper.xml
index f2a98725242..c521824b6bc 100644
--- a/sonar-core/src/main/resources/org/sonar/persistence/resource/ResourceIndexMapper.xml
+++ b/sonar-core/src/main/resources/org/sonar/persistence/resource/ResourceIndexMapper.xml
@@ -6,6 +6,7 @@
<resultMap id="resourceIndexMap" type="ResourceIndex">
<result property="key" column="kee"/>
<result property="position" column="position"/>
+ <result property="nameSize" column="name_size"/>
<result property="resourceId" column="resource_id"/>
<result property="projectId" column="project_id"/>
</resultMap>
@@ -14,7 +15,7 @@
kee, position, resource_id, project_id
</sql>
- <select id="selectLikeKey" parameterType="String" resultMap="resourceIndexMap">
+ <select id="selectByKeyword" parameterType="String" resultMap="resourceIndexMap">
select
<include refid="resourceIndexColumns"/>
from resource_index
@@ -22,9 +23,28 @@
order by position asc
</select>
+ <select id="selectResourcesToIndex" parameterType="map" resultType="resource">
+ select id, root_id as "rootId", name
+ from projects
+ where
+ enabled=#{enabled} and
+ copy_resource_id is null and
+ scope in
+ <foreach item="scope" index="index" collection="scopes"
+ open="(" separator="," close=")">#{scope}
+ </foreach>
+ </select>
+
+ <select id="selectRootId" parameterType="int" resultType="resource">
+ select id, root_id as "rootId"
+ from projects
+ where id=#{id}
+ </select>
+
+
<insert id="insert" parameterType="ResourceIndex" useGeneratedKeys="false">
- insert into resource_index (kee, position, resource_id, project_id)
- values (#{key}, #{position}, #{resourceId}, #{projectId})
+ insert into resource_index (kee, position, name_size, resource_id, project_id)
+ values (#{key}, #{position}, #{nameSize}, #{resourceId}, #{projectId})
</insert>
</mapper>
diff --git a/sonar-core/src/main/resources/org/sonar/persistence/schema-derby.ddl b/sonar-core/src/main/resources/org/sonar/persistence/schema-derby.ddl
index 25324f9e366..693b15d635c 100644
--- a/sonar-core/src/main/resources/org/sonar/persistence/schema-derby.ddl
+++ b/sonar-core/src/main/resources/org/sonar/persistence/schema-derby.ddl
@@ -463,6 +463,7 @@ CREATE TABLE "LOADED_TEMPLATES" (
CREATE TABLE "RESOURCE_INDEX" (
"KEE" VARCHAR(100) NOT NULL,
"POSITION" INTEGER NOT NULL,
+ "NAME_SIZE" INTEGER NOT NULL,
"RESOURCE_ID" INTEGER NOT NULL,
"PROJECT_ID" INTEGER NOT NULL
);
diff --git a/sonar-core/src/test/java/org/sonar/core/plugin/AbstractPluginRepositoryTest.java b/sonar-core/src/test/java/org/sonar/core/plugin/AbstractPluginRepositoryTest.java
deleted file mode 100644
index 7d08e633669..00000000000
--- a/sonar-core/src/test/java/org/sonar/core/plugin/AbstractPluginRepositoryTest.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar 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.
- *
- * Sonar 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 Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.core.plugin;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.picocontainer.MutablePicoContainer;
-import org.picocontainer.PicoContainer;
-import org.sonar.api.BatchExtension;
-import org.sonar.api.ExtensionProvider;
-import org.sonar.api.Plugin;
-import org.sonar.api.ServerExtension;
-
-import java.util.Arrays;
-import java.util.Collection;
-
-import static org.hamcrest.Matchers.endsWith;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-@Ignore
-public class AbstractPluginRepositoryTest {
-
-// @Test
-// public void testIsType() {
-// assertThat(AbstractPluginRepository.isType(FakeServerExtension.class, ServerExtension.class), is(true));
-// assertThat(AbstractPluginRepository.isType(new FakeServerExtension(), ServerExtension.class), is(true));
-//
-// assertThat(AbstractPluginRepository.isType(FakeBatchExtension.class, ServerExtension.class), is(false));
-// assertThat(AbstractPluginRepository.isType(new FakeBatchExtension(), ServerExtension.class), is(false));
-// assertThat(AbstractPluginRepository.isType(String.class, ServerExtension.class), is(false));
-// assertThat(AbstractPluginRepository.isType("foo", ServerExtension.class), is(false));
-// }
-//
-// @Test
-// public void extensionKeyshouldBeClassNameIfClass() {
-// assertEquals(AbstractPluginRepository.getExtensionKey(FakeServerExtension.class), FakeServerExtension.class);
-// }
-//
-// @Test
-// public void extensionKeyshouldBeUniqueIfObject() {
-// assertThat((String) AbstractPluginRepository.getExtensionKey(new FakeServerExtension()), endsWith("FakeServerExtension-instance"));
-// }
-//
-// @Test
-// public void shouldBeExtensionProvider() {
-// assertThat(AbstractPluginRepository.isExtensionProvider(BProvider.class), is(true));
-// assertThat(AbstractPluginRepository.isExtensionProvider(new BProvider(new A())), is(true));
-// }
-//
-// @Test
-// public void shouldRegisterExtensionProviders() {
-// MutablePicoContainer pico = IocContainer.buildPicoContainer();
-// AbstractPluginRepository repository = new AbstractPluginRepository() {
-// @Override
-// protected boolean shouldRegisterExtension(PicoContainer container, String pluginKey, Object extension) {
-// return isType(extension, ServerExtension.class);
-// }
-// };
-//
-// Plugin plugin = mock(Plugin.class);
-// when(plugin.getExtensions()).thenReturn(Arrays.asList(A.class, BProvider.class, B.class, C.class, D.class));
-// repository.registerPlugin(pico, plugin, "foo");
-// repository.invokeExtensionProviders(pico);
-// pico.start();
-//
-// assertThat(pico.getComponent(A.class), is(A.class));
-// assertThat(pico.getComponent(C.class), is(C.class));
-// assertThat(pico.getComponent(D.class), is(D.class));
-// assertThat(pico.getComponent(C.class).getBees().length, is(3));// 1 in plugin.getExtensions() + 2 created by BProvider
-// assertThat(pico.getComponent(D.class).getBees().length, is(3));
-// assertThat(pico.getComponent(BProvider.class).calls, is(1)); // do not create B instances two times (C and D dependencies)
-// assertThat(pico.getComponents(B.class).size(), is(3));
-// }
-//
-// public static class FakeServerExtension implements ServerExtension {
-// @Override
-// public String toString() {
-// return "instance";
-// }
-// }
-//
-// public static class FakeBatchExtension implements BatchExtension {
-//
-// }
-//
-// public static class A implements ServerExtension {
-// }
-//
-// public static class B implements ServerExtension {
-// private A a;
-//
-// public B(A a) {
-// this.a = a;
-// }
-// }
-//
-//
-// public static class C implements ServerExtension {
-// private B[] bees;
-//
-// public C(B[] bees) {
-// this.bees = bees;
-// }
-//
-// public B[] getBees() {
-// return bees;
-// }
-// }
-//
-// public static class D implements ServerExtension {
-// private B[] bees;
-//
-// public D(B[] bees) {
-// this.bees = bees;
-// }
-//
-// public B[] getBees() {
-// return bees;
-// }
-// }
-//
-// public static class BProvider extends ExtensionProvider implements ServerExtension {
-//
-// private int calls = 0;
-// private A a;
-//
-// public BProvider(A a) {
-// this.a = a;
-// }
-//
-// public Collection<B> provide() {
-// calls++;
-// return Arrays.asList(new B(a), new B(a));
-// }
-// }
-
-
-}
diff --git a/sonar-core/src/test/java/org/sonar/persistence/resource/ResourceIndexDaoTest.java b/sonar-core/src/test/java/org/sonar/persistence/resource/ResourceIndexDaoTest.java
index 8d8ec54c797..c803812edb4 100644
--- a/sonar-core/src/test/java/org/sonar/persistence/resource/ResourceIndexDaoTest.java
+++ b/sonar-core/src/test/java/org/sonar/persistence/resource/ResourceIndexDaoTest.java
@@ -83,4 +83,13 @@ public class ResourceIndexDaoTest extends DaoTestCase {
checkTables("testIndex", "resource_index");
}
+ @Test
+ public void testIndexAll() {
+ setupData("testIndexAll");
+
+ dao.index(new ResourceIndexerFilter());
+
+ checkTables("testIndexAll", "resource_index");
+ }
+
}
diff --git a/sonar-core/src/test/resources/logback-test.xml b/sonar-core/src/test/resources/logback-test.xml
index a0c5d2f364f..9c0d4b1c3ba 100644
--- a/sonar-core/src/test/resources/logback-test.xml
+++ b/sonar-core/src/test/resources/logback-test.xml
@@ -19,6 +19,11 @@
<level value="WARN"/>
</logger>
+ <!-- set to level DEBUG to log SQL requests executed by MyBatis -->
+ <logger name="java.sql">
+ <level value="WARN"/>
+ </logger>
+
<root>
<level value="INFO"/>
<appender-ref ref="STDOUT"/>
diff --git a/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testIndex-result.xml b/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testIndex-result.xml
index 6b11fda8cb5..3c1544c8f9a 100644
--- a/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testIndex-result.xml
+++ b/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testIndex-result.xml
@@ -1,8 +1,8 @@
<dataset>
- <resource_index kee="ziputils" position="0" resource_id="10" project_id="8"/>
- <resource_index kee="iputils" position="1" resource_id="10" project_id="8"/>
- <resource_index kee="putils" position="2" resource_id="10" project_id="8"/>
- <resource_index kee="utils" position="3" resource_id="10" project_id="8"/>
- <resource_index kee="tils" position="4" resource_id="10" project_id="8"/>
- <resource_index kee="ils" position="5" resource_id="10" project_id="8"/>
+ <resource_index kee="ziputils" position="0" name_size="8" resource_id="10" project_id="8"/>
+ <resource_index kee="iputils" position="1" name_size="8" resource_id="10" project_id="8"/>
+ <resource_index kee="putils" position="2" name_size="8" resource_id="10" project_id="8"/>
+ <resource_index kee="utils" position="3" name_size="8" resource_id="10" project_id="8"/>
+ <resource_index kee="tils" position="4" name_size="8" resource_id="10" project_id="8"/>
+ <resource_index kee="ils" position="5" name_size="8" resource_id="10" project_id="8"/>
</dataset> \ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testIndexAll-result.xml b/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testIndexAll-result.xml
new file mode 100644
index 00000000000..c1dd1bf9944
--- /dev/null
+++ b/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testIndexAll-result.xml
@@ -0,0 +1,32 @@
+<dataset>
+
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ root_id="[null]"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="org.struts.RequestContext" id="3" scope="FIL" qualifier="CLA" kee="org.struts:struts:org.struts.RequestContext"
+ name="RequestContext" root_id="1"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]"/>
+
+ <!-- Struts -->
+ <resource_index kee="struts" position="0" name_size="6" resource_id="1" project_id="1"/>
+ <resource_index kee="truts" position="1" name_size="6" resource_id="1" project_id="1"/>
+ <resource_index kee="ruts" position="2" name_size="6" resource_id="1" project_id="1"/>
+ <resource_index kee="uts" position="3" name_size="6" resource_id="1" project_id="1"/>
+
+ <!-- RequestContext -->
+ <resource_index kee="requestcontext" position="0" name_size="14" resource_id="3" project_id="1"/>
+ <resource_index kee="equestcontext" position="1" name_size="14" resource_id="3" project_id="1"/>
+ <resource_index kee="questcontext" position="2" name_size="14" resource_id="3" project_id="1"/>
+ <resource_index kee="uestcontext" position="3" name_size="14" resource_id="3" project_id="1"/>
+ <resource_index kee="estcontext" position="4" name_size="14" resource_id="3" project_id="1"/>
+ <resource_index kee="stcontext" position="5" name_size="14" resource_id="3" project_id="1"/>
+ <resource_index kee="tcontext" position="6" name_size="14" resource_id="3" project_id="1"/>
+ <resource_index kee="context" position="7" name_size="14" resource_id="3" project_id="1"/>
+ <resource_index kee="ontext" position="8" name_size="14" resource_id="3" project_id="1"/>
+ <resource_index kee="ntext" position="9" name_size="14" resource_id="3" project_id="1"/>
+ <resource_index kee="text" position="10" name_size="14" resource_id="3" project_id="1"/>
+ <resource_index kee="ext" position="11" name_size="14" resource_id="3" project_id="1"/>
+</dataset>
diff --git a/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testIndexAll.xml b/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testIndexAll.xml
new file mode 100644
index 00000000000..f89a5e67f25
--- /dev/null
+++ b/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testIndexAll.xml
@@ -0,0 +1,13 @@
+<dataset>
+
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
+ root_id="[null]"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]"/>
+
+ <projects long_name="org.struts.RequestContext" id="3" scope="FIL" qualifier="CLA" kee="org.struts:struts:org.struts.RequestContext"
+ name="RequestContext" root_id="1"
+ description="[null]"
+ enabled="[true]" language="java" copy_resource_id="[null]"/>
+
+</dataset>
diff --git a/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testSearch.xml b/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testSearch.xml
index 95dc517e22c..a885e5ffa72 100644
--- a/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testSearch.xml
+++ b/sonar-core/src/test/resources/org/sonar/persistence/resource/ResourceIndexDaoTest/testSearch.xml
@@ -1,18 +1,18 @@
<dataset>
<!-- ZipUtils -->
- <resource_index kee="ziputils" position="0" resource_id="10" project_id="8"/>
- <resource_index kee="iputils" position="1" resource_id="10" project_id="8"/>
- <resource_index kee="putils" position="2" resource_id="10" project_id="8"/>
- <resource_index kee="utils" position="3" resource_id="10" project_id="8"/>
- <resource_index kee="tils" position="4" resource_id="10" project_id="8"/>
+ <resource_index kee="ziputils" position="0" name_size="8" resource_id="10" project_id="8"/>
+ <resource_index kee="iputils" position="1" name_size="8" resource_id="10" project_id="8"/>
+ <resource_index kee="putils" position="2" name_size="8" resource_id="10" project_id="8"/>
+ <resource_index kee="utils" position="3" name_size="8" resource_id="10" project_id="8"/>
+ <resource_index kee="tils" position="4" name_size="8" resource_id="10" project_id="8"/>
<!-- DateUtils -->
- <resource_index kee="dateutils" position="0" resource_id="130" project_id="120"/>
- <resource_index kee="ateutils" position="1" resource_id="130" project_id="120"/>
- <resource_index kee="teutils" position="2" resource_id="130" project_id="120"/>
- <resource_index kee="eutils" position="3" resource_id="130" project_id="120"/>
- <resource_index kee="utils" position="4" resource_id="130" project_id="120"/>
- <resource_index kee="tils" position="5" resource_id="130" project_id="120"/>
+ <resource_index kee="dateutils" position="0" name_size="9" resource_id="130" project_id="120"/>
+ <resource_index kee="ateutils" position="1" name_size="9" resource_id="130" project_id="120"/>
+ <resource_index kee="teutils" position="2" name_size="9" resource_id="130" project_id="120"/>
+ <resource_index kee="eutils" position="3" name_size="9" resource_id="130" project_id="120"/>
+ <resource_index kee="utils" position="4" name_size="9" resource_id="130" project_id="120"/>
+ <resource_index kee="tils" position="5" name_size="9" resource_id="130" project_id="120"/>
</dataset> \ No newline at end of file
diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
index 0514b55e8b7..424dbef4868 100644
--- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
+++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
@@ -37,6 +37,8 @@ import org.sonar.core.i18n.RuleI18nManager;
import org.sonar.markdown.Markdown;
import org.sonar.persistence.Database;
import org.sonar.persistence.DatabaseMigrator;
+import org.sonar.persistence.resource.ResourceIndexDao;
+import org.sonar.persistence.resource.ResourceIndexerFilter;
import org.sonar.server.configuration.Backup;
import org.sonar.server.configuration.ProfilesManager;
import org.sonar.server.filters.Filter;
@@ -378,4 +380,14 @@ public final class JRubyFacade {
public ComponentContainer getContainer() {
return Platform.getInstance().getContainer();
}
+
+
+ // RESOURCE SEARCH ENGINE
+ public void indexResources() {
+ getContainer().getComponentByType(ResourceIndexDao.class).index(new ResourceIndexerFilter());
+ }
+
+ public boolean isValidResourceSearchInput(String input) {
+ return ResourceIndexDao.isValidInput(input);
+ }
}
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/search_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/search_controller.rb
new file mode 100644
index 00000000000..d68792f8143
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/search_controller.rb
@@ -0,0 +1,63 @@
+#
+# Sonar, entreprise quality control tool.
+# Copyright (C) 2008-2011 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# Sonar 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.
+#
+# Sonar 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 Sonar; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+#
+class SearchController < ApplicationController
+
+ SECTION=Navigation::SECTION_HOME
+
+ verify :method => :post, :only => [:reset]
+ before_filter :admin_required, :except => ['index']
+
+ # Do not exceed 1000 because of the Oracle limition on IN statements
+ MAX_RESULTS = 50
+
+ def index
+ @start_time = Time.now
+ @search = params[:s]
+ if @search
+ if java_facade.isValidResourceSearchInput(@search.to_s)
+ normalized_search = @search.downcase
+ @results = ResourceIndex.find(:all,
+ :conditions => ["resource_index.kee like ?", normalized_search + '%'],
+ :order => 'name_size, position')
+
+ @results = select_authorized(:user, @results)
+ @total = @results.size
+ @results = @results[0...MAX_RESULTS]
+
+ @resources_by_id = {}
+ unless @results.empty?
+ Project.find(:all, :conditions => ['id in (?)', @results.map { |resource_index| resource_index.resource_id }]).each do |resource|
+ @resources_by_id[resource.id]=resource
+ end
+ end
+ else
+ flash[:warning]='Please refine your search'
+ end
+ end
+ end
+
+ # Start indexing resources
+ #
+ # curl -v -u admin:admin -X POST http://localhost:9000/search/reset
+ def reset
+ java_facade.indexResources()
+ render :text => 'indexing'
+ end
+end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb
index 2321c8a6b00..62f4d8b44f2 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb
@@ -93,7 +93,7 @@ module ApplicationHelper
if date
label = message('since_version_detailed', :params => [mode_param.to_s, date.strftime("%Y %b %d").to_s])
else
- label = message('since_version', :params => mode_param.to_s)
+ label = message('since_version', :params => mode_param.to_s)
end
elsif mode=='previous_analysis'
if !date.nil?
@@ -232,7 +232,7 @@ module ApplicationHelper
end
url_params={:controller => 'drilldown', :action => 'measures', :metric => metric_key, :id => options[:resource]||@project.id}
-
+
url_for(options.merge(url_params))
end
@@ -341,6 +341,20 @@ module ApplicationHelper
end
+ def link_to_resource_home(resource, options={})
+ period_index=options[:period]
+ period_index=nil if period_index && period_index<=0
+ if resource.display_dashboard?
+ link_to(options[:name] || resource.name, {:controller => 'dashboard', :action => 'index', :id => (resource.copy_resource_id||resource.id), :period => period_index, :tab => options[:tab], :rule => options[:rule]}, :title => options[:title])
+ else
+ if options[:line]
+ anchor= 'L' + options[:line].to_s
+ end
+ link_to(options[:name] || resource.name, {:controller => 'resource', :action => 'index', :anchor => anchor, :id => resource.id, :period => period_index, :tab => options[:tab], :rule => options[:rule], :metric => options[:metric]}, :popup => ['resource', 'height=800,width=900,scrollbars=1,resizable=1'], :title => options[:title])
+ end
+ end
+
+
#
#
# JFree Eastwood is a partial implementation of Google Chart Api
@@ -423,8 +437,8 @@ module ApplicationHelper
initial_tooltip=message('click_to_remove_from_favourites')
end
- link_to_remote('', :url => { :controller => 'favourites', :action => 'toggle', :id => resource_id, :elt => html_id},
- :method => :post, :html => {:class => initial_class, :id => html_id, :alt => initial_tooltip, :title => initial_tooltip})
+ link_to_remote('', :url => {:controller => 'favourites', :action => 'toggle', :id => resource_id, :elt => html_id},
+ :method => :post, :html => {:class => initial_class, :id => html_id, :alt => initial_tooltip, :title => initial_tooltip})
end
#
@@ -453,12 +467,12 @@ module ApplicationHelper
filename = m.tendency.to_s
case m.tendency_qualitative
- when 0
- filename+= '-black'
- when -1
- filename+= '-red'
- when 1
- filename+= '-green'
+ when 0
+ filename+= '-black'
+ when -1
+ filename+= '-red'
+ when 1
+ filename+= '-green'
end
image_tag("tendency/#{filename}-small.png")
end
@@ -550,7 +564,7 @@ module ApplicationHelper
html = options[:default].to_s if html.nil? && options[:default]
html
end
-
+
#
# Creates a pagination section for the given array (items_array) if its size exceeds the pagination size (default: 20).
# Upon completion of this method, the HTML is returned and the given array contains only the selected elements.
@@ -563,8 +577,8 @@ module ApplicationHelper
#
def paginate(items_array, options={})
html = items_array.size.to_s + " " + message('results').downcase
-
- page_size = options[:page_size] || 20
+
+ page_size = options[:page_size] || 20
if items_array.size > page_size
# computes the pagination elements
page_id = (params[:page_id] ? params[:page_id].to_i : 1)
@@ -573,7 +587,7 @@ module ApplicationHelper
from = (page_id-1) * page_size
to = (page_id*page_size)-1
to = items_array.size-1 if to >= items_array.size
-
+
# render the pagination links
html += " | "
html += link_to_if page_id>1, message('paging_previous'), {:overwrite_params => {:page_id => page_id-1}}
@@ -583,14 +597,14 @@ module ApplicationHelper
html += " "
end
html += link_to_if page_id<page_count, message('paging_next'), {:overwrite_params => {:page_id => 1+page_id}}
-
+
# and adapt the items_array object according to the pagination
items_to_keep = items_array[from..to]
items_array.clear
- items_to_keep.each {|i| items_array << i}
+ items_to_keep.each { |i| items_array << i }
end
-
- html
+
+ html
end
-
+
end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/resource_index.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/resource_index.rb
new file mode 100644
index 00000000000..1db1a1cd361
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/models/resource_index.rb
@@ -0,0 +1,30 @@
+#
+# Sonar, entreprise quality control tool.
+# Copyright (C) 2008-2011 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# Sonar 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.
+#
+# Sonar 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 Sonar; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+#
+class ResourceIndex < ActiveRecord::Base
+
+ set_table_name 'resource_index'
+
+ belongs_to :resource, :class_name => 'Project', :foreign_key => 'resource_id'
+ belongs_to :project, :class_name => 'Project', :foreign_key => 'project_id'
+
+ def resource_id_for_authorization
+ project_id
+ end
+end \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/search/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/search/index.html.erb
new file mode 100644
index 00000000000..d9b0de36c0d
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/search/index.html.erb
@@ -0,0 +1,46 @@
+<form method="GET" action="index">
+ <input type="text" name="s" id="searchText" value="<%= @search -%>">
+ <input type="submit" value="Search" id="submitSearch">
+</form>
+
+<% if @results %>
+ <table class="data width100" id="searchResults">
+ <thead>
+ <tr>
+ <th colspan="3"></th>
+ </tr>
+ </thead>
+ <tbody>
+ <% @results.each do |resource_index|
+ resource=@resources_by_id[resource_index.resource_id]
+ %>
+ <tr class="<%= cycle('even', 'odd') -%>">
+ <td class="thin nowrap">
+ <% if resource.display_dashboard? %>
+ <a href="<%= ApplicationController.root_context -%>/components/index/<%= resource.id -%>"><img src="<%= ApplicationController.root_context -%>/images/zoom.png"></a>
+ <% end %>
+ </td>
+ <td class="thin nowrap">
+ <%= qualifier_icon resource -%>
+ </td>
+ <td>
+ <%= link_to_resource_home resource, :name => highlight(resource.name(true), @search) -%>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td colspan="3">
+ <% if @total>@results.size %>
+ <%= @results.size -%> among
+ <% end %>
+ <%= @total -%> results (<%= Time.now-@start_time -%> seconds)
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+<% end %>
+<script type="text/javascript">
+ $('searchText').focus();
+</script> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/237_create_table_resource_index.rb b/sonar-server/src/main/webapp/WEB-INF/db/migrate/237_create_table_resource_index.rb
index 8b651d244f1..c6190f0cfe6 100644
--- a/sonar-server/src/main/webapp/WEB-INF/db/migrate/237_create_table_resource_index.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/db/migrate/237_create_table_resource_index.rb
@@ -27,10 +27,12 @@ class CreateTableResourceIndex < ActiveRecord::Migration
create_table 'resource_index', :id => false do |t|
t.column 'kee', :string, :null => false, :limit => 100
t.column 'position', :integer, :null => false
+ t.column 'name_size', :integer, :null => false
t.column 'resource_id', :integer, :null => false
t.column 'project_id', :integer, :null => false
end
add_index 'resource_index', 'kee', :name => 'resource_index_key'
+ add_index 'resource_index', 'resource_id', :name => 'resource_index_rid'
end
end
diff --git a/sonar-server/src/main/webapp/stylesheets/style.css b/sonar-server/src/main/webapp/stylesheets/style.css
index 9fb6b224d75..c45ce56b77a 100644
--- a/sonar-server/src/main/webapp/stylesheets/style.css
+++ b/sonar-server/src/main/webapp/stylesheets/style.css
@@ -491,6 +491,10 @@ h4, .h4 {
color: #777;
}
+.highlight {
+ font-weight: bold;
+}
+
.subtitle {
color: #777;
font-size: 85%;
@@ -1239,7 +1243,9 @@ div.progress td {
}
div.progress td a {
- display: block; width: 100%; height: 100%;
+ display: block;
+ width: 100%;
+ height: 100%;
}
div.progress td.resolved {
@@ -1257,7 +1263,6 @@ div.progress div.note {
white-space: nowrap;
}
-
/* AUTOCOMPLETE FIELDS */
div.autocomplete {
position: absolute;