diff --git a/grails/build.xml b/grails/build.xml index bc256f9..8593cd2 100644 --- a/grails/build.xml +++ b/grails/build.xml @@ -16,7 +16,7 @@ - diff --git a/grails/grails-app/conf/Config.groovy b/grails/grails-app/conf/Config.groovy index e7a04e8..e4bcab1 100644 --- a/grails/grails-app/conf/Config.groovy +++ b/grails/grails-app/conf/Config.groovy @@ -92,7 +92,7 @@ uiperformance.exclusions = [ ] // statSleep is the time to sleep between calculating test run stats -statSleep = 1000 +statSleep = 3000 // testOutcomeAndTestRunInitSleep is the time to sleep between initializing TestOutcomes and TestRuns testOutcomeAndTestRunInitSleepTime = 5000 diff --git a/grails/grails-app/conf/DataSource.groovy b/grails/grails-app/conf/DataSource.groovy index 5218538..25c4129 100644 --- a/grails/grails-app/conf/DataSource.groovy +++ b/grails/grails-app/conf/DataSource.groovy @@ -7,6 +7,8 @@ hibernate { cache.use_second_level_cache=true cache.use_query_cache=true cache.provider_class='org.hibernate.cache.EhCacheProvider' + dialect='cuanto.CustomDialect' + //show_sql=true } // environment specific settings environments { @@ -21,6 +23,9 @@ environments { } } test { + hibernate { + dialect='org.hibernate.dialect.HSQLDialect' + } dataSource { dbCreate = "update" driverClassName = "org.hsqldb.jdbcDriver" @@ -28,6 +33,17 @@ environments { username = "sa" password = "" } + // use this for tag filter tests which are dependent on MySQL + /* + dataSource { + dbCreate = "update" + pooled = true + username = "root" + password = "" + driverClassName = "com.mysql.jdbc.Driver" + url = "jdbc:mysql://localhost:3306/cuanto-test?autoreconnect=true" + } + */ } production { dataSource { diff --git a/grails/grails-app/conf/spring/resources.groovy b/grails/grails-app/conf/spring/resources.groovy index b91c9be..2f42b2e 100644 --- a/grails/grails-app/conf/spring/resources.groovy +++ b/grails/grails-app/conf/spring/resources.groovy @@ -29,6 +29,7 @@ import cuanto.parsers.CuantoManualParser import cuanto.parsers.TestNgParser import cuanto.queryprocessor.TestRunQueryModule import cuanto.queryprocessor.TestResultIsFailureQueryModule +import cuanto.queryprocessor.TestResultIsNonPassingQueryModule import cuanto.queryprocessor.TestResultQueryModule import cuanto.queryprocessor.TestCaseFullNameQueryModule import cuanto.queryprocessor.TestCaseParametersQueryModule @@ -75,6 +76,7 @@ beans = { new TestRunQueryModule(), new TestResultIsFailureQueryModule(), new TestResultIsSkipQueryModule(), + new TestResultIsNonPassingQueryModule(), new TestResultQueryModule(), new TestCaseFullNameQueryModule(), new TestCaseParametersQueryModule(), @@ -88,11 +90,11 @@ beans = { new TestCaseQueryModule(), new NoteQueryModule(), new TestOutputQueryModule(), - new TagNameQueryModule(), new HasTagsQueryModule(), new DateExecutedQueryModule(), new FailureStatusChangedQueryModule(), - new TestOutcomeHasAllPropertiesQueryModule() + new TestOutcomeHasAllPropertiesQueryModule(), + new TagNameQueryModule() ] } } \ No newline at end of file diff --git a/grails/grails-app/controllers/cuanto/ProjectController.groovy b/grails/grails-app/controllers/cuanto/ProjectController.groovy index dae6b4d..709a724 100644 --- a/grails/grails-app/controllers/cuanto/ProjectController.groovy +++ b/grails/grails-app/controllers/cuanto/ProjectController.groovy @@ -202,9 +202,10 @@ class ProjectController { if (params.id) { Project proj = Project.get(params.id) if (proj) { + def urlBase = request.scheme + "://" + request.serverName + ":" + request.serverPort + "/" + grailsApplication.metadata.'app.name' render(feedType: "rss", feedVersion: "2.0") { title = "${proj.toString()} Recent Results" - link = "http://your.test.server/yourController/feed" + link = createLink(controller: "project", action: "feed", id: proj.id, base: urlBase) description = "Recent Test Results for ${proj.toString()}" List testRuns = testRunService.getTestRunsForProject([id: proj.id, offset: 0, max: Defaults.ItemsPerFeed, @@ -214,6 +215,7 @@ class ProjectController { def stats = TestRunStats.findByTestRun(testRun) if (stats){ entry(testRun.dateExecuted) { + // TODO: summary does not appear to be a valid action. what's it supposed to link to? link = createLink(controller: "testRun", action: "summary", id: testRun.id) def feedTxt = testRunService.getFeedText(testRun, stats) return feedTxt diff --git a/grails/grails-app/controllers/cuanto/TestRunController.groovy b/grails/grails-app/controllers/cuanto/TestRunController.groovy index c909063..b2fcee9 100644 --- a/grails/grails-app/controllers/cuanto/TestRunController.groovy +++ b/grails/grails-app/controllers/cuanto/TestRunController.groovy @@ -152,6 +152,15 @@ class TestRunController { def outcomes = { Map results = testOutcomeService.getTestOutcomeQueryResultsForParams(params) + def columns + if (params.columns) { + // I don't know why occasionally the columns come in as an array instead of String + if (params.columns.class.isArray() && !params.columns.isEmpty()) { + params.columns = params.columns[0] + } + columns = params.columns.tokenize(',').collect{ it.tokenize('|') } + } + withFormat { json { def formatter = testOutcomeService.getTestCaseFormatter(params.tcFormat) @@ -175,11 +184,11 @@ class TestRunController { } csv { response.contentType = "text/csv" - render testOutcomeService.getDelimitedTextForTestOutcomes(results?.testOutcomes, ",") + render testOutcomeService.getDelimitedTextForTestOutcomes(results?.testOutcomes, columns, ",") } tsv { response.contentType = 'text/tab-separated-values' - render testOutcomeService.getDelimitedTextForTestOutcomes(results?.testOutcomes, "\t") + render testOutcomeService.getDelimitedTextForTestOutcomes(results?.testOutcomes, columns, "\t") } } } @@ -447,6 +456,7 @@ class TestRunController { filterList += [id: "newpasses", value: "New Passes"] filterList += [id: "allresults", value: "All Results"] filterList += [id: "allquarantined", value: "All Quarantined"] + filterList += [id: "nonPassing", value: "All Non-Passing"] return filterList } diff --git a/grails/grails-app/domain/cuanto/TestOutcomeQueryFilter.groovy b/grails/grails-app/domain/cuanto/TestOutcomeQueryFilter.groovy index cb6a39d..74dab22 100644 --- a/grails/grails-app/domain/cuanto/TestOutcomeQueryFilter.groovy +++ b/grails/grails-app/domain/cuanto/TestOutcomeQueryFilter.groovy @@ -29,6 +29,7 @@ public class TestOutcomeQueryFilter implements QueryFilter { testRun(nullable: true) isFailure(nullable: true) isSkip(nullable: true) + isNonPassing(nullable: true) testResult(nullable:true) testCaseFullName(nullable: true) testCaseParameters(nullable: true) @@ -53,7 +54,7 @@ public class TestOutcomeQueryFilter implements QueryFilter { TestOutcomeQueryFilter() {} TestOutcomeQueryFilter(TestOutcomeQueryFilter filterToCopy) { - ["testRun", "isFailure", "isSkip", "testResult", "testCaseFullName", "testCaseParameters", "testCasePackage", "project", + ["testRun", "isFailure", "isSkip", "isNonPassing", "testResult", "testCaseFullName", "testCaseParameters", "testCasePackage", "project", "testResultIncludedInCalculations", "isAnalyzed", "analysisState", "bug", "owner", "testCase", "note", "testOutput", "dateCriteria", "sorts", "queryOffset", "queryMax", "isFailureStatusChanged", "hasAllTestOutcomeProperties", "successRate"].each { @@ -84,6 +85,11 @@ public class TestOutcomeQueryFilter implements QueryFilter { */ Boolean isSkip + /** + * If true then all returned outcomes that are considered failures or skips will be returned. + * If null, outcomes will not be returned based on isNonPassing status + */ + Boolean isNonPassing /** * If not null, then all returned outcomes must have this exact TestResult. @@ -221,7 +227,11 @@ public class TestOutcomeQueryFilter implements QueryFilter { String selectClause() { - def select = "select distinct t" + def select = "select distinct " + if (tags) { + select += "group_concat(tag_0.name), " + } + select += " t" if (sorts) { def newList = sorts.collect{"t." + it.sort} select += ", " + newList.join(", ") @@ -235,7 +245,9 @@ public class TestOutcomeQueryFilter implements QueryFilter { String countClause() { - "select count(distinct t) " + // don't know how to combine group_concat with a count + //"select count(distinct t) " + return selectClause() } @@ -300,10 +312,10 @@ public class TestOutcomeQueryFilter implements QueryFilter { public List resultTransform(List results) { - if (sorts) { - return results.collect{it[0]} - } else { - return results - } + if (sorts || tags) { + return results.collect{ it[tags ? 1 : 0] } + } else { + return results + } } } \ No newline at end of file diff --git a/grails/grails-app/jobs/TestRunStatsJob.groovy b/grails/grails-app/jobs/TestRunStatsJob.groovy index b9d2b76..9a5f5b4 100644 --- a/grails/grails-app/jobs/TestRunStatsJob.groovy +++ b/grails/grails-app/jobs/TestRunStatsJob.groovy @@ -26,8 +26,6 @@ class TestRunStatsJob { } def execute() { - if (!statisticService.processingTestRunStats) { - statisticService.processTestRunStats() - } + statisticService.processTestRunStats() } } diff --git a/grails/grails-app/services/cuanto/DataService.groovy b/grails/grails-app/services/cuanto/DataService.groovy index c690728..f1e0413 100644 --- a/grails/grails-app/services/cuanto/DataService.groovy +++ b/grails/grails-app/services/cuanto/DataService.groovy @@ -393,7 +393,7 @@ class DataService { } else { results = TestOutcome.executeQuery(cuantoQuery.hql) } - return results[0] + return results.size() } diff --git a/grails/grails-app/services/cuanto/ProjectService.groovy b/grails/grails-app/services/cuanto/ProjectService.groovy index 5c1cf6d..d09e388 100644 --- a/grails/grails-app/services/cuanto/ProjectService.groovy +++ b/grails/grails-app/services/cuanto/ProjectService.groovy @@ -242,4 +242,4 @@ class ProjectService { deleteProjectGroupIfUnused(group) } } -} \ No newline at end of file +} diff --git a/grails/grails-app/services/cuanto/StatisticService.groovy b/grails/grails-app/services/cuanto/StatisticService.groovy index 597facc..cdf4c65 100644 --- a/grails/grails-app/services/cuanto/StatisticService.groovy +++ b/grails/grails-app/services/cuanto/StatisticService.groovy @@ -34,8 +34,10 @@ class StatisticService { def testRunService def grailsApplication + def outcomeBatchSize = 100 + def queuedTestResultStats = [] as Set + int numRecentTestOutcomes = 40 - Boolean processingTestRunStats = false; final private static String queueLock = "Test Run Stat Queue Lock" final private static String calcLock = "Test Run Calculation Lock" @@ -103,32 +105,36 @@ class StatisticService { def processTestRunStats() { synchronized (calcLock) { def queueSize = QueuedTestRunStat.list().size() - while (queueSize > 0) { - log.debug "${queueSize} items in stat queue" + while (queueSize > 0 || queuedTestResultStats.size() > 0) { + log.debug "${queueSize} items in stat queue and ${queuedTestResultStats.size()} in result stat queue" QueuedTestRunStat queuedItem = getFirstTestRunIdInQueue() if (queuedItem) { + log.info "Calculating stats for test run ${queuedItem.testRunId}" + def startTime = System.currentTimeMillis() try { + queuedTestResultStats.add(queuedItem.testRunId) QueuedTestRunStat.withTransaction { calculateTestRunStats(queuedItem.testRunId) - calculateTestOutcomeStats(queuedItem.testRunId) - queuedItem.delete(flush: true) - queueSize = QueuedTestRunStat.list().size() } - } catch (OptimisticLockingFailureException e) { - log.info "OptimisticLockingFailureException for test run ${queuedItem.testRunId}" - // leave it in queue so it gets tried again - } catch (HibernateOptimisticLockingFailureException e) { - log.info "HibernateOptimisticLockingFailureException for test run ${queuedItem.testRunId}" - } catch (StaleObjectStateException e) { - log.info "StaleObjectStateException for test run ${queuedItem.testRunId}" - // leave it in queue so it gets tried again - } - } - } - if (grailsApplication.config.statSleep) { - sleep(grailsApplication.config.statSleep) + def elapsed = System.currentTimeMillis() - startTime + log.info "Calculated stats for ${queuedItem.testRunId} in ${elapsed} ms" + } catch (Exception e) { + // re-queue to the "end" of the queue so other items aren't starved + log.info "Exception for test run ${queuedItem.testRunId} - ${e.toString()}" + queuedItem.dateCreated = new Date() + dataService.saveDomainObject(queuedItem, true) + } + } else { + // process some queued test outcome stats + QueuedTestRunStat.withTransaction { + calculateTestOutcomeStats(queuedTestResultStats.toList().first()) + } + } + if (grailsApplication.config.statSleep) { + sleep(grailsApplication.config.statSleep) + } + queueSize = QueuedTestRunStat.list().size() } - } } @@ -143,12 +149,17 @@ class StatisticService { QueuedTestRunStat getFirstTestRunIdInQueue() { - def latest = QueuedTestRunStat.listOrderByDateCreated(max: 1) - if (latest.size() == 0) { - return null - } else { - return latest[0] - } + synchronized (queueLock) { + QueuedTestRunStat.withTransaction { + def queuedItem = null + def latest = QueuedTestRunStat.listOrderByDateCreated(max: 1) + if (latest.size() != 0) { + queuedItem = latest[0] + queuedItem.delete(flush: true) + } + return queuedItem + } + } } @@ -247,20 +258,30 @@ class StatisticService { if (!testRun) { log.error "Couldn't find test run ${testRunId}" } else { + def counter = outcomeBatchSize + def startTime = System.currentTimeMillis() List testOutcomes = TestOutcome.findAllByTestRun(testRun) - for (TestOutcome testOutcome : testOutcomes) - { - testOutcome.lock() - def stats = new TestOutcomeStats() - dataService.saveDomainObject(stats, true) - testOutcome.testOutcomeStats = stats - List recentTestResults = TestOutcome.executeQuery( - "SELECT to.testResult FROM cuanto.TestOutcome to WHERE to.testCase = ?", - [testOutcome.testCase], [max: numRecentTestOutcomes, sort: 'id', order: 'desc']) - calculateStreak(testOutcome, recentTestResults) - calculateSuccessRate(testOutcome, recentTestResults) - testOutcome.save() + for (TestOutcome testOutcome : testOutcomes) { + if (!testOutcome.testOutcomeStats) { + testOutcome.lock() + def stats = new TestOutcomeStats() + dataService.saveDomainObject(stats, true) + testOutcome.testOutcomeStats = stats + List recentTestResults = TestOutcome.executeQuery( + "SELECT to.testResult FROM cuanto.TestOutcome to WHERE to.testCase = ?", + [testOutcome.testCase], [max: numRecentTestOutcomes, sort: 'id', order: 'desc']) + calculateStreak(testOutcome, recentTestResults) + calculateSuccessRate(testOutcome, recentTestResults) + testOutcome.save() + if (counter-- <= 0) { + log.info "processed a batch of ${outcomeBatchSize} outcome stats for test run ${testRunId} in " + (System.currentTimeMillis() - startTime) + " ms" + return + } + } } + // made it to the end of for every testOutcome! + log.info "finished processing test outcome stats for test run ${testRunId} in " + (System.currentTimeMillis() - startTime) + " ms" + queuedTestResultStats.remove(testRunId) } } @@ -324,7 +345,7 @@ class StatisticService { List getTagStatistics(TestRun testRun) { - testRun = testRun.refresh() + testRun = testRun.refresh() def tagStats = [] if (Environment.current != Environment.TEST) { // The HSQL database doesn't like the following query, so we won't do it in testing. @@ -347,7 +368,7 @@ class StatisticService { tagStat.skipped = skipped ?: 0; log.debug "${skipped} skipped for ${tag.name}" - def duration = rawStats.collect { it[iDuration] }.sum() + def duration = rawStats.collect { it[iDuration] ?: 0 }.sum() tagStat.duration = Math.max(0, duration ?: 0); def total = rawStats.collect {it[iCount]}.sum() diff --git a/grails/grails-app/services/cuanto/TestOutcomeService.groovy b/grails/grails-app/services/cuanto/TestOutcomeService.groovy index 8926b4c..8df65d3 100644 --- a/grails/grails-app/services/cuanto/TestOutcomeService.groovy +++ b/grails/grails-app/services/cuanto/TestOutcomeService.groovy @@ -405,6 +405,8 @@ class TestOutcomeService { filter.isSkip = true } else if (params.filter?.equalsIgnoreCase("allquarantined")) { filter.analysisState = dataService.getAnalysisStateByName('Quarantined') + } else if (params.filter?.equalsIgnoreCase("nonPassing")) { + filter.isNonPassing = true } if (params.tag) { @@ -447,29 +449,97 @@ class TestOutcomeService { } - String getDelimitedTextForTestOutcomes(List outcomes, String delimiter) { + String getDelimitedTextForTestOutcomes(List outcomes, List columns, String delimiter) { StringBuffer buff = new StringBuffer() String d = delimiter - buff << "Test Outcome ID${d}Test Result${d}Analysis State${d}Started At${d}Finished At${d}Duration${d}Bug Title${d}Bug URL${d}Note\n" + def standardColumns = ["testCase", "parameters", "tags", "result", "streak", "successRate", "analysisState", + "startedAt", "finishedAt", "duration", "bug", "owner", "note", "testOutput", "links"] + def columnNames = ["Name", "Test Result", "Analysis State", "Started At", "Finished At", "Duration", + "Bug", "Note"] + def columnFields = ["testCase", "result", "analysisState", "startedAt", "finishedAt", "duration", + "bug", "note"] + def propertyFields = [] + + if (columns) { + columnNames = [] + columnFields = [] + columns.each { column -> + columnNames << column[0] + columnFields << column[1] + if (!standardColumns.contains(column[1])) { + propertyFields << column[0] + } + } + } + + buff << "Test Outcome ID${d}" + columnNames.each { name -> + buff << name << d + } + buff.setLength(buff.length() - 1) + buff << '\n' outcomes.each { outcome -> def renderList = [] renderList << outcome.id - renderList << outcome.testResult.name - renderList << outcome.analysisState?.name - renderList << outcome.startedAt - renderList << outcome.finishedAt - renderList << outcome.duration - renderList << outcome.bug?.title - renderList << outcome.bug?.url - renderList << outcome.note + if ("testCase" in columnFields) + renderList << outcome.testCase.fullName + if ("parameters" in columnFields) + renderList << outcome.testCase.parameters + if ("tags" in columnFields) + renderList << (outcome.tags.isEmpty() ? null : outcome.tags) + if ("result" in columnFields) + renderList << outcome.testResult.name + if ("streak" in columnFields) + renderList << outcome.testOutcomeStats?.streak + if ("successRate" in columnFields) + renderList << outcome.testOutcomeStats?.successRate + if ("analysisState" in columnFields) + renderList << outcome.analysisState?.name + if ("startedAt" in columnFields) + renderList << outcome.startedAt + if ("finishedAt" in columnFields) + renderList << outcome.finishedAt + if ("duration" in columnFields) + renderList << outcome.duration + if ("bug" in columnFields) + renderList << outcome.bug?.title + if ("owner" in columnFields) + renderList << outcome.owner + if ("note" in columnFields) + renderList << outcome.note + if ("testOutput" in columnFields) + renderList << outcome.testOutput + if ("links" in columnFields) + renderList << (outcome.links.isEmpty() ? null : outcome.links) + + propertyFields.each { propertyField -> + def found = false + outcome.testProperties.each { property -> + if (property.name == propertyField) { + renderList << property.value + found = true + } + } + if (!found) { + renderList << null + } + } + renderList.eachWithIndex { it, indx -> if (it == null) { - buff << '' + it = '' } else { - buff << it.toString().replaceAll(delimiter, "\\${delimiter}") + it = it.toString() + it = it.replaceAll(delimiter, "\\${delimiter}") + it = it.replaceAll("\"", "\"\"") + if (it.contains("\"") || it.contains(delimiter) || it.contains("\n")) { + it = "\"${it}\"" + } } - //buff << it != null ? it : '' + + buff << it + if (indx != renderList.size() - 1) { buff << delimiter } diff --git a/grails/grails-app/utils/cuanto/CustomDialect.groovy b/grails/grails-app/utils/cuanto/CustomDialect.groovy new file mode 100644 index 0000000..a8738fc --- /dev/null +++ b/grails/grails-app/utils/cuanto/CustomDialect.groovy @@ -0,0 +1,14 @@ +package cuanto + +import org.hibernate.dialect.MySQL5InnoDBDialect; +import org.hibernate.dialect.function.StandardSQLFunction; +import org.hibernate.type.StringType; + +class CustomDialect extends MySQL5InnoDBDialect { + + public CustomDialect() { + super() + registerFunction("group_concat", new StandardSQLFunction("group_concat", new StringType())); + } + +} \ No newline at end of file diff --git a/grails/grails-app/utils/cuanto/QueryBuilder.groovy b/grails/grails-app/utils/cuanto/QueryBuilder.groovy index abab4f4..b46cb55 100644 --- a/grails/grails-app/utils/cuanto/QueryBuilder.groovy +++ b/grails/grails-app/utils/cuanto/QueryBuilder.groovy @@ -33,8 +33,12 @@ public class QueryBuilder { Map result List fromClauses = [] List whereClauses = [] + List groupByClauses = [] + List havingClauses = [] List params = [] + List processors = getProcessors(queryFilter.appliesToClass()) + processors.each {QueryModule queryProcessor -> def details = queryProcessor.getQueryParts(queryFilter) @@ -44,18 +48,32 @@ public class QueryBuilder { if (details.where?.trim()) { whereClauses << " ${details.where} " - if (details.params) { - params += details.params - } + } + + if (details.having?.trim()) { + havingClauses << " ${details.having} " + } + + if (details.group?.trim()) { + groupByClauses << " ${details.group} " + } + + if (details.params) { + params += details.params } } + + String selectClause = queryFilter.countClause() + String fromClause = queryFilter.fromClause() fromClauses.each { fromClause += " ${it} " } + if (whereClauses.size() == 0) { throw new CuantoException("No filter options were specified for query") } + String whereClause = "" whereClauses.eachWithIndex {clause, idx -> whereClause += " ${clause}" @@ -65,15 +83,33 @@ public class QueryBuilder { whereClause += " " } } - String selectClause = queryFilter.countClause() - String query = selectClause + " " + fromClause + " where " + whereClause + + String groupByClause = "" + if (groupByClauses) { + groupByClause = " group by " + groupByClauses.join(", ") + } + + String havingClause = "" + if (havingClauses) { + havingClause = " having " + havingClauses.eachWithIndex {clause, idx -> + havingClause += " ${clause}" + if (idx < havingClauses.size() - 1) { + havingClause += " and " + } + else { + havingClause += " " + } + } + } + + String query = selectClause + " " + fromClause + " where " + whereClause + groupByClause + havingClause result = [hql: query.toString(), 'params': params.flatten()] return new CuantoQuery(hql: result.hql as String, positionalParameters: result.params as List) } CuantoQuery buildQuery(QueryFilter queryFilter) throws CuantoException { - Map base = buildQueryForBaseQuery(queryFilter) String query = base.hql as String @@ -112,6 +148,8 @@ public class QueryBuilder { def buildQueryForBaseQuery(QueryFilter queryFilter) { List fromClauses = [] List whereClauses = [] + List groupByClauses = [] + List havingClauses = [] List params = [] List processors = getProcessors(queryFilter.appliesToClass()) @@ -125,10 +163,19 @@ public class QueryBuilder { if (details.where?.trim()) { whereClauses << " ${details.where} " - if (details.params) { - params += details.params - } } + + if (details.having?.trim()) { + havingClauses << " ${details.having} " + } + + if (details.group?.trim()) { + groupByClauses << " ${details.group} " + } + + if (details.params) { + params += details.params + } } String selectClause = queryFilter.selectClause() @@ -152,7 +199,26 @@ public class QueryBuilder { } } - String query = selectClause + " " + fromClause + " where " + whereClause + String groupByClause = "" + if (groupByClauses) { + groupByClause = " group by " + groupByClauses.join(", ") + } + + String havingClause = "" + if (havingClauses) { + havingClause = " having " + havingClauses.eachWithIndex {clause, idx -> + havingClause += " ${clause}" + if (idx < havingClauses.size() - 1) { + havingClause += " and " + } + else { + havingClause += " " + } + } + } + + String query = selectClause + " " + fromClause + " where " + whereClause + groupByClause + havingClause return [hql: query.toString(), 'params': params.flatten()] } diff --git a/grails/grails-app/utils/cuanto/queryprocessor/TagNameQueryModule.groovy b/grails/grails-app/utils/cuanto/queryprocessor/TagNameQueryModule.groovy index ec95df3..413a3d5 100644 --- a/grails/grails-app/utils/cuanto/queryprocessor/TagNameQueryModule.groovy +++ b/grails/grails-app/utils/cuanto/queryprocessor/TagNameQueryModule.groovy @@ -28,19 +28,34 @@ import cuanto.TestOutcome public class TagNameQueryModule implements QueryModule { public Map getQueryParts(QueryFilter queryFilter) { - if (queryFilter.tags) { - def whereClauses = [] - def params = [] - queryFilter.tags.each { tagName-> - whereClauses << "upper(tag_0.name) like ?" - params << tagName.toUpperCase() - } - def whereText = "(" + whereClauses.join(" or ") + ")" - return [from: "inner join t.tags tag_0", where: whereText, - 'params': params ] - } else { - return [:] - } + def combineWith = "and" + def parts = [:] + if (queryFilter.tags) { + if (combineWith.equals("or")) { + def whereClauses = [] + def params = [] + queryFilter.tags.each { tagName-> + whereClauses << "upper(tag_0.name) like ?" + params << tagName.toUpperCase() + } + def whereText = "(" + whereClauses.join(" OR ") + ")" + parts = [from: "inner join t.tags tag_0", where: whereText, + 'params': params ] + } else { + def having = [] + def params = [] + queryFilter.tags.each { tagName-> + having << "upper(col_0_0_) like ?" + params << "%" + tagName.toUpperCase() + "%" + } + parts = [from: "inner join t.tags tag_0", + group: "t.id", + having: "(" + having.join(" AND ") + ")", + params: params] + } + + } + return parts } diff --git a/grails/grails-app/utils/cuanto/queryprocessor/TestOutcomeHasAllPropertiesQueryModule.groovy b/grails/grails-app/utils/cuanto/queryprocessor/TestOutcomeHasAllPropertiesQueryModule.groovy index a8aac7e..dbe95e0 100644 --- a/grails/grails-app/utils/cuanto/queryprocessor/TestOutcomeHasAllPropertiesQueryModule.groovy +++ b/grails/grails-app/utils/cuanto/queryprocessor/TestOutcomeHasAllPropertiesQueryModule.groovy @@ -36,8 +36,11 @@ class TestOutcomeHasAllPropertiesQueryModule implements QueryModule { queryFilter.hasAllTestOutcomeProperties.eachWithIndex { prop, indx -> fromClauses << " left join t.testProperties prop_${indx} " - whereClauses << " upper(prop_${indx}.name) = ? and upper(prop_${indx}.value) like ? " - qryArgs << prop.name.toUpperCase() + if (prop.name.trim()) { + whereClauses << " upper(prop_${indx}.name) = ? " + qryArgs << prop.name.toUpperCase() + } + whereClauses << " upper(prop_${indx}.value) like ? " qryArgs << "%${prop.value.toUpperCase()}%" } diff --git a/grails/grails-app/utils/cuanto/queryprocessor/TestResultIsNonPassingQueryModule.groovy b/grails/grails-app/utils/cuanto/queryprocessor/TestResultIsNonPassingQueryModule.groovy new file mode 100644 index 0000000..b7aa640 --- /dev/null +++ b/grails/grails-app/utils/cuanto/queryprocessor/TestResultIsNonPassingQueryModule.groovy @@ -0,0 +1,46 @@ +/* + +Copyright (c) 2010 Todd Wells + +This file is part of Cuanto, a test results repository and analysis program. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . + +*/ + +package cuanto.queryprocessor + +import cuanto.QueryFilter +import cuanto.TestOutcome +public class TestResultIsNonPassingQueryModule implements QueryModule { + + /** + * Based on the value of queryFilter.isNonPassing: + * If true then all returned outcomes that are considered failures or skips will be returned. + * If null, outcomes will not be returned based on failure status. + */ + public Map getQueryParts(QueryFilter queryFilter) { + if (queryFilter.isNonPassing != null) { + return [where: " (t.testResult.isFailure = ? OR t.testResult.isSkip = ?) ", params: [queryFilter.isNonPassing, queryFilter.isNonPassing]] + } else { + return [:] + } + } + + + public List getObjectTypes() { + return [TestOutcome.class] + } + +} \ No newline at end of file diff --git a/grails/grails-app/views/testRun/_header.gsp b/grails/grails-app/views/testRun/_header.gsp index 90b9884..25f4225 100644 --- a/grails/grails-app/views/testRun/_header.gsp +++ b/grails/grails-app/views/testRun/_header.gsp @@ -26,8 +26,9 @@ along with this program. If not, see . Test Run ${testRun?.dateExecuted?.encodeAsHTML()} - Permalink ${bullet} - Export ${bullet} + + Permalink ${bullet} + Export ${bullet} Edit ${bullet} Delete ${bullet} diff --git a/grails/grails-app/views/testRun/_tags.gsp b/grails/grails-app/views/testRun/_tags.gsp index b9d7750..9fc0f6f 100644 --- a/grails/grails-app/views/testRun/_tags.gsp +++ b/grails/grails-app/views/testRun/_tags.gsp @@ -43,9 +43,6 @@ along with this program. If not, see . - -
-
diff --git a/grails/grails-app/views/testRun/export.gsp b/grails/grails-app/views/testRun/export.gsp index 99c0e9b..32b0838 100644 --- a/grails/grails-app/views/testRun/export.gsp +++ b/grails/grails-app/views/testRun/export.gsp @@ -29,10 +29,10 @@ along with this program. If not, see .
Export Test Run ${testRun?.dateExecuted?.encodeAsHTML()} of Project ${testRun?.project?.name?.encodeAsHTML()}
    -
  • Comma Separated (CSV)
  • -
  • Tab Separated (TSV)
  • -
  • XML
  • -
  • Bucketed TestNG Suite
  • +
  • Comma Separated (CSV)
  • +
  • Tab Separated (TSV)
  • +
  • XML
  • +
  • Bucketed TestNG Suite


diff --git a/grails/grails-app/views/testRun/results.gsp b/grails/grails-app/views/testRun/results.gsp index 98e99fd..988da75 100644 --- a/grails/grails-app/views/testRun/results.gsp +++ b/grails/grails-app/views/testRun/results.gsp @@ -74,14 +74,24 @@ along with this program. If not, see . YAHOO.util.Event.onDOMReady(function () { - var newWidth = $(window).width() * .95; - $('#tabContainer').width(newWidth); - tabView = new YAHOO.widget.TabView("tabContainer"); + + var delay; + var onWindowResize = function() { + clearTimeout(delay); + delay = setTimeout(resizeContent, 200); + } + function resizeContent() { + var newWidth = $(window).width() - 30; + $('#tabContainer').width(newWidth); + } + YAHOO.util.Event.addListener(window, "resize", onWindowResize); + + resizeContent(); + tabView = new YAHOO.widget.TabView("tabContainer"); new YAHOO.cuanto.SummaryTab(); new YAHOO.cuanto.AnalysisTable(${testResultList}, ${analysisStateList}); new YAHOO.cuanto.GroupedOutput(); - }); diff --git a/grails/scripts/_BuildCuanto.groovy b/grails/scripts/_BuildCuanto.groovy index 48bdd24..459844d 100644 --- a/grails/scripts/_BuildCuanto.groovy +++ b/grails/scripts/_BuildCuanto.groovy @@ -59,6 +59,7 @@ Closure getModulePackager(moduleName, moduleDir, pomXml, targetDir) println "beginning packaging" def packageProcess = "mvn -f ${moduleDir}/pom.xml clean install".execute() + packageProcess.in.eachLine { line -> println line } packageProcess.waitFor() println "clean package done" if (packageProcess.exitValue() != 0) { diff --git a/grails/test/integration/cuanto/TestOutcomeQueryFilterTests.groovy b/grails/test/integration/cuanto/TestOutcomeQueryFilterTests.groovy index dc19cad..d2de431 100644 --- a/grails/test/integration/cuanto/TestOutcomeQueryFilterTests.groovy +++ b/grails/test/integration/cuanto/TestOutcomeQueryFilterTests.groovy @@ -1,6 +1,7 @@ package cuanto import cuanto.test.TestObjects +import org.codehaus.groovy.grails.commons.ApplicationHolder /** * User: Todd Wells @@ -84,6 +85,10 @@ public class TestOutcomeQueryFilterTests extends GroovyTestCase { void testFilterByTagsAndTestRun() { + // tests involving tags depend on MySQL specific code + if (!ApplicationHolder.application.config.dataSource.driverClassName.contains("mysql")) { + return + } def testCases = [] def numOutcomes = 10 0.upto(numOutcomes - 1) { @@ -122,48 +127,65 @@ public class TestOutcomeQueryFilterTests extends GroovyTestCase { outcomes[6].addToTags(tags[0]) outcomes[6].addToTags(tags[1]) outcomes[6].addToTags(tags[2]) - outcomes[6].addToTags(tags[3]) + + outcomes[7].addToTags(tags[0]) outcomes.each { dataService.saveDomainObject it, true } + // 1 tag - tag 0, 4 tests TestOutcomeQueryFilter queryFilterA = new TestOutcomeQueryFilter() queryFilterA.testRun = testRun - queryFilterA.tags = tags.collect{it.name} + queryFilterA.tags = [tags[0].name] def fetchedOutcomes = dataService.getTestOutcomes(queryFilterA) - assertEquals "Wrong number of TestOutcomes returned", 7, fetchedOutcomes.size() + assertEquals "Wrong number of TestOutcomes returned", 4, fetchedOutcomes.size() + // try it also with sorts + queryFilterA.sorts = [] + queryFilterA.sorts << new SortParameters(sort: "testRun.dateExecuted", sortOrder: "desc") + queryFilterA.sorts << new SortParameters(sort: "testRun.lastUpdated", sortOrder: "desc") + fetchedOutcomes = dataService.getTestOutcomes(queryFilterA) + assertEquals "Wrong number of TestOutcomes returned", 4, fetchedOutcomes.size() + + // 2 tags - tag 0, 1 - 2 tests (4, 6) TestOutcomeQueryFilter queryFilterB = new TestOutcomeQueryFilter() queryFilterB.testRun = testRun queryFilterB.tags = tags[0..1].collect{ it.name } fetchedOutcomes = dataService.getTestOutcomes(queryFilterB) - assertEquals "Wrong number of TestOutcomes returned", 4, fetchedOutcomes.size() + assertEquals "Wrong number of TestOutcomes returned", 2, fetchedOutcomes.size() fetchedOutcomes.each { outcome -> - assertNotNull "Couldnt find a matching tag", outcome.tags.find {it.name == tags[0].name} || outcome.tags.find {it.name == tags[1].name} + assertNotNull "Couldnt find a matching tag", outcome.tags.find {it.name == tags[0].name} && outcome.tags.find {it.name == tags[1].name} } // now do all uppercase, tags should still be found queryFilterB.tags = tags[0..1].collect{ it.name.toUpperCase() } fetchedOutcomes = dataService.getTestOutcomes(queryFilterB) - assertEquals "Wrong number of TestOutcomes returned", 4, fetchedOutcomes.size() + assertEquals "Wrong number of TestOutcomes returned", 2, fetchedOutcomes.size() fetchedOutcomes.each { outcome -> - assertNotNull "Couldnt find a matching tag", outcome.tags.find {it.name == tags[0].name} || outcome.tags.find {it.name == tags[1].name} + assertNotNull "Couldnt find a matching tag", outcome.tags.find {it.name == tags[0].name} && outcome.tags.find {it.name == tags[1].name} } - // now search for TestOutcomes without any tags + // all tags - 0 tests TestOutcomeQueryFilter queryFilterC = new TestOutcomeQueryFilter() queryFilterC.testRun = testRun + queryFilterC.sorts = queryFilterA.sorts + queryFilterC.tags = tags.collect{ it.name } + fetchedOutcomes = dataService.getTestOutcomes(queryFilterC) + assertEquals "Wrong number of TestOutcomes returned", 0, fetchedOutcomes.size() + + // now search for TestOutcomes without any tags + queryFilterC = new TestOutcomeQueryFilter() queryFilterC.hasTags = false fetchedOutcomes = dataService.getTestOutcomes(queryFilterC) - assertEquals "Wrong number of TestOutcomes returned", 3, fetchedOutcomes.size() + assertEquals "Wrong number of TestOutcomes returned", 2, fetchedOutcomes.size() queryFilterC.hasTags = true fetchedOutcomes = dataService.getTestOutcomes(queryFilterC) - assertEquals "Wrong number of TestOutcomes returned", 7, fetchedOutcomes.size() + assertEquals "Wrong number of TestOutcomes returned", 8, fetchedOutcomes.size() } @@ -239,32 +261,32 @@ public class TestOutcomeQueryFilterTests extends GroovyTestCase { fetchedOutcomes = dataService.getTestOutcomes(queryfilterD) assertEquals "Wrong number of TestOutcomes returned", 0, fetchedOutcomes.size() - println "****** ${Tag.count()} tags" + // tests involving tags depend on MySQL specific code + if (ApplicationHolder.application.config.dataSource.driverClassName.contains("mysql")) { + println "****** ${Tag.count()} tags" - TestOutcomeQueryFilter queryFilterWithTagsB = new TestOutcomeQueryFilter() - queryFilterWithTagsB.testRun = testRun - //queryFilterWithTagsB.hasAllTestOutcomeProperties = [new TestOutcomeProperty("john", "lennon")] - queryFilterWithTagsB.tags = ["cool"] + TestOutcomeQueryFilter queryFilterWithTagsB = new TestOutcomeQueryFilter() + queryFilterWithTagsB.testRun = testRun + queryFilterWithTagsB.tags = ["cool"] - fetchedOutcomes = dataService.getTestOutcomes(queryFilterWithTagsB) - assertEquals "Wrong number of TestOutcomes returned", 3, fetchedOutcomes.size() + fetchedOutcomes = dataService.getTestOutcomes(queryFilterWithTagsB) + assertEquals "Wrong number of TestOutcomes returned", 3, fetchedOutcomes.size() + TestOutcomeQueryFilter queryFilterWithTags = new TestOutcomeQueryFilter() + queryFilterWithTags.testRun = testRun + queryFilterWithTags.hasAllTestOutcomeProperties = [new TestOutcomeProperty("john", "lennon")] + queryFilterWithTags.tags = ["cool"] - TestOutcomeQueryFilter queryFilterWithTags = new TestOutcomeQueryFilter() - queryFilterWithTags.testRun = testRun - queryFilterWithTags.hasAllTestOutcomeProperties = [new TestOutcomeProperty("john", "lennon")] - queryFilterWithTags.tags = ["cool"] + fetchedOutcomes = dataService.getTestOutcomes(queryFilterWithTags) + assertEquals "Wrong number of TestOutcomes returned", 3, fetchedOutcomes.size() - fetchedOutcomes = dataService.getTestOutcomes(queryFilterWithTags) - assertEquals "Wrong number of TestOutcomes returned", 3, fetchedOutcomes.size() - - TestOutcomeQueryFilter queryFilterWithTagsC = new TestOutcomeQueryFilter() - queryFilterWithTagsC.testRun = testRun - queryFilterWithTagsC.hasAllTestOutcomeProperties = [new TestOutcomeProperty("george", "harrison")] - queryFilterWithTagsC.tags = ["cool"] - - fetchedOutcomes = dataService.getTestOutcomes(queryFilterWithTagsC) - assertEquals "Wrong number of TestOutcomes returned", 1, fetchedOutcomes.size() + TestOutcomeQueryFilter queryFilterWithTagsC = new TestOutcomeQueryFilter() + queryFilterWithTagsC.testRun = testRun + queryFilterWithTagsC.hasAllTestOutcomeProperties = [new TestOutcomeProperty("george", "harrison")] + queryFilterWithTagsC.tags = ["cool"] + fetchedOutcomes = dataService.getTestOutcomes(queryFilterWithTagsC) + assertEquals "Wrong number of TestOutcomes returned", 1, fetchedOutcomes.size() + } } } \ No newline at end of file diff --git a/grails/test/integration/cuanto/TestOutcomeServiceTests.groovy b/grails/test/integration/cuanto/TestOutcomeServiceTests.groovy index f854c60..4dc96f2 100644 --- a/grails/test/integration/cuanto/TestOutcomeServiceTests.groovy +++ b/grails/test/integration/cuanto/TestOutcomeServiceTests.groovy @@ -161,7 +161,7 @@ public class TestOutcomeServiceTests extends GroovyTestCase { outcomes << outcome } - def csv = testOutcomeService.getDelimitedTextForTestOutcomes(outcomes, ",") + def csv = testOutcomeService.getDelimitedTextForTestOutcomes(outcomes, null, ",") def csvLines = csv.readLines() assertEquals "Wrong number of lines for CSV output", outcomes.size() + 1, csvLines.size() diff --git a/grails/test/unit/QueryBuilderTests.groovy b/grails/test/unit/QueryBuilderTests.groovy index 088cb49..f366dac 100644 --- a/grails/test/unit/QueryBuilderTests.groovy +++ b/grails/test/unit/QueryBuilderTests.groovy @@ -441,8 +441,8 @@ public class QueryBuilderTests extends GroovyTestCase { qf.sorts << new SortParameters(sort: "testRun.dateExecuted", sortOrder: "desc") CuantoQuery expectedQuery = new CuantoQuery() - expectedQuery.hql = "select distinct t, t.testRun.dateExecuted from cuanto.TestOutcome t inner join t.tags tag_0 where t.testRun = ? and t.testRun.dateExecuted > ? and t.testRun.dateExecuted < ? and (upper(tag_0.name) like ? or upper(tag_0.name) like ?) order by t.testRun.dateExecuted desc" - expectedQuery.positionalParameters = [qf.testRun, qf.dateCriteria[0].date, qf.dateCriteria[1].date, qf.tags[0].toUpperCase(), qf.tags[1].toUpperCase()] + expectedQuery.hql = "select distinct group_concat(tag_0.name), t, t.testRun.dateExecuted from cuanto.TestOutcome t inner join t.tags tag_0 where t.testRun = ? and t.testRun.dateExecuted > ? and t.testRun.dateExecuted < ? group by t.id having (upper(col_0_0_) like ? AND upper(col_0_0_) like ?) order by t.testRun.dateExecuted desc" + expectedQuery.positionalParameters = [qf.testRun, qf.dateCriteria[0].date, qf.dateCriteria[1].date, "%${qf.tags[0].toUpperCase()}%", "%${qf.tags[1].toUpperCase()}%"] expectedQuery.paginateParameters = [:] CuantoQuery actualQuery = queryBuilder.buildQuery(qf) diff --git a/grails/web-app/css/analysis.css b/grails/web-app/css/analysis.css index d371a8c..7e96193 100644 --- a/grails/web-app/css/analysis.css +++ b/grails/web-app/css/analysis.css @@ -184,6 +184,10 @@ overflow: auto; } +#trDetailsTable > table { + width: 100%; +} + .yui-skin-sam tr.yui-dt-selected td a { color: white; } diff --git a/grails/web-app/js/cuanto/analysisTable.js b/grails/web-app/js/cuanto/analysisTable.js index 1de8e0d..cf4459b 100644 --- a/grails/web-app/js/cuanto/analysisTable.js +++ b/grails/web-app/js/cuanto/analysisTable.js @@ -25,6 +25,9 @@ YAHOO.cuanto.AnalysisTable = function(testResultNames, analysisStateNames, propN var analysisCookieName = "cuantoAnalysis"; var prefTcFormat = "tcFormat"; var prefHiddenColumns = "hiddenColumns"; + var prefRowsPerPage = "rowsPerPage"; + + var defaultRowsPerPage = 10; var dataTable; var analysisDialog; @@ -79,6 +82,7 @@ YAHOO.cuanto.AnalysisTable = function(testResultNames, analysisStateNames, propN onSearchTermChange(null); dataTable = new YAHOO.widget.DataTable("trDetailsTable", getDataTableColumnDefs(), getAnalysisDataSource(), getDataTableConfig()); + dataTable.get('paginator').subscribe('rowsPerPageChange', onRowsPerPageChange); var hiddenCols = getHiddenColumns(); @@ -142,6 +146,7 @@ YAHOO.cuanto.AnalysisTable = function(testResultNames, analysisStateNames, propN YAHOO.util.Event.addListener("deleteTestRun", "click", deleteTestRun); YAHOO.util.Event.addListener("recalcStats", "click", recalcStats); YAHOO.util.Event.addListener("chooseColumns", "click", chooseColumns); + YAHOO.util.Event.addListener("export", "click", exportResults); new YAHOO.widget.Tooltip("feedtt", {context:"feedImg"}); @@ -186,7 +191,7 @@ YAHOO.cuanto.AnalysisTable = function(testResultNames, analysisStateNames, propN width: tableWidth + "px", renderLoopSize:10, generateRequest: buildOutcomeQueryString, - paginator: getDataTablePaginator(0, Number.MAX_VALUE, 0, 10), + paginator: getDataTablePaginator(0, Number.MAX_VALUE, 0, getRowsPerPage(defaultRowsPerPage)), sortedBy: {key:"testCase", dir:YAHOO.widget.DataTable.CLASS_ASC}, dynamicData: true }; @@ -244,7 +249,7 @@ YAHOO.cuanto.AnalysisTable = function(testResultNames, analysisStateNames, propN return name.toLowerCase() == prop.toLowerCase(); }); if (matchingNames.length == 0) { - var col = new YAHOO.widget.Column({key: prop, label: prop, resizeable: true, width: 100, sortable:true, + var col = new YAHOO.widget.Column({key: prop, label: prop, resizeable: true, width: 100, sortable:false, formatter: propertyFormatter}); var hiddenCols = getHiddenColumns(); dataTable.insertColumn(col); @@ -315,6 +320,22 @@ YAHOO.cuanto.AnalysisTable = function(testResultNames, analysisStateNames, propN return newRequest; } + function generateExportRequest() { + var order; + if (dataTable.get("sortedBy").dir == YAHOO.widget.DataTable.CLASS_DESC) { + order = "desc"; + } else { + order = "asc"; + } + var newRequest = "filter=" + getCurrentFilter() + + "&order=" + order + + "&sort=" + dataTable.get("sortedBy").key + + getSearchQuery() + + getTagsQuery() + + "&rand=" + new Date().getTime(); + + return newRequest; + } function onFilterChangeEvent(e, arg) { var filter = arg[0]; @@ -412,14 +433,20 @@ YAHOO.cuanto.AnalysisTable = function(testResultNames, analysisStateNames, propN function getDefaultTableState() { - var tcFormat = YAHOO.util.Cookie.getSub(analysisCookieName, prefTcFormat); + var tcFormat = getSubCookie(prefTcFormat); if (tcFormat) { $('#tcFormat').val(tcFormat); } else { setTcFormatPref(); } - return "format=json&offset=0&max=10&order=asc&sort=testCase&filter=" + getCurrentFilter() + - "&tcFormat=" + tcFormat + "&rand=" + new Date().getTime(); + return "format=json" + + "&offset=0" + + "&max=" + getRowsPerPage(defaultRowsPerPage) + + "&order=asc" + + "&sort=testCase" + + "&filter=" + getCurrentFilter() + + "&tcFormat=" + tcFormat + + "&rand=" + new Date().getTime(); } @@ -830,19 +857,37 @@ YAHOO.cuanto.AnalysisTable = function(testResultNames, analysisStateNames, propN function onTcFormatChange(e) { - YAHOO.util.Cookie.setSub(analysisCookieName, prefTcFormat, getCurrentTcFormat(), {path: "/", expires: new Date().getDate() + 30}); setTcFormatPref(); onTableStateChange(e); } function setTcFormatPref() { var tcFormat = getCurrentTcFormat(); - var expDate = new Date(); - expDate.setDate(expDate.getDate() + 30); - YAHOO.util.Cookie.setSub(analysisCookieName, prefTcFormat, tcFormat, {path: "/", expires: expDate}); + setSubCookie(prefTcFormat, tcFormat); return tcFormat; } + function onRowsPerPageChange(e) { + setSubCookie("rowsPerPage", e.newValue); + } + + function getRowsPerPage(defaultVal) { + var rowsPerPage = getSubCookie("rowsPerPage"); + if (!rowsPerPage) { + rowsPerPage = defaultVal; + } + return rowsPerPage; + } + + function setSubCookie(name, value) { + var expDate = new Date(); + expDate.setDate(expDate.getDate() + 365); + YAHOO.util.Cookie.setSub(analysisCookieName, name, value, {path: "/", expires: expDate}) + } + + function getSubCookie(name) { + return YAHOO.util.Cookie.getSub(analysisCookieName, name); + } function onTableStateChange(e) { var newRequest = generateNewRequest(); @@ -1034,6 +1079,23 @@ YAHOO.cuanto.AnalysisTable = function(testResultNames, analysisStateNames, propN }); } + // modify the export url as it's clicked to add in current view filter + // query parameters + function exportResults(e) { + var requestString = generateExportRequest(); + + var columns = []; + $.each(dataTable.getColumnSet().flat, function(idx, column) { + var isYui = column.key.match(/^yui/); + if (!column.key.match(/^yui/) && !column.hidden) { + columns.push(column.label + "|" + column.key); + } + }); + + requestString += "&columns=" + columns; + e.target.href += "?" + requestString; + } + function unescapeHtmlEntities(s) { return s.replace(/</g, '<').replace(/>/g, '>'); } @@ -1099,6 +1161,24 @@ YAHOO.cuanto.AnalysisTable = function(testResultNames, analysisStateNames, propN $.each($('.tagspan'), function(idx, tagspan) { tagButtons.push(new YAHOO.widget.Button(tagspan.id, {type: "checkbox", checked: false, onclick: { fn: onTagClick } })); }); + + // if there's a tags query param, toggle those tag buttons on + if (YAHOO.util.History.getQueryStringParameter('tags') ) { + var removeUnchecked = true; + var tags = YAHOO.util.History.getQueryStringParameter('tags').split(","); + // loop over tag buttons + for (var i = 2; i < tagButtons.length; i++) { + var button = tagButtons[i]; + if (tags.indexOf(button.get("label")) >= 0) { + button.set("checked", true); + } else { + if (removeUnchecked) { + $(button._button).parents('.tagspan').remove(); + } + } + } + onFilterChange(null); + } } function onTagClick(e) { diff --git a/grails/web-app/js/cuanto/columnDialog.js b/grails/web-app/js/cuanto/columnDialog.js index 5822aad..4a974ee 100644 --- a/grails/web-app/js/cuanto/columnDialog.js +++ b/grails/web-app/js/cuanto/columnDialog.js @@ -56,7 +56,7 @@ YAHOO.cuanto.ColumnDialog = function (datatable, overlayManager, subCookieName, function setAnalysisColumnPref() { var expDate = new Date(); - expDate.setDate(expDate.getDate() + 30); + expDate.setDate(expDate.getDate() + 365); var colHidden = []; $.each(datatable.getColumnSet().flat, function(idx, column) {