-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JQL refactoring draft #23
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.atlassian.performance.tools.jiraactions.api.jql | ||
|
||
import com.atlassian.performance.tools.jiraactions.api.SeededRandom | ||
import com.atlassian.performance.tools.jiraactions.api.page.IssuePage | ||
|
||
interface JqlContext { | ||
fun seededRandom(): SeededRandom | ||
fun issuePage(): IssuePage? | ||
} | ||
|
||
interface JqlSupplier { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to reduce the number of public interfaces? It seems that our API grows quickly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. supplier is replacement for PrescriptionType which is public and could be removed only witch major change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's fine to have many small interfaces in the API. It's much better than having a few huge ones. |
||
fun get(context: JqlContext): Jql? | ||
fun uniqueName(): String | ||
} | ||
|
||
interface Jql { | ||
fun query(): String | ||
fun supplier(): JqlSupplier | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.atlassian.performance.tools.jiraactions.api.memories | ||
|
||
import com.atlassian.performance.tools.jiraactions.api.jql.Jql | ||
import com.atlassian.performance.tools.jiraactions.api.page.IssuePage | ||
import java.util.function.Predicate | ||
|
||
interface JqlMemory2 : Memory<Jql> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this a replacement for
|
||
|
||
fun observe(issuePage: IssuePage) | ||
fun recall(filter: Predicate<Jql>): Jql? | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package com.atlassian.performance.tools.jiraactions.api.memories.adaptive | ||
|
||
import com.atlassian.performance.tools.jiraactions.api.SeededRandom | ||
import com.atlassian.performance.tools.jiraactions.api.jql.Jql | ||
import com.atlassian.performance.tools.jiraactions.api.jql.JqlSupplier | ||
import com.atlassian.performance.tools.jiraactions.api.memories.JqlMemory | ||
import com.atlassian.performance.tools.jiraactions.api.memories.JqlMemory2 | ||
import com.atlassian.performance.tools.jiraactions.api.memories.adaptive.jql.JqlPrescription | ||
import com.atlassian.performance.tools.jiraactions.api.memories.adaptive.jql.JqlPrescriptions | ||
import com.atlassian.performance.tools.jiraactions.api.page.IssuePage | ||
import com.atlassian.performance.tools.jiraactions.jql.BuiltInJQL | ||
import com.atlassian.performance.tools.jiraactions.jql.JqlContextImpl | ||
import org.apache.logging.log4j.LogManager | ||
import org.apache.logging.log4j.Logger | ||
import java.util.function.Predicate | ||
|
||
class AdaptiveJqlMemory2( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why this class is public? We have already public interface JqlMemory2. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in general classes with "2" suffix are replacements, for existing which will be deprecated and removed with major change, so in some time number of interfaces will be the same as before refactoring There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lipinskipawel for more invasive refactoring You can look here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My question was more about the Factory class, I do understand the "2" suffix. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. So concern of this change is to replace "String" jql representation with something else which is more relevant (so all JQL interfaces ore for this reason). Other stuff remains almost untouched (so we still offer Adaptive memories implementations as usual). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The old There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Factories cost complexity and must have additional value to be justified. AFAIK across all JPT libs we expose only one factory: Why did we forbid custom subtypes? Because we needed to add methods to the (old) The question is - do we want to allow custom implementations of |
||
private val random: SeededRandom | ||
) : JqlMemory2 { | ||
|
||
private val logger: Logger = LogManager.getLogger(this::class.java) | ||
|
||
companion object { | ||
fun getBuiltInJqlSuppliers(): List<JqlSupplier> = BuiltInJQL.values().toList() | ||
} | ||
|
||
private val preparedQueries = mutableListOf<Jql>(); | ||
|
||
init { | ||
BuiltInJQL.RESOLVED.get(JqlContextImpl(random))?.let { preparedQueries.add(it) } | ||
BuiltInJQL.GENERIC_WIDE.get(JqlContextImpl(random))?.let { preparedQueries.add(it) } | ||
} | ||
|
||
private val availableJqlSuppliers = mutableSetOf( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
BuiltInJQL.PRIORITIES, | ||
BuiltInJQL.PROJECT, | ||
BuiltInJQL.ASSIGNEE, | ||
BuiltInJQL.REPORTERS, | ||
BuiltInJQL.PROJECT_ASSIGNEE, | ||
BuiltInJQL.GIVEN_WORD | ||
) | ||
|
||
override fun observe(issuePage: IssuePage) { | ||
val jql = availableJqlSuppliers.asSequence() | ||
.map { it.get(JqlContextImpl(random, issuePage)) } | ||
.filter { it != null } | ||
.firstOrNull() | ||
|
||
jql?.let { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Micro-code-style 😉 |
||
logger.debug("Rendered a new jql query: <<${it.query()!!}>>") | ||
preparedQueries.add(it) | ||
availableJqlSuppliers.remove(it.supplier()) | ||
} | ||
} | ||
|
||
override fun recall(): Jql? { | ||
return random.pick(preparedQueries) | ||
} | ||
|
||
override fun remember(memories: Collection<Jql>) { | ||
preparedQueries.addAll(memories) | ||
} | ||
|
||
override fun recall(filter: Predicate<Jql>): Jql? { | ||
return random.pick(preparedQueries.filter {filter.test(it) }.toList()) | ||
} | ||
} | ||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package com.atlassian.performance.tools.jiraactions.jql | ||
|
||
import com.atlassian.performance.tools.jiraactions.api.SeededRandom | ||
import com.atlassian.performance.tools.jiraactions.api.jql.Jql | ||
import com.atlassian.performance.tools.jiraactions.api.jql.JqlContext | ||
import com.atlassian.performance.tools.jiraactions.api.jql.JqlSupplier | ||
import com.atlassian.performance.tools.jiraactions.api.page.IssuePage | ||
|
||
internal class JqlImpl(private val query: String, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "JQL implementation". What makes this one different from the other implementations? It seems that the interface allows dynamic |
||
private val supplier: JqlSupplier): Jql { | ||
override fun query(): String = this.query | ||
override fun supplier(): JqlSupplier = this.supplier | ||
} | ||
|
||
internal class JqlContextImpl(private val rnd: SeededRandom, private val page: IssuePage?) : JqlContext { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
constructor(rnd: SeededRandom) : this(rnd, null) | ||
override fun seededRandom(): SeededRandom = this.rnd | ||
override fun issuePage(): IssuePage? = this.page | ||
} | ||
|
||
internal enum class BuiltInJQL(private val sup: (JqlContext) -> String?) : JqlSupplier { | ||
GENERIC_WIDE({ "text ~ \"a*\" order by summary" }), | ||
RESOLVED({ "resolved is not empty order by description" }), | ||
PRIORITIES({ | ||
ctx -> ctx.issuePage()?.getPossiblePriorities() | ||
?.let { ctx.seededRandom().pickMany(it, 3) } | ||
?.joinToString() | ||
?.let { "priority in ($it) order by reporter" } | ||
}), | ||
PROJECT({ | ||
ctx -> ctx.issuePage()?.getProject()?.let { "project = ${it.key} order by status" } | ||
}), | ||
ASSIGNEE({ | ||
ctx -> ctx.issuePage()?.getAssignee()?.let { "assignee = $it order by project" } | ||
}), | ||
REPORTERS({ | ||
ctx -> ctx.issuePage()?.getReporter()?.let { "reporter was $it order by description" } | ||
}), | ||
PROJECT_ASSIGNEE({ | ||
ctx -> ctx.issuePage()?.let { | ||
val userName = it.getAssignee() | ||
val project = it.getProject() | ||
if (userName != null && project != null) { | ||
"project = ${project.key} and assignee = $userName order by reporter" | ||
} else { | ||
null | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's something wonky about the indents here |
||
|
||
}), | ||
|
||
/** | ||
* Creates a JQL based on the words used in fields of this issue. | ||
* What we need is to pick a word that will get us a non-empty jql, preferably that will return more issues in | ||
* the search result. | ||
* Warning! Current implementation will work only with locales that use Latin alphabet. | ||
*/ | ||
GIVEN_WORD({ | ||
ctx -> ctx.issuePage()?.let { | ||
val whitespacesPattern = Regex("\\s") | ||
val lettersOnly = Regex("\\w+") | ||
|
||
// in order to find a word that's neither long or short, with a chance of it being just a regular word, | ||
// we're looking for a word with three vowels. | ||
val description = it.getDescription() | ||
val summary = it.getSummary() | ||
|
||
val vowels = setOf('a', 'e', 'i', 'o', 'u', 'y') | ||
fun numVowels(s: String): Int { | ||
return s.toCharArray().asSequence().filter { vowels.contains(it) }.count() | ||
} | ||
|
||
"$description $summary" | ||
.split(whitespacesPattern) | ||
.filter { it.isNotBlank() } | ||
.shuffled(ctx.seededRandom().random) | ||
.asSequence() | ||
.filter { it.matches(lettersOnly) } | ||
.filter { numVowels(it) == 3 } | ||
.firstOrNull() | ||
?.let { """text ~ "$it" order by key""" } | ||
} | ||
}); | ||
|
||
override fun uniqueName(): String = this.name | ||
override fun get(context: JqlContext): Jql? = sup.invoke(context)?.let { JqlImpl(it, this) } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like and public SPI. Historically we had many issues with SPI written in Kotlin and how it interoperated with Java and Java interfaces and their default methods. I would suggest writhing this part in Java which should not be costly but may pay off in case we want to extend the SPI in the future without breaking the compatibility.